Merge pull request #41408 from neikeq/senkyu

3.2 New csproj style with backport of Godot.NET.Sdk
This commit is contained in:
Rémi Verschelde 2020-08-21 01:57:54 +02:00 committed by GitHub
commit 20e411545c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 924 additions and 830 deletions

View file

@ -20,3 +20,7 @@ indent_size = 4
[.travis.yml]
indent_style = space
indent_size = 2
[*.{csproj,props,targets,nuspec}]
indent_style = space
indent_size = 2

View file

@ -0,0 +1,16 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.NET.Sdk", "Godot.NET.Sdk\Godot.NET.Sdk.csproj", "{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View file

@ -0,0 +1,35 @@
<Project Sdk="Microsoft.Build.NoTargets/2.0.1">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Description>MSBuild .NET Sdk for Godot projects.</Description>
<Authors>Godot Engine contributors</Authors>
<PackageId>Godot.NET.Sdk</PackageId>
<Version>3.2.3</Version>
<PackageVersion>3.2.3</PackageVersion>
<PackageProjectUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk</PackageProjectUrl>
<PackageType>MSBuildSdk</PackageType>
<PackageTags>MSBuildSdk</PackageTags>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<PropertyGroup>
<NuspecFile>Godot.NET.Sdk.nuspec</NuspecFile>
<GenerateNuspecDependsOn>$(GenerateNuspecDependsOn);SetNuSpecProperties</GenerateNuspecDependsOn>
</PropertyGroup>
<Target Name="SetNuSpecProperties" Condition=" Exists('$(NuspecFile)') ">
<PropertyGroup>
<NuspecProperties>
id=$(PackageId);
description=$(Description);
authors=$(Authors);
version=$(PackageVersion);
packagetype=$(PackageType);
tags=$(PackageTags);
projecturl=$(PackageProjectUrl)
</NuspecProperties>
</PropertyGroup>
</Target>
</Project>

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8" ?>
<package xmlns="http://schemas.microsoft.com/packaging/2011/10/nuspec.xsd">
<metadata>
<id>$id$</id>
<version>$version$</version>
<description>$description$</description>
<authors>$authors$</authors>
<owners>$authors$</owners>
<projectUrl>$projecturl$</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<licenseUrl>https://licenses.nuget.org/MIT</licenseUrl>
<tags>$tags$</tags>
<packageTypes>
<packageType name="$packagetype$" />
</packageTypes>
<repository url="$projecturl$" />
</metadata>
<files>
<file src="Sdk\**" target="Sdk" />\
</files>
</package>

View file

@ -0,0 +1,119 @@
<Project>
<PropertyGroup>
<!-- Determines if we should import Microsoft.NET.Sdk, if it wasn't already imported. -->
<GodotSdkImportsMicrosoftNetSdk Condition=" '$(UsingMicrosoftNETSdk)' != 'true' ">true</GodotSdkImportsMicrosoftNetSdk>
<GodotProjectTypeGuid>{8F3E2DF0-C35C-4265-82FC-BEA011F4A7ED}</GodotProjectTypeGuid>
</PropertyGroup>
<PropertyGroup>
<Configurations>Debug;ExportDebug;ExportRelease</Configurations>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<GodotProjectDir Condition=" '$(SolutionDir)' != '' ">$(SolutionDir)</GodotProjectDir>
<GodotProjectDir Condition=" '$(SolutionDir)' == '' ">$(MSBuildProjectDirectory)</GodotProjectDir>
<GodotProjectDir>$([MSBuild]::EnsureTrailingSlash('$(GodotProjectDir)'))</GodotProjectDir>
<!-- Custom output paths for Godot projects. In brief, 'bin\' and 'obj\' are moved to '$(GodotProjectDir)\.mono\temp\'. -->
<BaseOutputPath>$(GodotProjectDir).mono\temp\bin\</BaseOutputPath>
<OutputPath>$(GodotProjectDir).mono\temp\bin\$(Configuration)\</OutputPath>
<!--
Use custom IntermediateOutputPath and BaseIntermediateOutputPath only if it wasn't already set.
Otherwise the old values may have already been changed by MSBuild which can cause problems with NuGet.
-->
<IntermediateOutputPath Condition=" '$(IntermediateOutputPath)' == '' ">$(GodotProjectDir).mono\temp\obj\$(Configuration)\</IntermediateOutputPath>
<BaseIntermediateOutputPath Condition=" '$(BaseIntermediateOutputPath)' == '' ">$(GodotProjectDir).mono\temp\obj\</BaseIntermediateOutputPath>
<!-- Do not append the target framework name to the output path. -->
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
<Import Sdk="Microsoft.NET.Sdk" Project="Sdk.props" Condition=" '$(GodotSdkImportsMicrosoftNetSdk)' == 'true' " />
<PropertyGroup>
<EnableDefaultNoneItems>false</EnableDefaultNoneItems>
</PropertyGroup>
<!--
The Microsoft.NET.Sdk only understands of the Debug and Release configurations.
We need to set the following properties manually for ExportDebug and ExportRelease.
-->
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' or '$(Configuration)' == 'ExportDebug' ">
<DebugSymbols Condition=" '$(DebugSymbols)' == '' ">true</DebugSymbols>
<Optimize Condition=" '$(Optimize)' == '' ">false</Optimize>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'ExportRelease' ">
<Optimize Condition=" '$(Optimize)' == '' ">true</Optimize>
</PropertyGroup>
<PropertyGroup>
<GodotApiConfiguration Condition=" '$(Configuration)' != 'ExportRelease' ">Debug</GodotApiConfiguration>
<GodotApiConfiguration Condition=" '$(Configuration)' == 'ExportRelease' ">Release</GodotApiConfiguration>
</PropertyGroup>
<!-- Auto-detect the target Godot platform if it was not specified. -->
<PropertyGroup Condition=" '$(GodotTargetPlatform)' == '' ">
<GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(Linux))' ">x11</GodotTargetPlatform>
<GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(FreeBSD))' ">x11</GodotTargetPlatform>
<GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(OSX))' ">osx</GodotTargetPlatform>
<GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(Windows))' ">windows</GodotTargetPlatform>
</PropertyGroup>
<PropertyGroup>
<GodotRealTIsDouble Condition=" '$(GodotRealTIsDouble)' == '' ">false</GodotRealTIsDouble>
</PropertyGroup>
<!-- Godot DefineConstants. -->
<PropertyGroup>
<!-- Define constant to identify Godot builds. -->
<GodotDefineConstants>GODOT</GodotDefineConstants>
<!--
Define constant to determine the target Godot platform. This includes the
recognized platform names and the platform category (PC, MOBILE or WEB).
-->
<GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'windows' ">GODOT_WINDOWS;GODOT_PC</GodotPlatformConstants>
<GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'x11' ">GODOT_X11;GODOT_PC</GodotPlatformConstants>
<GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'osx' ">GODOT_OSX;GODOT_MACOS;GODOT_PC</GodotPlatformConstants>
<GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'server' ">GODOT_SERVER;GODOT_PC</GodotPlatformConstants>
<GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'uwp' ">GODOT_UWP;GODOT_PC</GodotPlatformConstants>
<GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'haiku' ">GODOT_HAIKU;GODOT_PC</GodotPlatformConstants>
<GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'android' ">GODOT_ANDROID;GODOT_MOBILE</GodotPlatformConstants>
<GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'iphone' ">GODOT_IPHONE;GODOT_IOS;GODOT_MOBILE</GodotPlatformConstants>
<GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'javascript' ">GODOT_JAVASCRIPT;GODOT_HTML5;GODOT_WASM;GODOT_WEB</GodotPlatformConstants>
<GodotDefineConstants>$(GodotDefineConstants);$(GodotPlatformConstants)</GodotDefineConstants>
</PropertyGroup>
<PropertyGroup>
<!-- ExportDebug also defines DEBUG like Debug does. -->
<DefineConstants Condition=" '$(Configuration)' == 'ExportDebug' ">$(DefineConstants);DEBUG</DefineConstants>
<!-- Debug defines TOOLS to differenciate between Debug and ExportDebug configurations. -->
<DefineConstants Condition=" '$(Configuration)' == 'Debug' ">$(DefineConstants);TOOLS</DefineConstants>
<DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants>
</PropertyGroup>
<ItemGroup>
<!--
TODO:
We should consider a nuget package for reference assemblies. This is difficult because the
Godot scripting API is continuaslly breaking backwards compatibility even in patch releases.
-->
<Reference Include="GodotSharp">
<Private>false</Private>
<HintPath>$(GodotProjectDir).mono\assemblies\$(GodotApiConfiguration)\GodotSharp.dll</HintPath>
</Reference>
<Reference Include="GodotSharpEditor" Condition=" '$(Configuration)' == 'Debug' ">
<Private>false</Private>
<HintPath>$(GodotProjectDir).mono\assemblies\$(GodotApiConfiguration)\GodotSharpEditor.dll</HintPath>
</Reference>
</ItemGroup>
<PropertyGroup Condition=" '$(AutomaticallyUseReferenceAssemblyPackages)' == '' and '$(MicrosoftNETFrameworkReferenceAssembliesLatestPackageVersion)' == '' ">
<!-- Old 'Microsoft.NET.Sdk' so we reference the 'Microsoft.NETFramework.ReferenceAssemblies' package ourselves. -->
<AutomaticallyUseReferenceAssemblyPackages>true</AutomaticallyUseReferenceAssemblyPackages>
<MicrosoftNETFrameworkReferenceAssembliesLatestPackageVersion>1.0.0</MicrosoftNETFrameworkReferenceAssembliesLatestPackageVersion>
<GodotUseNETFrameworkRefAssemblies>true</GodotUseNETFrameworkRefAssemblies>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,34 @@
<Project>
<Import Sdk="Microsoft.NET.Sdk" Project="Sdk.targets" Condition=" '$(GodotSdkImportsMicrosoftNetSdk)' == 'true' " />
<PropertyGroup>
<EnableGodotProjectTypeGuid Condition=" '$(EnableGodotProjectTypeGuid)' == '' ">true</EnableGodotProjectTypeGuid>
<ProjectTypeGuids Condition=" '$(EnableGodotProjectTypeGuid)' == 'true' ">$(GodotProjectTypeGuid);$(DefaultProjectTypeGuid)</ProjectTypeGuids>
</PropertyGroup>
<PropertyGroup>
<!--
Define constant to determine whether the real_t type in Godot is double precision or not.
By default this is false, like the official Godot builds. If someone is using a custom
Godot build where real_t is double, they can override the GodotRealTIsDouble property.
-->
<DefineConstants Condition=" '$(GodotRealTIsDouble)' == 'true' ">GODOT_REAL_T_IS_DOUBLE;$(DefineConstants)</DefineConstants>
</PropertyGroup>
<!-- Backported from newer Microsoft.NET.Sdk version -->
<Target Name="IncludeTargetingPackReference" BeforeTargets="_GetRestoreSettingsPerFramework;_CheckForInvalidConfigurationAndPlatform"
Condition=" '$(GodotUseNETFrameworkRefAssemblies)' == 'true' and '$(TargetFrameworkMoniker)' != '' and '$(TargetFrameworkIdentifier)' == '.NETFramework' and '$(AutomaticallyUseReferenceAssemblyPackages)' == 'true' ">
<GetReferenceAssemblyPaths
TargetFrameworkMoniker="$(TargetFrameworkMoniker)"
RootPath="$(TargetFrameworkRootPath)"
TargetFrameworkFallbackSearchPaths="$(TargetFrameworkFallbackSearchPaths)"
BypassFrameworkInstallChecks="$(BypassFrameworkInstallChecks)"
SuppressNotFoundError="true">
<Output TaskParameter="FullFrameworkReferenceAssemblyPaths" PropertyName="_FullFrameworkReferenceAssemblyPaths"/>
</GetReferenceAssemblyPaths>
<ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="$(MicrosoftNETFrameworkReferenceAssembliesLatestPackageVersion)" IsImplicitlyDefined="true" Condition="'$(_FullFrameworkReferenceAssemblyPaths)' == ''"/>
</ItemGroup>
</Target>
</Project>

View file

@ -2,7 +2,6 @@ using System;
using System.IO;
using System.Security;
using Microsoft.Build.Framework;
using GodotTools.Core;
namespace GodotTools.BuildLogger
{
@ -18,7 +17,7 @@ namespace GodotTools.BuildLogger
if (null == Parameters)
throw new LoggerException("Log directory was not set.");
var parameters = Parameters.Split(new[] { ';' });
var parameters = Parameters.Split(new[] {';'});
string logDir = parameters[0];
@ -183,4 +182,17 @@ namespace GodotTools.BuildLogger
private StreamWriter issuesStreamWriter;
private int indent;
}
internal static class StringExtensions
{
public static string CsvEscape(this string value, char delimiter = ',')
{
bool hasSpecialChar = value.IndexOfAny(new[] {'\"', '\n', '\r', delimiter}) != -1;
if (hasSpecialChar)
return "\"" + value.Replace("\"", "\"\"") + "\"";
return value;
}
}
}

View file

@ -1,13 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ProjectGuid>{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}</ProjectGuid>
<OutputType>Library</OutputType>
<TargetFramework>net472</TargetFramework>
<LangVersion>7</LangVersion>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>7.2</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Build.Framework" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.Build.Framework" Version="16.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj" />

View file

@ -19,7 +19,10 @@ namespace GodotTools.Core
}
if (attempt > maxAttempts + 1)
return;
{
// Overwrite the oldest one
backupPath = backupPathBase;
}
File.Copy(filePath, backupPath, overwrite: true);
}

View file

@ -1,11 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ProjectGuid>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</ProjectGuid>
<OutputType>Library</OutputType>
<TargetFramework>net472</TargetFramework>
<LangVersion>7</LangVersion>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>7.2</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
</Project>

View file

@ -34,23 +34,13 @@ namespace GodotTools.Core
return rooted ? Path.DirectorySeparatorChar + path : path;
}
private static readonly string driveRoot = Path.GetPathRoot(Environment.CurrentDirectory);
private static readonly string DriveRoot = Path.GetPathRoot(Environment.CurrentDirectory);
public static bool IsAbsolutePath(this string path)
{
return path.StartsWith("/", StringComparison.Ordinal) ||
path.StartsWith("\\", StringComparison.Ordinal) ||
path.StartsWith(driveRoot, StringComparison.Ordinal);
}
public static string CsvEscape(this string value, char delimiter = ',')
{
bool hasSpecialChar = value.IndexOfAny(new char[] { '\"', '\n', '\r', delimiter }) != -1;
if (hasSpecialChar)
return "\"" + value.Replace("\"", "\"\"") + "\"";
return value;
path.StartsWith(DriveRoot, StringComparison.Ordinal);
}
public static string ToSafeDirName(this string dirName, bool allowDirSeparator)

View file

@ -1,16 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ProjectGuid>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</ProjectGuid>
<OutputType>Library</OutputType>
<TargetFramework>net472</TargetFramework>
<LangVersion>7</LangVersion>
<LangVersion>7.2</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Build" />
<PackageReference Include="DotNet.Glob" Version="2.1.1" />
<PackageReference Include="Microsoft.Build" Version="16.5.0" />
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3.0" ExcludeAssets="runtime" PrivateAssets="all" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj" />
</ItemGroup>
<ItemGroup>
<!--
The Microsoft.Build.Runtime package is too problematic so we create a MSBuild.exe stub. The workaround described
here doesn't work with Microsoft.NETFramework.ReferenceAssemblies: https://github.com/microsoft/msbuild/issues/3486
We need a MSBuild.exe file as there's an issue in Microsoft.Build where it executes platform dependent code when
searching for MSBuild.exe before the fallback to not using it. A stub is fine as it should never be executed.
-->
<None Include="MSBuild.exe" CopyToOutputDirectory="Always" />
</ItemGroup>
</Project>

View file

@ -22,6 +22,37 @@ namespace GodotTools.ProjectEditor
return string.Join(".", identifiers);
}
/// <summary>
/// Skips invalid identifier characters including decimal digit numbers at the start of the identifier.
/// </summary>
private static void SkipInvalidCharacters(string source, int startIndex, StringBuilder outputBuilder)
{
for (int i = startIndex; i < source.Length; i++)
{
char @char = source[i];
switch (char.GetUnicodeCategory(@char))
{
case UnicodeCategory.UppercaseLetter:
case UnicodeCategory.LowercaseLetter:
case UnicodeCategory.TitlecaseLetter:
case UnicodeCategory.ModifierLetter:
case UnicodeCategory.LetterNumber:
case UnicodeCategory.OtherLetter:
outputBuilder.Append(@char);
break;
case UnicodeCategory.NonSpacingMark:
case UnicodeCategory.SpacingCombiningMark:
case UnicodeCategory.ConnectorPunctuation:
case UnicodeCategory.DecimalDigitNumber:
// Identifiers may start with underscore
if (outputBuilder.Length > startIndex || @char == '_')
outputBuilder.Append(@char);
break;
}
}
}
public static string SanitizeIdentifier(string identifier, bool allowEmpty)
{
if (string.IsNullOrEmpty(identifier))
@ -44,30 +75,7 @@ namespace GodotTools.ProjectEditor
startIndex += 1;
}
for (int i = startIndex; i < identifier.Length; i++)
{
char @char = identifier[i];
switch (Char.GetUnicodeCategory(@char))
{
case UnicodeCategory.UppercaseLetter:
case UnicodeCategory.LowercaseLetter:
case UnicodeCategory.TitlecaseLetter:
case UnicodeCategory.ModifierLetter:
case UnicodeCategory.LetterNumber:
case UnicodeCategory.OtherLetter:
identifierBuilder.Append(@char);
break;
case UnicodeCategory.NonSpacingMark:
case UnicodeCategory.SpacingCombiningMark:
case UnicodeCategory.ConnectorPunctuation:
case UnicodeCategory.DecimalDigitNumber:
// Identifiers may start with underscore
if (identifierBuilder.Length > startIndex || @char == '_')
identifierBuilder.Append(@char);
break;
}
}
SkipInvalidCharacters(identifier, startIndex, identifierBuilder);
if (identifierBuilder.Length == startIndex)
{

View file

@ -2,8 +2,9 @@ using GodotTools.Core;
using System;
using System.Collections.Generic;
using System.IO;
using DotNet.Globbing;
using System.Linq;
using Microsoft.Build.Construction;
using Microsoft.Build.Globbing;
namespace GodotTools.ProjectEditor
{
@ -11,8 +12,6 @@ namespace GodotTools.ProjectEditor
{
public static ProjectItemElement FindItemOrNull(this ProjectRootElement root, string itemType, string include, bool noCondition = false)
{
GlobOptions globOptions = new GlobOptions {Evaluation = {CaseInsensitive = false}};
string normalizedInclude = include.NormalizePath();
foreach (var itemGroup in root.ItemGroups)
@ -25,7 +24,7 @@ namespace GodotTools.ProjectEditor
if (item.ItemType != itemType)
continue;
var glob = Glob.Parse(item.Include.NormalizePath(), globOptions);
var glob = MSBuildGlob.Parse(item.Include.NormalizePath());
if (glob.IsMatch(normalizedInclude))
return item;
@ -34,10 +33,9 @@ namespace GodotTools.ProjectEditor
return null;
}
public static ProjectItemElement FindItemOrNullAbs(this ProjectRootElement root, string itemType, string include, bool noCondition = false)
{
GlobOptions globOptions = new GlobOptions {Evaluation = {CaseInsensitive = false}};
string normalizedInclude = Path.GetFullPath(include).NormalizePath();
foreach (var itemGroup in root.ItemGroups)
@ -50,7 +48,7 @@ namespace GodotTools.ProjectEditor
if (item.ItemType != itemType)
continue;
var glob = Glob.Parse(Path.GetFullPath(item.Include).NormalizePath(), globOptions);
var glob = MSBuildGlob.Parse(Path.GetFullPath(item.Include).NormalizePath());
if (glob.IsMatch(normalizedInclude))
return item;
@ -117,5 +115,19 @@ namespace GodotTools.ProjectEditor
return Guid.Empty;
}
public static bool AreDefaultCompileItemsEnabled(this ProjectRootElement root)
{
var enableDefaultCompileItemsProps = root.PropertyGroups
.Where(g => string.IsNullOrEmpty(g.Condition))
.SelectMany(g => g.Properties
.Where(p => p.Name == "EnableDefaultCompileItems" && string.IsNullOrEmpty(p.Condition)));
bool enableDefaultCompileItems = true;
foreach (var prop in enableDefaultCompileItemsProps)
enableDefaultCompileItems = prop.Value.Equals("true", StringComparison.OrdinalIgnoreCase);
return enableDefaultCompileItems;
}
}
}

View file

@ -1,174 +1,49 @@
using GodotTools.Core;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Microsoft.Build.Construction;
using Microsoft.Build.Evaluation;
namespace GodotTools.ProjectEditor
{
public static class ProjectGenerator
{
private const string CoreApiProjectName = "GodotSharp";
private const string EditorApiProjectName = "GodotSharpEditor";
public const string GodotSdkVersionToUse = "3.2.3";
public const string CSharpProjectTypeGuid = "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}";
public const string GodotProjectTypeGuid = "{8F3E2DF0-C35C-4265-82FC-BEA011F4A7ED}";
public static string GodotSdkAttrValue => $"Godot.NET.Sdk/{GodotSdkVersionToUse}";
public static readonly string GodotDefaultProjectTypeGuids = $"{GodotProjectTypeGuid};{CSharpProjectTypeGuid}";
public static string GenGameProject(string dir, string name, IEnumerable<string> compileItems)
public static ProjectRootElement GenGameProject(string name)
{
string path = Path.Combine(dir, name + ".csproj");
if (name.Length == 0)
throw new ArgumentException("Project name is empty", nameof(name));
ProjectPropertyGroupElement mainGroup;
var root = CreateLibraryProject(name, "Debug", out mainGroup);
var root = ProjectRootElement.Create(NewProjectFileOptions.None);
mainGroup.SetProperty("ProjectTypeGuids", GodotDefaultProjectTypeGuids);
mainGroup.SetProperty("OutputPath", Path.Combine(".mono", "temp", "bin", "$(Configuration)"));
mainGroup.SetProperty("BaseIntermediateOutputPath", Path.Combine(".mono", "temp", "obj"));
mainGroup.SetProperty("IntermediateOutputPath", Path.Combine("$(BaseIntermediateOutputPath)", "$(Configuration)"));
mainGroup.SetProperty("ApiConfiguration", "Debug").Condition = " '$(Configuration)' != 'ExportRelease' ";
mainGroup.SetProperty("ApiConfiguration", "Release").Condition = " '$(Configuration)' == 'ExportRelease' ";
root.Sdk = GodotSdkAttrValue;
var debugGroup = root.AddPropertyGroup();
debugGroup.Condition = " '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ";
debugGroup.AddProperty("DebugSymbols", "true");
debugGroup.AddProperty("DebugType", "portable");
debugGroup.AddProperty("Optimize", "false");
debugGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;DEBUG;TOOLS;");
debugGroup.AddProperty("ErrorReport", "prompt");
debugGroup.AddProperty("WarningLevel", "4");
debugGroup.AddProperty("ConsolePause", "false");
var mainGroup = root.AddPropertyGroup();
mainGroup.AddProperty("TargetFramework", "net472");
var coreApiRef = root.AddItem("Reference", CoreApiProjectName);
coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", CoreApiProjectName + ".dll"));
coreApiRef.AddMetadata("Private", "False");
string sanitizedName = IdentifierUtils.SanitizeQualifiedIdentifier(name, allowEmptyIdentifiers: true);
var editorApiRef = root.AddItem("Reference", EditorApiProjectName);
editorApiRef.Condition = " '$(Configuration)' == 'Debug' ";
editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", EditorApiProjectName + ".dll"));
editorApiRef.AddMetadata("Private", "False");
GenAssemblyInfoFile(root, dir, name);
foreach (var item in compileItems)
{
root.AddItem("Compile", item.RelativeToPath(dir).Replace("/", "\\"));
}
root.Save(path);
return root.GetGuid().ToString().ToUpper();
}
private static void GenAssemblyInfoFile(ProjectRootElement root, string dir, string name, string[] assemblyLines = null, string[] usingDirectives = null)
{
string propertiesDir = Path.Combine(dir, "Properties");
if (!Directory.Exists(propertiesDir))
Directory.CreateDirectory(propertiesDir);
string usingDirectivesText = string.Empty;
if (usingDirectives != null)
{
foreach (var usingDirective in usingDirectives)
usingDirectivesText += "\nusing " + usingDirective + ";";
}
string assemblyLinesText = string.Empty;
if (assemblyLines != null)
assemblyLinesText += string.Join("\n", assemblyLines) + "\n";
string content = string.Format(AssemblyInfoTemplate, usingDirectivesText, name, assemblyLinesText);
string assemblyInfoFile = Path.Combine(propertiesDir, "AssemblyInfo.cs");
File.WriteAllText(assemblyInfoFile, content);
root.AddItem("Compile", assemblyInfoFile.RelativeToPath(dir).Replace("/", "\\"));
}
public static ProjectRootElement CreateLibraryProject(string name, string defaultConfig, out ProjectPropertyGroupElement mainGroup)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentException($"{nameof(name)} cannot be empty", nameof(name));
var root = ProjectRootElement.Create();
root.DefaultTargets = "Build";
mainGroup = root.AddPropertyGroup();
mainGroup.AddProperty("Configuration", defaultConfig).Condition = " '$(Configuration)' == '' ";
mainGroup.AddProperty("Platform", "AnyCPU").Condition = " '$(Platform)' == '' ";
mainGroup.AddProperty("ProjectGuid", "{" + Guid.NewGuid().ToString().ToUpper() + "}");
mainGroup.AddProperty("OutputType", "Library");
mainGroup.AddProperty("OutputPath", Path.Combine("bin", "$(Configuration)"));
mainGroup.AddProperty("RootNamespace", IdentifierUtils.SanitizeQualifiedIdentifier(name, allowEmptyIdentifiers: true));
mainGroup.AddProperty("AssemblyName", name);
mainGroup.AddProperty("TargetFrameworkVersion", "v4.7");
mainGroup.AddProperty("GodotProjectGeneratorVersion", Assembly.GetExecutingAssembly().GetName().Version.ToString());
var exportDebugGroup = root.AddPropertyGroup();
exportDebugGroup.Condition = " '$(Configuration)|$(Platform)' == 'ExportDebug|AnyCPU' ";
exportDebugGroup.AddProperty("DebugSymbols", "true");
exportDebugGroup.AddProperty("DebugType", "portable");
exportDebugGroup.AddProperty("Optimize", "false");
exportDebugGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;DEBUG;");
exportDebugGroup.AddProperty("ErrorReport", "prompt");
exportDebugGroup.AddProperty("WarningLevel", "4");
exportDebugGroup.AddProperty("ConsolePause", "false");
var exportReleaseGroup = root.AddPropertyGroup();
exportReleaseGroup.Condition = " '$(Configuration)|$(Platform)' == 'ExportRelease|AnyCPU' ";
exportReleaseGroup.AddProperty("DebugType", "portable");
exportReleaseGroup.AddProperty("Optimize", "true");
exportReleaseGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;");
exportReleaseGroup.AddProperty("ErrorReport", "prompt");
exportReleaseGroup.AddProperty("WarningLevel", "4");
exportReleaseGroup.AddProperty("ConsolePause", "false");
// References
var referenceGroup = root.AddItemGroup();
referenceGroup.AddItem("Reference", "System");
var frameworkRefAssembliesItem = referenceGroup.AddItem("PackageReference", "Microsoft.NETFramework.ReferenceAssemblies");
// Use metadata (child nodes) instead of attributes for the PackageReference.
// This is for compatibility with 3.2, where GodotTools uses an old Microsoft.Build.
frameworkRefAssembliesItem.AddMetadata("Version", "1.0.0");
frameworkRefAssembliesItem.AddMetadata("PrivateAssets", "All");
root.AddImport(Path.Combine("$(MSBuildBinPath)", "Microsoft.CSharp.targets").Replace("/", "\\"));
// If the name is not a valid namespace, manually set RootNamespace to a sanitized one.
if (sanitizedName != name)
mainGroup.AddProperty("RootNamespace", sanitizedName);
return root;
}
private const string AssemblyInfoTemplate =
@"using System.Reflection;{0}
public static string GenAndSaveGameProject(string dir, string name)
{
if (name.Length == 0)
throw new ArgumentException("Project name is empty", nameof(name));
// Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project.
string path = Path.Combine(dir, name + ".csproj");
[assembly: AssemblyTitle(""{1}"")]
[assembly: AssemblyDescription("""")]
[assembly: AssemblyConfiguration("""")]
[assembly: AssemblyCompany("""")]
[assembly: AssemblyProduct("""")]
[assembly: AssemblyCopyright("""")]
[assembly: AssemblyTrademark("""")]
[assembly: AssemblyCulture("""")]
var root = GenGameProject(name);
// The assembly version has the format ""{{Major}}.{{Minor}}.{{Build}}.{{Revision}}"".
// The form ""{{Major}}.{{Minor}}.*"" will automatically update the build and revision,
// and ""{{Major}}.{{Minor}}.{{Build}}.*"" will update just the revision.
root.Save(path);
[assembly: AssemblyVersion(""1.0.*"")]
// The following attributes are used to specify the signing key for the assembly,
// if desired. See the Mono documentation for more information about signing.
//[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("""")]
{2}";
return Guid.NewGuid().ToString().ToUpper();
}
}
}

View file

@ -1,17 +1,20 @@
using System;
using GodotTools.Core;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using DotNet.Globbing;
using System.Xml;
using System.Xml.Linq;
using JetBrains.Annotations;
using Microsoft.Build.Construction;
using Microsoft.Build.Globbing;
namespace GodotTools.ProjectEditor
{
public sealed class MSBuildProject
{
public ProjectRootElement Root { get; }
internal ProjectRootElement Root { get; set; }
public bool HasUnsavedChanges { get; set; }
@ -31,12 +34,20 @@ namespace GodotTools.ProjectEditor
return root != null ? new MSBuildProject(root) : null;
}
[PublicAPI]
public static void AddItemToProjectChecked(string projectPath, string itemType, string include)
{
var dir = Directory.GetParent(projectPath).FullName;
var root = ProjectRootElement.Open(projectPath);
Debug.Assert(root != null);
if (root.AreDefaultCompileItemsEnabled())
{
// No need to add. It's already included automatically by the MSBuild Sdk.
// This assumes the source file is inside the project directory and not manually excluded in the csproj
return;
}
var normalizedInclude = include.RelativeToPath(dir).Replace("/", "\\");
if (root.AddItemChecked(itemType, normalizedInclude))
@ -49,6 +60,13 @@ namespace GodotTools.ProjectEditor
var root = ProjectRootElement.Open(projectPath);
Debug.Assert(root != null);
if (root.AreDefaultCompileItemsEnabled())
{
// No need to add. It's already included automatically by the MSBuild Sdk.
// This assumes the source file is inside the project directory and not manually excluded in the csproj
return;
}
var normalizedOldInclude = oldInclude.NormalizePath();
var normalizedNewInclude = newInclude.NormalizePath();
@ -66,6 +84,13 @@ namespace GodotTools.ProjectEditor
var root = ProjectRootElement.Open(projectPath);
Debug.Assert(root != null);
if (root.AreDefaultCompileItemsEnabled())
{
// No need to add. It's already included automatically by the MSBuild Sdk.
// This assumes the source file is inside the project directory and not manually excluded in the csproj
return;
}
var normalizedInclude = include.NormalizePath();
if (root.RemoveItemChecked(itemType, normalizedInclude))
@ -78,6 +103,13 @@ namespace GodotTools.ProjectEditor
var root = ProjectRootElement.Open(projectPath);
Debug.Assert(root != null);
if (root.AreDefaultCompileItemsEnabled())
{
// No need to add. It's already included automatically by the MSBuild Sdk.
// This assumes the source file is inside the project directory and not manually excluded in the csproj
return;
}
bool dirty = false;
var oldFolderNormalized = oldFolder.NormalizePath();
@ -102,6 +134,13 @@ namespace GodotTools.ProjectEditor
var root = ProjectRootElement.Open(projectPath);
Debug.Assert(root != null);
if (root.AreDefaultCompileItemsEnabled())
{
// No need to add. It's already included automatically by the MSBuild Sdk.
// This assumes the source file is inside the project directory and not manually excluded in the csproj
return;
}
var folderNormalized = folder.NormalizePath();
var itemsToRemove = root.FindAllItemsInFolder(itemType, folderNormalized).ToList();
@ -133,12 +172,32 @@ namespace GodotTools.ProjectEditor
var result = new List<string>();
var existingFiles = GetAllFilesRecursive(Path.GetDirectoryName(projectPath), "*.cs");
var globOptions = new GlobOptions();
globOptions.Evaluation.CaseInsensitive = false;
var root = ProjectRootElement.Open(projectPath);
Debug.Assert(root != null);
if (root.AreDefaultCompileItemsEnabled())
{
var excluded = new List<string>();
result = GetAllFilesRecursive(Path.GetDirectoryName(projectPath), "*.cs").ToList();
foreach (var item in root.Items)
{
if (string.IsNullOrEmpty(item.Condition))
continue;
if (item.ItemType != itemType)
continue;
string normalizedExclude = item.Exclude.NormalizePath();
var glob = MSBuildGlob.Parse(normalizedExclude);
excluded.AddRange(result.Where(includedFile => glob.IsMatch(includedFile)));
}
result.RemoveAll(f => excluded.Contains(f));
}
foreach (var itemGroup in root.ItemGroups)
{
if (itemGroup.Condition.Length != 0)
@ -151,9 +210,7 @@ namespace GodotTools.ProjectEditor
string normalizedInclude = item.Include.NormalizePath();
var glob = Glob.Parse(normalizedInclude, globOptions);
// TODO Check somehow if path has no blob to avoid the following loop...
var glob = MSBuildGlob.Parse(normalizedInclude);
foreach (var existingFile in existingFiles)
{
@ -168,222 +225,186 @@ namespace GodotTools.ProjectEditor
return result.ToArray();
}
public static void EnsureHasProjectTypeGuids(MSBuildProject project)
public static void MigrateToProjectSdksStyle(MSBuildProject project, string projectName)
{
var root = project.Root;
bool found = root.PropertyGroups.Any(pg =>
string.IsNullOrEmpty(pg.Condition) && pg.Properties.Any(p => p.Name == "ProjectTypeGuids"));
if (found)
if (!string.IsNullOrEmpty(root.Sdk))
return;
root.AddProperty("ProjectTypeGuids", ProjectGenerator.GodotDefaultProjectTypeGuids);
root.Sdk = ProjectGenerator.GodotSdkAttrValue;
root.ToolsVersion = null;
root.DefaultTargets = null;
root.AddProperty("TargetFramework", "net472");
// Remove obsolete properties, items and elements. We're going to be conservative
// here to minimize the chances of introducing breaking changes. As such we will
// only remove elements that could potentially cause issues with the Godot.NET.Sdk.
void RemoveElements(IEnumerable<ProjectElement> elements)
{
foreach (var element in elements)
element.Parent.RemoveChild(element);
}
// Default Configuration
RemoveElements(root.PropertyGroups.SelectMany(g => g.Properties)
.Where(p => p.Name == "Configuration" && p.Condition.Trim() == "'$(Configuration)' == ''" && p.Value == "Debug"));
// Default Platform
RemoveElements(root.PropertyGroups.SelectMany(g => g.Properties)
.Where(p => p.Name == "Platform" && p.Condition.Trim() == "'$(Platform)' == ''" && p.Value == "AnyCPU"));
// Simple properties
var yabaiProperties = new[]
{
"OutputPath",
"BaseIntermediateOutputPath",
"IntermediateOutputPath",
"TargetFrameworkVersion",
"ProjectTypeGuids",
"ApiConfiguration"
};
RemoveElements(root.PropertyGroups.SelectMany(g => g.Properties)
.Where(p => yabaiProperties.Contains(p.Name)));
// Configuration dependent properties
var yabaiPropertiesForConfigs = new[]
{
"DebugSymbols",
"DebugType",
"Optimize",
"DefineConstants",
"ErrorReport",
"WarningLevel",
"ConsolePause"
};
foreach (var config in new[] {"ExportDebug", "ExportRelease", "Debug"})
{
var group = root.PropertyGroups
.First(g => g.Condition.Trim() == $"'$(Configuration)|$(Platform)' == '{config}|AnyCPU'");
RemoveElements(group.Properties.Where(p => yabaiPropertiesForConfigs.Contains(p.Name)));
if (group.Count == 0)
{
// No more children, safe to delete the group
group.Parent.RemoveChild(group);
}
}
// Godot API References
var apiAssemblies = new[] {ApiAssemblyNames.Core, ApiAssemblyNames.Editor};
RemoveElements(root.ItemGroups.SelectMany(g => g.Items)
.Where(i => i.ItemType == "Reference" && apiAssemblies.Contains(i.Include)));
// Microsoft.NETFramework.ReferenceAssemblies PackageReference
RemoveElements(root.ItemGroups.SelectMany(g => g.Items).Where(i =>
i.ItemType == "PackageReference" &&
i.Include.Equals("Microsoft.NETFramework.ReferenceAssemblies", StringComparison.OrdinalIgnoreCase)));
// Imports
var yabaiImports = new[]
{
"$(MSBuildBinPath)/Microsoft.CSharp.targets",
"$(MSBuildBinPath)Microsoft.CSharp.targets"
};
RemoveElements(root.Imports.Where(import => yabaiImports.Contains(
import.Project.Replace("\\", "/").Replace("//", "/"))));
// 'EnableDefaultCompileItems' and 'GenerateAssemblyInfo' are kept enabled by default
// on new projects, but when migrating old projects we disable them to avoid errors.
root.AddProperty("EnableDefaultCompileItems", "false");
root.AddProperty("GenerateAssemblyInfo", "false");
// Older AssemblyInfo.cs cause the following error:
// 'Properties/AssemblyInfo.cs(19,28): error CS8357:
// The specified version string contains wildcards, which are not compatible with determinism.
// Either remove wildcards from the version string, or disable determinism for this compilation.'
// We disable deterministic builds to prevent this. The user can then fix this manually when desired
// by fixing 'AssemblyVersion("1.0.*")' to not use wildcards.
root.AddProperty("Deterministic", "false");
project.HasUnsavedChanges = true;
var xDoc = XDocument.Parse(root.RawXml);
if (xDoc.Root == null)
return; // Too bad, we will have to keep the xmlns/namespace and xml declaration
XElement GetElement(XDocument doc, string name, string value, string parentName)
{
foreach (var node in doc.DescendantNodes())
{
if (!(node is XElement element))
continue;
if (element.Name.LocalName.Equals(name) && element.Value == value &&
element.Parent != null && element.Parent.Name.LocalName.Equals(parentName))
{
return element;
}
}
return null;
}
// Add comment about Microsoft.NET.Sdk properties disabled during migration
GetElement(xDoc, name: "EnableDefaultCompileItems", value: "false", parentName: "PropertyGroup")
.AddBeforeSelf(new XComment("The following properties were overriden during migration to prevent errors.\n" +
" Enabling them may require other manual changes to the project and its files."));
void RemoveNamespace(XElement element)
{
element.Attributes().Where(x => x.IsNamespaceDeclaration).Remove();
element.Name = element.Name.LocalName;
foreach (var node in element.DescendantNodes())
{
if (node is XElement xElement)
{
// Need to do the same for all children recursively as it adds it to them for some reason...
RemoveNamespace(xElement);
}
}
}
// Remove xmlns/namespace
RemoveNamespace(xDoc.Root);
// Remove xml declaration
xDoc.Nodes().FirstOrDefault(node => node.NodeType == XmlNodeType.XmlDeclaration)?.Remove();
string projectFullPath = root.FullPath;
root = ProjectRootElement.Create(xDoc.CreateReader());
root.FullPath = projectFullPath;
project.Root = root;
}
/// Simple function to make sure the Api assembly references are configured correctly
public static void FixApiHintPath(MSBuildProject project)
public static void EnsureGodotSdkIsUpToDate(MSBuildProject project)
{
var root = project.Root;
string godotSdkAttrValue = ProjectGenerator.GodotSdkAttrValue;
void AddPropertyIfNotPresent(string name, string condition, string value)
{
if (root.PropertyGroups
.Any(g => (string.IsNullOrEmpty(g.Condition) || g.Condition.Trim() == condition) &&
g.Properties
.Any(p => p.Name == name &&
p.Value == value &&
(p.Condition.Trim() == condition || g.Condition.Trim() == condition))))
{
return;
}
root.AddProperty(name, value).Condition = " " + condition + " ";
project.HasUnsavedChanges = true;
}
AddPropertyIfNotPresent(name: "ApiConfiguration",
condition: "'$(Configuration)' != 'ExportRelease'",
value: "Debug");
AddPropertyIfNotPresent(name: "ApiConfiguration",
condition: "'$(Configuration)' == 'ExportRelease'",
value: "Release");
void SetReferenceHintPath(string referenceName, string condition, string hintPath)
{
foreach (var itemGroup in root.ItemGroups.Where(g =>
g.Condition.Trim() == string.Empty || g.Condition.Trim() == condition))
{
var references = itemGroup.Items.Where(item =>
item.ItemType == "Reference" &&
item.Include == referenceName &&
(item.Condition.Trim() == condition || itemGroup.Condition.Trim() == condition));
var referencesWithHintPath = references.Where(reference =>
reference.Metadata.Any(m => m.Name == "HintPath"));
if (referencesWithHintPath.Any(reference => reference.Metadata
.Any(m => m.Name == "HintPath" && m.Value == hintPath)))
{
// Found a Reference item with the right HintPath
return;
}
var referenceWithHintPath = referencesWithHintPath.FirstOrDefault();
if (referenceWithHintPath != null)
{
// Found a Reference item with a wrong HintPath
foreach (var metadata in referenceWithHintPath.Metadata.ToList()
.Where(m => m.Name == "HintPath"))
{
// Safe to remove as we duplicate with ToList() to loop
referenceWithHintPath.RemoveChild(metadata);
}
referenceWithHintPath.AddMetadata("HintPath", hintPath);
project.HasUnsavedChanges = true;
return;
}
var referenceWithoutHintPath = references.FirstOrDefault();
if (referenceWithoutHintPath != null)
{
// Found a Reference item without a HintPath
referenceWithoutHintPath.AddMetadata("HintPath", hintPath);
project.HasUnsavedChanges = true;
return;
}
}
// Found no Reference item at all. Add it.
root.AddItem("Reference", referenceName).Condition = " " + condition + " ";
project.HasUnsavedChanges = true;
}
const string coreProjectName = "GodotSharp";
const string editorProjectName = "GodotSharpEditor";
const string coreCondition = "";
const string editorCondition = "'$(Configuration)' == 'Debug'";
var coreHintPath = $"$(ProjectDir)/.mono/assemblies/$(ApiConfiguration)/{coreProjectName}.dll";
var editorHintPath = $"$(ProjectDir)/.mono/assemblies/$(ApiConfiguration)/{editorProjectName}.dll";
SetReferenceHintPath(coreProjectName, coreCondition, coreHintPath);
SetReferenceHintPath(editorProjectName, editorCondition, editorHintPath);
}
public static void MigrateFromOldConfigNames(MSBuildProject project)
{
var root = project.Root;
bool hasGodotProjectGeneratorVersion = false;
bool foundOldConfiguration = false;
foreach (var propertyGroup in root.PropertyGroups.Where(g => string.IsNullOrEmpty(g.Condition)))
{
if (!hasGodotProjectGeneratorVersion && propertyGroup.Properties.Any(p => p.Name == "GodotProjectGeneratorVersion"))
hasGodotProjectGeneratorVersion = true;
foreach (var configItem in propertyGroup.Properties
.Where(p => p.Condition.Trim() == "'$(Configuration)' == ''" && p.Value == "Tools"))
{
configItem.Value = "Debug";
foundOldConfiguration = true;
project.HasUnsavedChanges = true;
}
}
if (!hasGodotProjectGeneratorVersion)
{
root.PropertyGroups.First(g => string.IsNullOrEmpty(g.Condition))?
.AddProperty("GodotProjectGeneratorVersion", Assembly.GetExecutingAssembly().GetName().Version.ToString());
project.HasUnsavedChanges = true;
}
if (!foundOldConfiguration)
{
var toolsConditions = new[]
{
"'$(Configuration)|$(Platform)' == 'Tools|AnyCPU'",
"'$(Configuration)|$(Platform)' != 'Tools|AnyCPU'",
"'$(Configuration)' == 'Tools'",
"'$(Configuration)' != 'Tools'"
};
foundOldConfiguration = root.PropertyGroups
.Any(g => toolsConditions.Any(c => c == g.Condition.Trim()));
}
if (foundOldConfiguration)
{
void MigrateConfigurationConditions(string oldConfiguration, string newConfiguration)
{
void MigrateConditions(string oldCondition, string newCondition)
{
foreach (var propertyGroup in root.PropertyGroups.Where(g => g.Condition.Trim() == oldCondition))
{
propertyGroup.Condition = " " + newCondition + " ";
project.HasUnsavedChanges = true;
}
foreach (var propertyGroup in root.PropertyGroups)
{
foreach (var prop in propertyGroup.Properties.Where(p => p.Condition.Trim() == oldCondition))
{
prop.Condition = " " + newCondition + " ";
project.HasUnsavedChanges = true;
}
}
foreach (var itemGroup in root.ItemGroups.Where(g => g.Condition.Trim() == oldCondition))
{
itemGroup.Condition = " " + newCondition + " ";
project.HasUnsavedChanges = true;
}
foreach (var itemGroup in root.ItemGroups)
{
foreach (var item in itemGroup.Items.Where(item => item.Condition.Trim() == oldCondition))
{
item.Condition = " " + newCondition + " ";
project.HasUnsavedChanges = true;
}
}
}
foreach (var op in new[] {"==", "!="})
{
MigrateConditions($"'$(Configuration)|$(Platform)' {op} '{oldConfiguration}|AnyCPU'", $"'$(Configuration)|$(Platform)' {op} '{newConfiguration}|AnyCPU'");
MigrateConditions($"'$(Configuration)' {op} '{oldConfiguration}'", $"'$(Configuration)' {op} '{newConfiguration}'");
}
}
MigrateConfigurationConditions("Debug", "ExportDebug");
MigrateConfigurationConditions("Release", "ExportRelease");
MigrateConfigurationConditions("Tools", "Debug"); // Must be last
}
}
public static void EnsureHasNugetNetFrameworkRefAssemblies(MSBuildProject project)
{
var root = project.Root;
bool found = root.ItemGroups.Any(g => string.IsNullOrEmpty(g.Condition) && g.Items.Any(
item => item.ItemType == "PackageReference" && item.Include == "Microsoft.NETFramework.ReferenceAssemblies"));
if (found)
if (!string.IsNullOrEmpty(root.Sdk) && root.Sdk.Trim().Equals(godotSdkAttrValue, StringComparison.OrdinalIgnoreCase))
return;
var frameworkRefAssembliesItem = root.AddItem("PackageReference", "Microsoft.NETFramework.ReferenceAssemblies");
// Use metadata (child nodes) instead of attributes for the PackageReference.
// This is for compatibility with 3.2, where GodotTools uses an old Microsoft.Build.
frameworkRefAssembliesItem.AddMetadata("Version", "1.0.0");
frameworkRefAssembliesItem.AddMetadata("PrivateAssets", "All");
root.Sdk = godotSdkAttrValue;
project.HasUnsavedChanges = true;
}
}

View file

@ -24,48 +24,50 @@ namespace GodotTools
private ToolButton errorsBtn;
private Button viewLogBtn;
private void _UpdateBuildTab(int index, int? currentTab)
{
var tab = (BuildTab)buildTabs.GetChild(index);
string itemName = Path.GetFileNameWithoutExtension(tab.BuildInfo.Solution);
itemName += " [" + tab.BuildInfo.Configuration + "]";
buildTabsList.AddItem(itemName, tab.IconTexture);
string itemTooltip = "Solution: " + tab.BuildInfo.Solution;
itemTooltip += "\nConfiguration: " + tab.BuildInfo.Configuration;
itemTooltip += "\nStatus: ";
if (tab.BuildExited)
itemTooltip += tab.BuildResult == BuildTab.BuildResults.Success ? "Succeeded" : "Errored";
else
itemTooltip += "Running";
if (!tab.BuildExited || tab.BuildResult == BuildTab.BuildResults.Error)
itemTooltip += $"\nErrors: {tab.ErrorCount}";
itemTooltip += $"\nWarnings: {tab.WarningCount}";
buildTabsList.SetItemTooltip(index, itemTooltip);
// If this tab was already selected before the changes or if no tab was selected
if (currentTab == null || currentTab == index)
{
buildTabsList.Select(index);
_BuildTabsItemSelected(index);
}
}
private void _UpdateBuildTabsList()
{
buildTabsList.Clear();
int currentTab = buildTabs.CurrentTab;
int? currentTab = buildTabs.CurrentTab;
bool noCurrentTab = currentTab < 0 || currentTab >= buildTabs.GetTabCount();
if (currentTab < 0 || currentTab >= buildTabs.GetTabCount())
currentTab = null;
for (int i = 0; i < buildTabs.GetChildCount(); i++)
{
var tab = (BuildTab)buildTabs.GetChild(i);
if (tab == null)
continue;
string itemName = Path.GetFileNameWithoutExtension(tab.BuildInfo.Solution);
itemName += " [" + tab.BuildInfo.Configuration + "]";
buildTabsList.AddItem(itemName, tab.IconTexture);
string itemTooltip = "Solution: " + tab.BuildInfo.Solution;
itemTooltip += "\nConfiguration: " + tab.BuildInfo.Configuration;
itemTooltip += "\nStatus: ";
if (tab.BuildExited)
itemTooltip += tab.BuildResult == BuildTab.BuildResults.Success ? "Succeeded" : "Errored";
else
itemTooltip += "Running";
if (!tab.BuildExited || tab.BuildResult == BuildTab.BuildResults.Error)
itemTooltip += $"\nErrors: {tab.ErrorCount}";
itemTooltip += $"\nWarnings: {tab.WarningCount}";
buildTabsList.SetItemTooltip(i, itemTooltip);
if (noCurrentTab || currentTab == i)
{
buildTabsList.Select(i);
_BuildTabsItemSelected(i);
}
}
_UpdateBuildTab(i, currentTab);
}
public BuildTab GetBuildTabFor(BuildInfo buildInfo)
@ -160,13 +162,7 @@ namespace GodotTools
}
}
var godotDefines = new[]
{
OS.GetName(),
Internal.GodotIs32Bits() ? "32" : "64"
};
bool buildSuccess = BuildManager.BuildProjectBlocking("Debug", godotDefines);
bool buildSuccess = BuildManager.BuildProjectBlocking("Debug");
if (!buildSuccess)
return;
@ -272,7 +268,7 @@ namespace GodotTools
};
panelTabs.AddChild(panelBuildsTab);
var toolBarHBox = new HBoxContainer { SizeFlagsHorizontal = (int)SizeFlags.ExpandFill };
var toolBarHBox = new HBoxContainer {SizeFlagsHorizontal = (int)SizeFlags.ExpandFill};
panelBuildsTab.AddChild(toolBarHBox);
var buildProjectBtn = new Button

View file

@ -36,15 +36,13 @@ namespace GodotTools.Build
}
case BuildTool.MsBuildVs:
{
if (_msbuildToolsPath.Empty() || !File.Exists(_msbuildToolsPath))
if (string.IsNullOrEmpty(_msbuildToolsPath) || !File.Exists(_msbuildToolsPath))
{
// Try to search it again if it wasn't found last time or if it was removed from its location
_msbuildToolsPath = FindMsBuildToolsPathOnWindows();
if (_msbuildToolsPath.Empty())
{
if (string.IsNullOrEmpty(_msbuildToolsPath))
throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMSBuildVs}'.");
}
}
if (!_msbuildToolsPath.EndsWith("\\"))
@ -57,15 +55,14 @@ namespace GodotTools.Build
string msbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "msbuild.bat");
if (!File.Exists(msbuildPath))
{
throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMSBuildMono}'. Tried with path: {msbuildPath}");
}
return (msbuildPath, BuildTool.MsBuildMono);
}
case BuildTool.JetBrainsMsBuild:
{
var editorPath = (string)editorSettings.GetSetting(RiderPathManager.EditorPathSettingName);
if (!File.Exists(editorPath))
throw new FileNotFoundException($"Cannot find Rider executable. Tried with path: {editorPath}");
@ -83,7 +80,7 @@ namespace GodotTools.Build
}
}
if (OS.IsUnixLike())
if (OS.IsUnixLike)
{
switch (buildTool)
{
@ -138,12 +135,12 @@ namespace GodotTools.Build
{
string ret = OS.PathWhich(name);
if (!ret.Empty())
if (!string.IsNullOrEmpty(ret))
return ret;
string retFallback = OS.PathWhich($"{name}.exe");
if (!retFallback.Empty())
if (!string.IsNullOrEmpty(retFallback))
return retFallback;
foreach (string hintDir in MsBuildHintDirs)
@ -195,7 +192,7 @@ namespace GodotTools.Build
string value = line.Substring(sepIdx + 1).StripEdges();
if (value.Empty())
if (string.IsNullOrEmpty(value))
throw new FormatException("installationPath value is empty");
if (!value.EndsWith("\\"))

View file

@ -6,6 +6,7 @@ using GodotTools.Build;
using GodotTools.Ides.Rider;
using GodotTools.Internals;
using GodotTools.Utils;
using JetBrains.Annotations;
using static GodotTools.Internals.Globals;
using File = GodotTools.Utils.File;
@ -152,7 +153,7 @@ namespace GodotTools
}
}
public static bool BuildProjectBlocking(string config, IEnumerable<string> godotDefines)
public static bool BuildProjectBlocking(string config, [CanBeNull] string platform = null)
{
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
return true; // No solution to build
@ -168,29 +169,18 @@ namespace GodotTools
return false;
}
var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
var buildTool = (BuildTool)editorSettings.GetSetting("mono/builds/build_tool");
using (var pr = new EditorProgress("mono_project_debug_build", "Building project solution...", 1))
{
pr.Step("Building project solution", 0);
var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, new[] {"Build"}, config, restore: true);
bool escapeNeedsDoubleBackslash = buildTool == BuildTool.MsBuildMono || buildTool == BuildTool.DotnetCli;
// Add Godot defines
string constants = !escapeNeedsDoubleBackslash ? "GodotDefineConstants=\"" : "GodotDefineConstants=\\\"";
foreach (var godotDefine in godotDefines)
constants += $"GODOT_{godotDefine.ToUpper().Replace("-", "_").Replace(" ", "_").Replace(";", "_")};";
// If a platform was not specified, try determining the current one. If that fails, let MSBuild auto-detect it.
if (platform != null || OS.PlatformNameMap.TryGetValue(Godot.OS.GetName(), out platform))
buildInfo.CustomProperties.Add($"GodotTargetPlatform={platform}");
if (Internal.GodotIsRealTDouble())
constants += "GODOT_REAL_T_IS_DOUBLE;";
constants += !escapeNeedsDoubleBackslash ? "\"" : "\\\"";
buildInfo.CustomProperties.Add(constants);
buildInfo.CustomProperties.Add("GodotRealTIsDouble=true");
if (!Build(buildInfo))
{
@ -233,13 +223,7 @@ namespace GodotTools
return true; // Requested play from an external editor/IDE which already built the project
}
var godotDefines = new[]
{
Godot.OS.GetName(),
Internal.GodotIs32Bits() ? "32" : "64"
};
return BuildProjectBlocking("Debug", godotDefines);
return BuildProjectBlocking("Debug");
}
public static void Initialize()

View file

@ -72,7 +72,7 @@ namespace GodotTools
{
string[] csvColumns = file.GetCsvLine();
if (csvColumns.Length == 1 && csvColumns[0].Empty())
if (csvColumns.Length == 1 && string.IsNullOrEmpty(csvColumns[0]))
return;
if (csvColumns.Length != 7)
@ -115,12 +115,12 @@ namespace GodotTools
// Get correct issue idx from issue list
int issueIndex = (int)issuesList.GetItemMetadata(idx);
if (idx < 0 || idx >= issues.Count)
if (issueIndex < 0 || issueIndex >= issues.Count)
throw new IndexOutOfRangeException("Issue index out of range");
BuildIssue issue = issues[issueIndex];
if (issue.ProjectFile.Empty() && issue.File.Empty())
if (string.IsNullOrEmpty(issue.ProjectFile) && string.IsNullOrEmpty(issue.File))
return;
string projectDir = issue.ProjectFile.Length > 0 ? issue.ProjectFile.GetBaseDir() : BuildInfo.Solution.GetBaseDir();
@ -158,14 +158,14 @@ namespace GodotTools
string tooltip = string.Empty;
tooltip += $"Message: {issue.Message}";
if (!issue.Code.Empty())
if (!string.IsNullOrEmpty(issue.Code))
tooltip += $"\nCode: {issue.Code}";
tooltip += $"\nType: {(issue.Warning ? "warning" : "error")}";
string text = string.Empty;
if (!issue.File.Empty())
if (!string.IsNullOrEmpty(issue.File))
{
text += $"{issue.File}({issue.Line},{issue.Column}): ";
@ -174,7 +174,7 @@ namespace GodotTools
tooltip += $"\nColumn: {issue.Column}";
}
if (!issue.ProjectFile.Empty())
if (!string.IsNullOrEmpty(issue.ProjectFile))
tooltip += $"\nProject: {issue.ProjectFile}";
text += issue.Message;

View file

@ -1,9 +1,9 @@
using Godot;
using System;
using System.Linq;
using Godot.Collections;
using GodotTools.Internals;
using GodotTools.ProjectEditor;
using static GodotTools.Internals.Globals;
using File = GodotTools.Utils.File;
using Directory = GodotTools.Utils.Directory;
@ -15,7 +15,7 @@ namespace GodotTools
{
try
{
return ProjectGenerator.GenGameProject(dir, name, compileItems: new string[] { });
return ProjectGenerator.GenAndSaveGameProject(dir, name);
}
catch (Exception e)
{
@ -24,14 +24,6 @@ namespace GodotTools
}
}
public static void AddItem(string projectPath, string itemType, string include)
{
if (!(bool)GlobalDef("mono/project/auto_update_project", true))
return;
ProjectUtils.AddItemToProjectChecked(projectPath, itemType, include);
}
private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private static ulong ConvertToTimestamp(this DateTime value)
@ -40,81 +32,77 @@ namespace GodotTools
return (ulong)elapsedTime.TotalSeconds;
}
private static bool TryParseFileMetadata(string includeFile, ulong modifiedTime, out Dictionary fileMetadata)
{
fileMetadata = null;
var parseError = ScriptClassParser.ParseFile(includeFile, out var classes, out string errorStr);
if (parseError != Error.Ok)
{
GD.PushError($"Failed to determine namespace and class for script: {includeFile}. Parse error: {errorStr ?? parseError.ToString()}");
return false;
}
string searchName = System.IO.Path.GetFileNameWithoutExtension(includeFile);
var firstMatch = classes.FirstOrDefault(classDecl =>
classDecl.BaseCount != 0 && // If it doesn't inherit anything, it can't be a Godot.Object.
classDecl.SearchName != searchName // Filter by the name we're looking for
);
if (firstMatch == null)
return false; // Not found
fileMetadata = new Dictionary
{
["modified_time"] = $"{modifiedTime}",
["class"] = new Dictionary
{
["namespace"] = firstMatch.Namespace,
["class_name"] = firstMatch.Name,
["nested"] = firstMatch.Nested
}
};
return true;
}
public static void GenerateScriptsMetadata(string projectPath, string outputPath)
{
if (File.Exists(outputPath))
File.Delete(outputPath);
var metadataDict = Internal.GetScriptsMetadataOrNothing().Duplicate();
var oldDict = Internal.GetScriptsMetadataOrNothing();
var newDict = new Godot.Collections.Dictionary<string, object>();
foreach (var includeFile in ProjectUtils.GetIncludeFiles(projectPath, "Compile"))
bool IsUpToDate(string includeFile, ulong modifiedTime)
{
string projectIncludeFile = ("res://" + includeFile).SimplifyGodotPath();
ulong modifiedTime = File.GetLastWriteTime(projectIncludeFile).ConvertToTimestamp();
if (oldDict.TryGetValue(projectIncludeFile, out var oldFileVar))
{
var oldFileDict = (Dictionary)oldFileVar;
if (ulong.TryParse(oldFileDict["modified_time"] as string, out ulong storedModifiedTime))
{
if (storedModifiedTime == modifiedTime)
{
// No changes so no need to parse again
newDict[projectIncludeFile] = oldFileDict;
continue;
}
}
}
Error parseError = ScriptClassParser.ParseFile(projectIncludeFile, out var classes, out string errorStr);
if (parseError != Error.Ok)
{
GD.PushError($"Failed to determine namespace and class for script: {projectIncludeFile}. Parse error: {errorStr ?? parseError.ToString()}");
continue;
}
string searchName = System.IO.Path.GetFileNameWithoutExtension(projectIncludeFile);
var classDict = new Dictionary();
foreach (var classDecl in classes)
{
if (classDecl.BaseCount == 0)
continue; // Does not inherit nor implement anything, so it can't be a script class
string classCmp = classDecl.Nested ?
classDecl.Name.Substring(classDecl.Name.LastIndexOf(".", StringComparison.Ordinal) + 1) :
classDecl.Name;
if (classCmp != searchName)
continue;
classDict["namespace"] = classDecl.Namespace;
classDict["class_name"] = classDecl.Name;
classDict["nested"] = classDecl.Nested;
break;
}
if (classDict.Count == 0)
continue; // Not found
newDict[projectIncludeFile] = new Dictionary { ["modified_time"] = $"{modifiedTime}", ["class"] = classDict };
return metadataDict.TryGetValue(includeFile, out var oldFileVar) &&
ulong.TryParse(((Dictionary)oldFileVar)["modified_time"] as string,
out ulong storedModifiedTime) && storedModifiedTime == modifiedTime;
}
if (newDict.Count > 0)
var outdatedFiles = ProjectUtils.GetIncludeFiles(projectPath, "Compile")
.Select(path => ("res://" + path).SimplifyGodotPath())
.ToDictionary(path => path, path => File.GetLastWriteTime(path).ConvertToTimestamp())
.Where(pair => !IsUpToDate(includeFile: pair.Key, modifiedTime: pair.Value))
.ToArray();
foreach (var pair in outdatedFiles)
{
string json = JSON.Print(newDict);
metadataDict.Remove(pair.Key);
string baseDir = outputPath.GetBaseDir();
string includeFile = pair.Key;
if (!Directory.Exists(baseDir))
Directory.CreateDirectory(baseDir);
File.WriteAllText(outputPath, json);
if (TryParseFileMetadata(includeFile, modifiedTime: pair.Value, out var fileMetadata))
metadataDict[includeFile] = fileMetadata;
}
string json = metadataDict.Count <= 0 ? "{}" : JSON.Print(metadataDict);
string baseDir = outputPath.GetBaseDir();
if (!Directory.Exists(baseDir))
Directory.CreateDirectory(baseDir);
File.WriteAllText(outputPath, json);
}
}
}

View file

@ -7,6 +7,7 @@ using System.Linq;
using System.Runtime.CompilerServices;
using GodotTools.Core;
using GodotTools.Internals;
using JetBrains.Annotations;
using static GodotTools.Internals.Globals;
using Directory = GodotTools.Utils.Directory;
using File = GodotTools.Utils.File;
@ -145,9 +146,7 @@ namespace GodotTools.Export
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
return;
string platform = DeterminePlatformFromFeatures(features);
if (platform == null)
if (!DeterminePlatformFromFeatures(features, out string platform))
throw new NotSupportedException("Target platform not supported");
string outputDir = new FileInfo(path).Directory?.FullName ??
@ -160,10 +159,7 @@ namespace GodotTools.Export
AddFile(scriptsMetadataPath, scriptsMetadataPath);
// Turn export features into defines
var godotDefines = features;
if (!BuildManager.BuildProjectBlocking(buildConfig, godotDefines))
if (!BuildManager.BuildProjectBlocking(buildConfig, platform))
throw new Exception("Failed to build project");
// Add dependency assemblies
@ -289,6 +285,7 @@ namespace GodotTools.Export
}
}
[NotNull]
private static string ExportDataDirectory(string[] features, string platform, bool isDebug, string outputDir)
{
string target = isDebug ? "release_debug" : "release";
@ -343,18 +340,19 @@ namespace GodotTools.Export
private static bool PlatformHasTemplateDir(string platform)
{
// OSX export templates are contained in a zip, so we place our custom template inside it and let Godot do the rest.
return !new[] { OS.Platforms.OSX, OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5 }.Contains(platform);
return !new[] {OS.Platforms.OSX, OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5}.Contains(platform);
}
private static string DeterminePlatformFromFeatures(IEnumerable<string> features)
private static bool DeterminePlatformFromFeatures(IEnumerable<string> features, out string platform)
{
foreach (var feature in features)
{
if (OS.PlatformNameMap.TryGetValue(feature, out string platform))
return platform;
if (OS.PlatformNameMap.TryGetValue(feature, out platform))
return true;
}
return null;
platform = null;
return false;
}
private static string GetBclProfileDir(string profile)
@ -391,7 +389,7 @@ namespace GodotTools.Export
/// </summary>
private static bool PlatformRequiresCustomBcl(string platform)
{
if (new[] { OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5 }.Contains(platform))
if (new[] {OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5}.Contains(platform))
return true;
// The 'net_4_x' BCL is not compatible between Windows and the other platforms.

View file

@ -302,7 +302,7 @@ namespace GodotTools
case ExternalEditorId.VsCode:
{
if (_vsCodePath.Empty() || !File.Exists(_vsCodePath))
if (string.IsNullOrEmpty(_vsCodePath) || !File.Exists(_vsCodePath))
{
// Try to search it again if it wasn't found last time or if it was removed from its location
_vsCodePath = VsCodeNames.SelectFirstNotNull(OS.PathWhich, orElse: string.Empty);
@ -354,7 +354,7 @@ namespace GodotTools
if (OS.IsOSX)
{
if (!osxAppBundleInstalled && _vsCodePath.Empty())
if (!osxAppBundleInstalled && string.IsNullOrEmpty(_vsCodePath))
{
GD.PushError("Cannot find code editor: VSCode");
return Error.FileNotFound;
@ -364,7 +364,7 @@ namespace GodotTools
}
else
{
if (_vsCodePath.Empty())
if (string.IsNullOrEmpty(_vsCodePath))
{
GD.PushError("Cannot find code editor: VSCode");
return Error.FileNotFound;
@ -403,6 +403,37 @@ namespace GodotTools
return BuildManager.EditorBuildCallback();
}
private void ApplyNecessaryChangesToSolution()
{
try
{
// Migrate solution from old configuration names to: Debug, ExportDebug and ExportRelease
DotNetSolution.MigrateFromOldConfigNames(GodotSharpDirs.ProjectSlnPath);
var msbuildProject = ProjectUtils.Open(GodotSharpDirs.ProjectCsProjPath)
?? throw new Exception("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, ProjectAssemblyName);
ProjectUtils.EnsureGodotSdkIsUpToDate(msbuildProject);
if (msbuildProject.HasUnsavedChanges)
{
// Save a copy of the project before replacing it
FileUtils.SaveBackupCopy(GodotSharpDirs.ProjectCsProjPath);
msbuildProject.Save();
}
}
catch (Exception e)
{
GD.PushError(e.ToString());
}
}
public override void EnablePlugin()
{
base.EnablePlugin();
@ -478,42 +509,7 @@ namespace GodotTools
if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath))
{
try
{
// Migrate solution from old configuration names to: Debug, ExportDebug and ExportRelease
DotNetSolution.MigrateFromOldConfigNames(GodotSharpDirs.ProjectSlnPath);
var msbuildProject = ProjectUtils.Open(GodotSharpDirs.ProjectCsProjPath)
?? throw new Exception("Cannot open C# project");
// NOTE: The order in which changes are made to the project is important
// Migrate csproj from old configuration names to: Debug, ExportDebug and ExportRelease
ProjectUtils.MigrateFromOldConfigNames(msbuildProject);
// Apply the other fixes only after configurations have been migrated
// Make sure the existing project has the ProjectTypeGuids property (for VisualStudio)
ProjectUtils.EnsureHasProjectTypeGuids(msbuildProject);
// Make sure the existing project has Api assembly references configured correctly
ProjectUtils.FixApiHintPath(msbuildProject);
// Make sure the existing project references the Microsoft.NETFramework.ReferenceAssemblies nuget package
ProjectUtils.EnsureHasNugetNetFrameworkRefAssemblies(msbuildProject);
if (msbuildProject.HasUnsavedChanges)
{
// Save a copy of the project before replacing it
FileUtils.SaveBackupCopy(GodotSharpDirs.ProjectCsProjPath);
msbuildProject.Save();
}
}
catch (Exception e)
{
GD.PushError(e.ToString());
}
ApplyNecessaryChangesToSolution();
}
else
{
@ -551,7 +547,7 @@ namespace GodotTools
$",Visual Studio Code:{(int)ExternalEditorId.VsCode}" +
$",JetBrains Rider:{(int)ExternalEditorId.Rider}";
}
else if (OS.IsUnixLike())
else if (OS.IsUnixLike)
{
settingsHintStr += $",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" +
$",Visual Studio Code:{(int)ExternalEditorId.VsCode}" +

View file

@ -1,12 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ProjectGuid>{27B00618-A6F2-4828-B922-05CAEB08C286}</ProjectGuid>
<OutputType>Library</OutputType>
<TargetFramework>net472</TargetFramework>
<GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath>
<DataDirToolsOutputPath>$(GodotSourceRootPath)/bin/GodotSharp/Tools</DataDirToolsOutputPath>
<GodotApiConfiguration>Debug</GodotApiConfiguration>
<LangVersion>7</LangVersion>
<LangVersion>7.2</LangVersion>
<GodotApiConfiguration>Debug</GodotApiConfiguration> <!-- The Godot editor uses the Debug Godot API assemblies -->
<GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath>
<GodotOutputDataDir>$(GodotSourceRootPath)/bin/GodotSharp</GodotOutputDataDir>

View file

@ -128,7 +128,7 @@ namespace GodotTools.Ides.MonoDevelop
{EditorId.MonoDevelop, "MonoDevelop.exe"}
};
}
else if (OS.IsUnixLike())
else if (OS.IsUnixLike)
{
ExecutableNames = new Dictionary<EditorId, string>
{

View file

@ -36,7 +36,7 @@ namespace GodotTools.Ides.Rider
{
return CollectRiderInfosMac();
}
if (OS.IsUnixLike())
if (OS.IsUnixLike)
{
return CollectAllRiderPathsLinux();
}
@ -147,7 +147,7 @@ namespace GodotTools.Ides.Rider
return GetToolboxRiderRootPath(localAppData);
}
if (OS.IsUnixLike())
if (OS.IsUnixLike)
{
var home = Environment.GetEnvironmentVariable("HOME");
if (string.IsNullOrEmpty(home))
@ -209,7 +209,7 @@ namespace GodotTools.Ides.Rider
private static string GetRelativePathToBuildTxt()
{
if (OS.IsWindows || OS.IsUnixLike())
if (OS.IsWindows || OS.IsUnixLike)
return "../../build.txt";
if (OS.IsOSX)
return "Contents/Resources/build.txt";

View file

@ -15,6 +15,10 @@ namespace GodotTools.Internals
public bool Nested { get; }
public int BaseCount { get; }
public string SearchName => Nested ?
Name.Substring(Name.LastIndexOf(".", StringComparison.Ordinal) + 1) :
Name;
public ClassDecl(string name, string @namespace, bool nested, int baseCount)
{
Name = name;

View file

@ -62,6 +62,11 @@ namespace GodotTools.Utils
return name.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
private static bool IsAnyOS(IEnumerable<string> names)
{
return names.Any(p => p.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase));
}
private static readonly Lazy<bool> _isWindows = new Lazy<bool>(() => IsOS(Names.Windows));
private static readonly Lazy<bool> _isOSX = new Lazy<bool>(() => IsOS(Names.OSX));
private static readonly Lazy<bool> _isX11 = new Lazy<bool>(() => IsOS(Names.X11));
@ -71,6 +76,7 @@ namespace GodotTools.Utils
private static readonly Lazy<bool> _isAndroid = new Lazy<bool>(() => IsOS(Names.Android));
private static readonly Lazy<bool> _isiOS = new Lazy<bool>(() => IsOS(Names.iOS));
private static readonly Lazy<bool> _isHTML5 = new Lazy<bool>(() => IsOS(Names.HTML5));
private static readonly Lazy<bool> _isUnixLike = new Lazy<bool>(() => IsAnyOS(UnixLikePlatforms));
public static bool IsWindows => _isWindows.Value || IsUWP;
public static bool IsOSX => _isOSX.Value;
@ -82,18 +88,9 @@ namespace GodotTools.Utils
public static bool IsiOS => _isiOS.Value;
public static bool IsHTML5 => _isHTML5.Value;
private static bool? _isUnixCache;
private static readonly string[] UnixLikePlatforms = { Names.OSX, Names.X11, Names.Server, Names.Haiku, Names.Android, Names.iOS };
private static readonly string[] UnixLikePlatforms = {Names.OSX, Names.X11, Names.Server, Names.Haiku, Names.Android, Names.iOS};
public static bool IsUnixLike()
{
if (_isUnixCache.HasValue)
return _isUnixCache.Value;
string osName = GetPlatformName();
_isUnixCache = UnixLikePlatforms.Any(p => p.Equals(osName, StringComparison.OrdinalIgnoreCase));
return _isUnixCache.Value;
}
public static bool IsUnixLike => _isUnixLike.Value;
public static char PathSep => IsWindows ? ';' : ':';
@ -121,10 +118,10 @@ namespace GodotTools.Utils
return searchDirs.Select(dir => Path.Combine(dir, name)).FirstOrDefault(File.Exists);
return (from dir in searchDirs
select Path.Combine(dir, name)
select Path.Combine(dir, name)
into path
from ext in windowsExts
select path + ext).FirstOrDefault(File.Exists);
from ext in windowsExts
select path + ext).FirstOrDefault(File.Exists);
}
private static string PathWhichUnix([NotNull] string name)
@ -189,7 +186,7 @@ namespace GodotTools.Utils
startInfo.UseShellExecute = false;
using (var process = new Process { StartInfo = startInfo })
using (var process = new Process {StartInfo = startInfo})
{
process.Start();
process.WaitForExit();

View file

@ -45,7 +45,6 @@
#include "../mono_gd/gd_mono_marshal.h"
#include "../utils/path_utils.h"
#include "../utils/string_utils.h"
#include "csharp_project.h"
#define CS_INDENT " " // 4 whitespaces

View file

@ -32,69 +32,71 @@
#include <mono/metadata/image.h>
#include "core/io/file_access_pack.h"
#include "core/os/os.h"
#include "core/project_settings.h"
#include "../mono_gd/gd_mono.h"
#include "../mono_gd/gd_mono_assembly.h"
#include "../mono_gd/gd_mono_cache.h"
#include "../utils/macros.h"
namespace GodotSharpExport {
String get_assemblyref_name(MonoImage *p_image, int index) {
struct AssemblyRefInfo {
String name;
uint16_t major;
uint16_t minor;
uint16_t build;
uint16_t revision;
};
AssemblyRefInfo get_assemblyref_name(MonoImage *p_image, int index) {
const MonoTableInfo *table_info = mono_image_get_table_info(p_image, MONO_TABLE_ASSEMBLYREF);
uint32_t cols[MONO_ASSEMBLYREF_SIZE];
mono_metadata_decode_row(table_info, index, cols, MONO_ASSEMBLYREF_SIZE);
return String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME]));
return {
String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME])),
(uint16_t)cols[MONO_ASSEMBLYREF_MAJOR_VERSION],
(uint16_t)cols[MONO_ASSEMBLYREF_MINOR_VERSION],
(uint16_t)cols[MONO_ASSEMBLYREF_BUILD_NUMBER],
(uint16_t)cols[MONO_ASSEMBLYREF_REV_NUMBER]
};
}
Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_assembly_dependencies) {
MonoImage *image = p_assembly->get_image();
for (int i = 0; i < mono_image_get_table_rows(image, MONO_TABLE_ASSEMBLYREF); i++) {
String ref_name = get_assemblyref_name(image, i);
AssemblyRefInfo ref_info = get_assemblyref_name(image, i);
const String &ref_name = ref_info.name;
if (r_assembly_dependencies.has(ref_name))
continue;
GDMonoAssembly *ref_assembly = NULL;
String path;
bool has_extension = ref_name.ends_with(".dll") || ref_name.ends_with(".exe");
for (int j = 0; j < p_search_dirs.size(); j++) {
const String &search_dir = p_search_dirs[j];
{
MonoAssemblyName *ref_aname = mono_assembly_name_new("A"); // We can't allocate an empty MonoAssemblyName, hence "A"
CRASH_COND(ref_aname == nullptr);
SCOPE_EXIT {
mono_assembly_name_free(ref_aname);
mono_free(ref_aname);
};
if (has_extension) {
path = search_dir.plus_file(ref_name);
if (FileAccess::exists(path)) {
GDMono::get_singleton()->load_assembly_from(ref_name.get_basename(), path, &ref_assembly, true);
if (ref_assembly != NULL)
break;
}
} else {
path = search_dir.plus_file(ref_name + ".dll");
if (FileAccess::exists(path)) {
GDMono::get_singleton()->load_assembly_from(ref_name, path, &ref_assembly, true);
if (ref_assembly != NULL)
break;
}
mono_assembly_get_assemblyref(image, i, ref_aname);
path = search_dir.plus_file(ref_name + ".exe");
if (FileAccess::exists(path)) {
GDMono::get_singleton()->load_assembly_from(ref_name, path, &ref_assembly, true);
if (ref_assembly != NULL)
break;
}
if (!GDMono::get_singleton()->load_assembly(ref_name, ref_aname, &ref_assembly, /* refonly: */ true, p_search_dirs)) {
ERR_FAIL_V_MSG(ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'.");
}
r_assembly_dependencies[ref_name] = ref_assembly->get_path();
}
ERR_FAIL_COND_V_MSG(!ref_assembly, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'.");
// Use the path we got from the search. Don't try to get the path from the loaded assembly as we can't trust it will be from the selected BCL dir.
r_assembly_dependencies[ref_name] = path;
Error err = get_assembly_dependencies(ref_assembly, p_search_dirs, r_assembly_dependencies);
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot load one of the dependencies for the assembly: '" + ref_name + "'.");
}
@ -113,6 +115,11 @@ Error get_exported_assembly_dependencies(const Dictionary &p_initial_assemblies,
Vector<String> search_dirs;
GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_bcl_dir);
if (p_custom_bcl_dir.length()) {
// Only one mscorlib can be loaded. We need this workaround to make sure we get it from the right BCL directory.
r_assembly_dependencies["mscorlib"] = p_custom_bcl_dir.plus_file("mscorlib.dll").simplify_path();
}
for (const Variant *key = p_initial_assemblies.next(); key; key = p_initial_assemblies.next(key)) {
String assembly_name = *key;
String assembly_path = p_initial_assemblies[*key];

View file

@ -1,38 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{AEBF0036-DA76-4341-B651-A3F2856AB2FA}</ProjectGuid>
<OutputType>Library</OutputType>
<OutputPath>bin/$(Configuration)</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<RootNamespace>Godot</RootNamespace>
<AssemblyName>GodotSharp</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<TargetFramework>netstandard2.0</TargetFramework>
<DocumentationFile>$(OutputPath)/$(AssemblyName).xml</DocumentationFile>
<BaseIntermediateOutputPath>obj</BaseIntermediateOutputPath>
<EnableDefaultItems>false</EnableDefaultItems>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<DefineConstants>$(GodotDefineConstants);GODOT;DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
<PropertyGroup>
<DefineConstants>$(DefineConstants);GODOT</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>portable</DebugType>
<Optimize>true</Optimize>
<DefineConstants>$(GodotDefineConstants);GODOT;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="Core\AABB.cs" />
<Compile Include="Core\Array.cs" />
@ -82,5 +60,4 @@
Fortunately code completion, go to definition and such still work.
-->
<Import Project="Generated\GeneratedIncludes.props" />
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -1,27 +1,3 @@
using System.Reflection;
using System.Runtime.CompilerServices;
// Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project.
[assembly: AssemblyTitle("GodotSharp")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
[assembly: AssemblyVersion("1.0.*")]
// The following attributes are used to specify the signing key for the assembly,
// if desired. See the Mono documentation for more information about signing.
//[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("")]
[assembly: InternalsVisibleTo("GodotSharpEditor")]

View file

@ -1,46 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{8FBEC238-D944-4074-8548-B3B524305905}</ProjectGuid>
<OutputType>Library</OutputType>
<OutputPath>bin/$(Configuration)</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<RootNamespace>Godot</RootNamespace>
<AssemblyName>GodotSharpEditor</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<TargetFramework>netstandard2.0</TargetFramework>
<DocumentationFile>$(OutputPath)/$(AssemblyName).xml</DocumentationFile>
<BaseIntermediateOutputPath>obj</BaseIntermediateOutputPath>
<EnableDefaultItems>false</EnableDefaultItems>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<DefineConstants>$(GodotDefineConstants);GODOT;DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
<PropertyGroup>
<DefineConstants>$(DefineConstants);GODOT</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>portable</DebugType>
<Optimize>true</Optimize>
<DefineConstants>$(GodotDefineConstants);GODOT;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="Generated\GeneratedIncludes.props" />
<ItemGroup>
<ProjectReference Include="..\GodotSharp\GodotSharp.csproj">
<Private>False</Private>
<Private>false</Private>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!--
We import a props file with auto-generated includes. This works well with Rider.
However, Visual Studio and MonoDevelop won't list them in the solution explorer.
We can't use wildcards as there may be undesired old files still hanging around.
Fortunately code completion, go to definition and such still work.
-->
<Import Project="Generated\GeneratedIncludes.props" />
</Project>

View file

@ -1,25 +0,0 @@
using System.Reflection;
// Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project.
[assembly: AssemblyTitle("GodotSharpEditor")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
[assembly: AssemblyVersion("1.0.*")]
// The following attributes are used to specify the signing key for the assembly,
// if desired. See the Mono documentation for more information about signing.
//[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("")]

View file

@ -521,8 +521,8 @@ void GDMono::add_assembly(uint32_t p_domain_id, GDMonoAssembly *p_assembly) {
GDMonoAssembly *GDMono::get_loaded_assembly(const String &p_name) {
if (p_name == "mscorlib")
return get_corlib_assembly();
if (p_name == "mscorlib" && corlib_assembly)
return corlib_assembly;
MonoDomain *domain = mono_domain_get();
uint32_t domain_id = domain ? mono_domain_get_id(domain) : 0;
@ -532,7 +532,9 @@ GDMonoAssembly *GDMono::get_loaded_assembly(const String &p_name) {
bool GDMono::load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bool p_refonly) {
#ifdef DEBUG_ENABLED
CRASH_COND(!r_assembly);
#endif
MonoAssemblyName *aname = mono_assembly_name_new(p_name.utf8());
bool result = load_assembly(p_name, aname, r_assembly, p_refonly);
@ -544,26 +546,27 @@ bool GDMono::load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bo
bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly) {
#ifdef DEBUG_ENABLED
CRASH_COND(!r_assembly);
#endif
return load_assembly(p_name, p_aname, r_assembly, p_refonly, GDMonoAssembly::get_default_search_dirs());
}
bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly, const Vector<String> &p_search_dirs) {
#ifdef DEBUG_ENABLED
CRASH_COND(!r_assembly);
#endif
print_verbose("Mono: Loading assembly " + p_name + (p_refonly ? " (refonly)" : "") + "...");
MonoImageOpenStatus status = MONO_IMAGE_OK;
MonoAssembly *assembly = mono_assembly_load_full(p_aname, NULL, &status, p_refonly);
GDMonoAssembly *assembly = GDMonoAssembly::load(p_name, p_aname, p_refonly, p_search_dirs);
if (!assembly)
return false;
ERR_FAIL_COND_V(status != MONO_IMAGE_OK, false);
uint32_t domain_id = mono_domain_get_id(mono_domain_get());
GDMonoAssembly **stored_assembly = assemblies[domain_id].getptr(p_name);
ERR_FAIL_COND_V(stored_assembly == NULL, false);
ERR_FAIL_COND_V((*stored_assembly)->get_assembly() != assembly, false);
*r_assembly = *stored_assembly;
*r_assembly = assembly;
print_verbose("Mono: Assembly " + p_name + (p_refonly ? " (refonly)" : "") + " loaded from path: " + (*r_assembly)->get_path());

View file

@ -241,6 +241,7 @@ public:
bool load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bool p_refonly = false);
bool load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly = false);
bool load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly, const Vector<String> &p_search_dirs);
bool load_assembly_from(const String &p_name, const String &p_path, GDMonoAssembly **r_assembly, bool p_refonly = false);
Error finalize_and_unload_domain(MonoDomain *p_domain);

View file

@ -33,6 +33,7 @@
#include <mono/metadata/mono-debug.h>
#include <mono/metadata/tokentype.h>
#include "core/io/file_access_pack.h"
#include "core/list.h"
#include "core/os/file_access.h"
#include "core/os/os.h"
@ -99,7 +100,7 @@ void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const Strin
// - The 'load' hook is called after the assembly has been loaded. Its job is to add the
// assembly to the list of loaded assemblies so that the 'search' hook can look it up.
void GDMonoAssembly::assembly_load_hook(MonoAssembly *assembly, void *user_data) {
void GDMonoAssembly::assembly_load_hook(MonoAssembly *assembly, [[maybe_unused]] void *user_data) {
String name = String::utf8(mono_assembly_name_get_name(mono_assembly_get_name(assembly)));
@ -133,9 +134,7 @@ MonoAssembly *GDMonoAssembly::assembly_refonly_preload_hook(MonoAssemblyName *an
return GDMonoAssembly::_preload_hook(aname, assemblies_path, user_data, true);
}
MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, void *user_data, bool refonly) {
(void)user_data; // UNUSED
MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, [[maybe_unused]] void *user_data, bool refonly) {
String name = String::utf8(mono_assembly_name_get_name(aname));
bool has_extension = name.ends_with(".dll") || name.ends_with(".exe");
@ -147,15 +146,13 @@ MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, void *user_d
return NULL;
}
MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, void *user_data, bool refonly) {
(void)user_data; // UNUSED
MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, [[maybe_unused]] void *user_data, bool refonly) {
String name = String::utf8(mono_assembly_name_get_name(aname));
return _load_assembly_search(name, search_dirs, refonly);
return _load_assembly_search(name, aname, refonly, search_dirs);
}
MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, const Vector<String> &p_search_dirs, bool p_refonly) {
MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs) {
MonoAssembly *res = NULL;
String path;
@ -168,21 +165,21 @@ MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, const
if (has_extension) {
path = search_dir.plus_file(p_name);
if (FileAccess::exists(path)) {
res = _real_load_assembly_from(path, p_refonly);
res = _real_load_assembly_from(path, p_refonly, p_aname);
if (res != NULL)
return res;
}
} else {
path = search_dir.plus_file(p_name + ".dll");
if (FileAccess::exists(path)) {
res = _real_load_assembly_from(path, p_refonly);
res = _real_load_assembly_from(path, p_refonly, p_aname);
if (res != NULL)
return res;
}
path = search_dir.plus_file(p_name + ".exe");
if (FileAccess::exists(path)) {
res = _real_load_assembly_from(path, p_refonly);
res = _real_load_assembly_from(path, p_refonly, p_aname);
if (res != NULL)
return res;
}
@ -230,7 +227,7 @@ void GDMonoAssembly::initialize() {
mono_install_assembly_load_hook(&assembly_load_hook, NULL);
}
MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, bool p_refonly) {
MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, bool p_refonly, MonoAssemblyName *p_aname) {
Vector<uint8_t> data = FileAccess::get_file_as_array(p_path);
ERR_FAIL_COND_V_MSG(data.empty(), NULL, "Could read the assembly in the specified location");
@ -255,7 +252,33 @@ MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, boo
true, &status, p_refonly,
image_filename.utf8());
ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !image, NULL, "Failed to open assembly image from the loaded data");
ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !image, NULL, "Failed to open assembly image from memory: '" + p_path + "'.");
if (p_aname != nullptr) {
// Check assembly version
const MonoTableInfo *table = mono_image_get_table_info(image, MONO_TABLE_ASSEMBLY);
ERR_FAIL_NULL_V(table, nullptr);
if (mono_table_info_get_rows(table)) {
uint32_t cols[MONO_ASSEMBLY_SIZE];
mono_metadata_decode_row(table, 0, cols, MONO_ASSEMBLY_SIZE);
// Not sure about .NET's policy. We will only ensure major and minor are equal, and ignore build and revision.
uint16_t major = cols[MONO_ASSEMBLY_MAJOR_VERSION];
uint16_t minor = cols[MONO_ASSEMBLY_MINOR_VERSION];
uint16_t required_minor;
uint16_t required_major = mono_assembly_name_get_version(p_aname, &required_minor, nullptr, nullptr);
if (required_major != 0) {
if (major != required_major && minor != required_minor) {
mono_image_close(image);
return nullptr;
}
}
}
}
#ifdef DEBUG_ENABLED
Vector<uint8_t> pdb_data;
@ -283,7 +306,7 @@ no_pdb:
MonoAssembly *assembly = mono_assembly_load_from_full(image, image_filename.utf8().get_data(), &status, p_refonly);
ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !assembly, NULL, "Failed to load assembly for image");
ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !assembly, nullptr, "Failed to load assembly for image");
if (need_manual_load_hook) {
// For some reason if an assembly survived domain reloading (maybe because it's referenced somewhere else),
@ -425,6 +448,26 @@ GDMonoClass *GDMonoAssembly::get_object_derived_class(const StringName &p_class)
return match;
}
GDMonoAssembly *GDMonoAssembly::load(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs) {
if (GDMono::get_singleton()->get_corlib_assembly() && (p_name == "mscorlib" || p_name == "mscorlib.dll"))
return GDMono::get_singleton()->get_corlib_assembly();
// We need to manually call the search hook in this case, as it won't be called in the next step
MonoAssembly *assembly = mono_assembly_invoke_search_hook(p_aname);
if (!assembly) {
assembly = _load_assembly_search(p_name, p_aname, p_refonly, p_search_dirs);
ERR_FAIL_NULL_V(assembly, nullptr);
}
GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(p_name);
ERR_FAIL_NULL_V_MSG(loaded_asm, nullptr, "Loaded assembly missing from table. Did we not receive the load hook?");
ERR_FAIL_COND_V(loaded_asm->get_assembly() != assembly, nullptr);
return loaded_asm;
}
GDMonoAssembly *GDMonoAssembly::load_from(const String &p_name, const String &p_path, bool p_refonly) {
if (p_name == "mscorlib" || p_name == "mscorlib.dll")

View file

@ -93,8 +93,8 @@ class GDMonoAssembly {
static MonoAssembly *_search_hook(MonoAssemblyName *aname, void *user_data, bool refonly);
static MonoAssembly *_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data, bool refonly);
static MonoAssembly *_real_load_assembly_from(const String &p_path, bool p_refonly);
static MonoAssembly *_load_assembly_search(const String &p_name, const Vector<String> &p_search_dirs, bool p_refonly);
static MonoAssembly *_real_load_assembly_from(const String &p_path, bool p_refonly, MonoAssemblyName *p_aname = nullptr);
static MonoAssembly *_load_assembly_search(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs);
friend class GDMono;
static void initialize();
@ -120,7 +120,9 @@ public:
static String find_assembly(const String &p_name);
static void fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config = String(), const String &p_custom_bcl_dir = String());
static const Vector<String> &get_default_search_dirs() { return search_dirs; }
static GDMonoAssembly *load(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs);
static GDMonoAssembly *load_from(const String &p_name, const String &p_path, bool p_refonly);
GDMonoAssembly(const String &p_name, MonoImage *p_image, MonoAssembly *p_assembly);

View file

@ -175,7 +175,7 @@ void GDMonoLog::initialize() {
log_level_id = get_log_level_id(log_level.get_data());
if (log_file) {
OS::get_singleton()->print("Mono: Logfile is: %s\n", log_file_path.utf8().get_data());
OS::get_singleton()->print("Mono: Log file is: '%s'\n", log_file_path.utf8().get_data());
mono_trace_set_log_handler(mono_log_callback, this);
} else {
OS::get_singleton()->printerr("Mono: No log file, using default log handler\n");

View file

@ -81,4 +81,25 @@
} while (true);
#endif
namespace gdmono {
template <typename F>
struct ScopeExit {
ScopeExit(F p_exit_func) :
exit_func(p_exit_func) {}
~ScopeExit() { exit_func(); }
F exit_func;
};
class ScopeExitAux {
public:
template <typename F>
ScopeExit<F> operator+(F p_exit_func) { return ScopeExit<F>(p_exit_func); }
};
} // namespace gdmono
#define SCOPE_EXIT \
auto GD_UNIQUE_NAME(gd_scope_exit) = gdmono::ScopeExitAux() + [=]() -> void
#endif // UTIL_MACROS_H