Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add C# source generator for ScriptPathAttribute #46713

Merged
merged 1 commit into from
Mar 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions modules/mono/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Project>
<Import Project="$(MSBuildThisFileDirectory)\SdkPackageVersions.props" />
</Project>
6 changes: 6 additions & 0 deletions modules/mono/SdkPackageVersions.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<Project>
<PropertyGroup>
<PackageVersion_Godot_NET_Sdk>4.0.0-dev4</PackageVersion_Godot_NET_Sdk>
<PackageVersion_Godot_SourceGenerators>4.0.0-dev1</PackageVersion_Godot_SourceGenerators>
Comment on lines +3 to +4
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it intended to have version 4.0.0-dev4 instead of 4.0.0-dev1?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, since these are the versions of each package, dev1 to dev3 have already been used. It does seem a bit strange that they're out of sync, so maybe the source generators package should just start at 4.0.0-dev4.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For stable releases they should be the same version, but during development there will be new additions to the source generator while the Sdk may remain the same. Technically we can release an unchanged new version of the Sdk to keep the version in sync with the other package, but that doesn't feel right. The only case where that would be appropriate is when going from rc to stable without changes in the packages.

</PropertyGroup>
</Project>
24 changes: 17 additions & 7 deletions modules/mono/build_scripts/godot_net_sdk_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ def build_godot_net_sdk(source, target, env):
# No need to copy targets. The Godot.NET.Sdk csproj takes care of copying them.


def get_nupkgs_versions(props_file):
import xml.etree.ElementTree as ET

tree = ET.parse(props_file)
root = tree.getroot()

return {
"Godot.NET.Sdk": root.find("./PropertyGroup/PackageVersion_Godot_NET_Sdk").text.strip(),
"Godot.SourceGenerators": root.find("./PropertyGroup/PackageVersion_Godot_SourceGenerators").text.strip(),
}


def build(env_mono):
assert env_mono["tools"]

Expand All @@ -30,14 +42,12 @@ def build(env_mono):

module_dir = os.getcwd()

package_version_file = os.path.join(
module_dir, "editor", "Godot.NET.Sdk", "Godot.NET.Sdk", "Godot.NET.Sdk_PackageVersion.txt"
)

with open(package_version_file, mode="r") as f:
version = f.read().strip()
nupkgs_versions = get_nupkgs_versions(os.path.join(module_dir, "SdkPackageVersions.props"))

target_filenames = ["Godot.NET.Sdk.%s.nupkg" % version]
target_filenames = [
"Godot.NET.Sdk.%s.nupkg" % nupkgs_versions["Godot.NET.Sdk"],
"Godot.SourceGenerators.%s.nupkg" % nupkgs_versions["Godot.SourceGenerators"],
]

targets = [os.path.join(nupkgs_dir, filename) for filename in target_filenames]

Expand Down
120 changes: 60 additions & 60 deletions modules/mono/csharp_script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "csharp_script.h"

#include <mono/metadata/threads.h>
#include <mono/metadata/tokentype.h>
#include <stdint.h>

#include "core/config/project_settings.h"
Expand Down Expand Up @@ -1182,46 +1183,56 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
}
#endif

void CSharpLanguage::_load_scripts_metadata() {
scripts_metadata.clear();
void CSharpLanguage::lookup_script_for_class(GDMonoClass *p_class) {
if (!p_class->has_attribute(CACHED_CLASS(ScriptPathAttribute))) {
return;
}

String scripts_metadata_filename = "scripts_metadata.";
MonoObject *attr = p_class->get_attribute(CACHED_CLASS(ScriptPathAttribute));
String path = CACHED_FIELD(ScriptPathAttribute, path)->get_string_value(attr);

#ifdef TOOLS_ENABLED
scripts_metadata_filename += Engine::get_singleton()->is_editor_hint() ? "editor" : "editor_player";
#else
#ifdef DEBUG_ENABLED
scripts_metadata_filename += "debug";
#else
scripts_metadata_filename += "release";
#endif
#endif
dotnet_script_lookup_map[path] = DotNetScriptLookupInfo(
p_class->get_namespace(), p_class->get_name(), p_class);
}

String scripts_metadata_path = GodotSharpDirs::get_res_metadata_dir().plus_file(scripts_metadata_filename);
void CSharpLanguage::lookup_scripts_in_assembly(GDMonoAssembly *p_assembly) {
if (p_assembly->has_attribute(CACHED_CLASS(AssemblyHasScriptsAttribute))) {
MonoObject *attr = p_assembly->get_attribute(CACHED_CLASS(AssemblyHasScriptsAttribute));
bool requires_lookup = CACHED_FIELD(AssemblyHasScriptsAttribute, requiresLookup)->get_bool_value(attr);

if (FileAccess::exists(scripts_metadata_path)) {
String old_json;
if (requires_lookup) {
// This is supported for scenarios where specifying all types would be cumbersome,
// such as when disabling C# source generators (for whatever reason) or when using a
// language other than C# that has nothing similar to source generators to automate it.
MonoImage *image = p_assembly->get_image();

Error ferr = read_all_file_utf8(scripts_metadata_path, old_json);
int rows = mono_image_get_table_rows(image, MONO_TABLE_TYPEDEF);

ERR_FAIL_COND(ferr != OK);
for (int i = 1; i < rows; i++) {
// We don't search inner classes, only top-level.
MonoClass *mono_class = mono_class_get(image, (i + 1) | MONO_TOKEN_TYPE_DEF);

Variant old_dict_var;
String err_str;
int err_line;
Error json_err = JSON::parse(old_json, old_dict_var, err_str, err_line);
if (json_err != OK) {
ERR_PRINT("Failed to parse metadata file: '" + err_str + "' (" + String::num_int64(err_line) + ").");
return;
}
if (!mono_class_is_assignable_from(CACHED_CLASS_RAW(GodotObject), mono_class)) {
continue;
}

scripts_metadata = old_dict_var.operator Dictionary();
scripts_metadata_invalidated = false;
GDMonoClass *current = p_assembly->get_class(mono_class);
if (current) {
lookup_script_for_class(current);
}
}
} else {
// This is the most likely scenario as we use C# source generators
MonoArray *script_types = (MonoArray *)CACHED_FIELD(AssemblyHasScriptsAttribute, scriptTypes)->get_value(attr);

print_verbose("Successfully loaded scripts metadata");
} else {
if (!Engine::get_singleton()->is_editor_hint()) {
ERR_PRINT("Missing scripts metadata file.");
int length = mono_array_length(script_types);

for (int i = 0; i < length; i++) {
MonoReflectionType *reftype = mono_array_get(script_types, MonoReflectionType *, i);
ManagedType type = ManagedType::from_reftype(reftype);
ERR_CONTINUE(!type.type_class);
lookup_script_for_class(type.type_class);
}
}
}
}
Expand Down Expand Up @@ -1300,7 +1311,7 @@ void CSharpLanguage::_on_scripts_domain_unloaded() {
}
#endif

scripts_metadata_invalidated = true;
dotnet_script_lookup_map.clear();
}

#ifdef TOOLS_ENABLED
Expand Down Expand Up @@ -3356,45 +3367,34 @@ Error CSharpScript::reload(bool p_keep_state) {

GD_MONO_SCOPE_THREAD_ATTACH;

GDMonoAssembly *project_assembly = GDMono::get_singleton()->get_project_assembly();

if (project_assembly) {
const Variant *script_metadata_var = CSharpLanguage::get_singleton()->get_scripts_metadata().getptr(get_path());
if (script_metadata_var) {
Dictionary script_metadata = script_metadata_var->operator Dictionary()["class"];
const Variant *namespace_ = script_metadata.getptr("namespace");
const Variant *class_name = script_metadata.getptr("class_name");
ERR_FAIL_NULL_V(namespace_, ERR_BUG);
ERR_FAIL_NULL_V(class_name, ERR_BUG);
GDMonoClass *klass = project_assembly->get_class(namespace_->operator String(), class_name->operator String());
if (klass && CACHED_CLASS(GodotObject)->is_assignable_from(klass)) {
script_class = klass;
}
} else {
// Missing script metadata. Fallback to legacy method
script_class = project_assembly->get_object_derived_class(name);
const DotNetScriptLookupInfo *lookup_info =
CSharpLanguage::get_singleton()->lookup_dotnet_script(get_path());

if (lookup_info) {
GDMonoClass *klass = lookup_info->script_class;
if (klass) {
ERR_FAIL_COND_V(!CACHED_CLASS(GodotObject)->is_assignable_from(klass), FAILED);
script_class = klass;
}
}

valid = script_class != nullptr;
valid = script_class != nullptr;

if (script_class) {
if (script_class) {
#ifdef DEBUG_ENABLED
print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path());
print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path());
#endif

native = GDMonoUtils::get_class_native_base(script_class);
native = GDMonoUtils::get_class_native_base(script_class);

CRASH_COND(native == nullptr);
CRASH_COND(native == nullptr);

update_script_class_info(this);
update_script_class_info(this);

_update_exports();
}

return OK;
_update_exports();
}

return ERR_FILE_MISSING_DEPENDENCIES;
return OK;
}

ScriptLanguage *CSharpScript::get_language() const {
Expand Down
34 changes: 20 additions & 14 deletions modules/mono/csharp_script.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@ TScriptInstance *cast_script_instance(ScriptInstance *p_inst) {

#define CAST_CSHARP_INSTANCE(m_inst) (cast_script_instance<CSharpInstance, CSharpLanguage>(m_inst))

struct DotNetScriptLookupInfo {
String class_namespace;
String class_name;
GDMonoClass *script_class = nullptr;

DotNetScriptLookupInfo() {} // Required by HashMap...

DotNetScriptLookupInfo(const String &p_class_namespace, const String &p_class_name, GDMonoClass *p_script_class) :
class_namespace(p_class_namespace), class_name(p_class_name), script_class(p_script_class) {
}
};

class CSharpScript : public Script {
GDCLASS(CSharpScript, Script);

Expand Down Expand Up @@ -390,16 +402,15 @@ class CSharpLanguage : public ScriptLanguage {

int lang_idx = -1;

Dictionary scripts_metadata;
bool scripts_metadata_invalidated = true;
HashMap<String, DotNetScriptLookupInfo> dotnet_script_lookup_map;

void lookup_script_for_class(GDMonoClass *p_class);

// For debug_break and debug_break_parse
int _debug_parse_err_line = -1;
String _debug_parse_err_file;
String _debug_error;

void _load_scripts_metadata();

friend class GDMono;
void _on_scripts_domain_unloaded();

Expand Down Expand Up @@ -436,18 +447,13 @@ class CSharpLanguage : public ScriptLanguage {
void reload_assemblies(bool p_soft_reload);
#endif

_FORCE_INLINE_ Dictionary get_scripts_metadata_or_nothing() {
return scripts_metadata_invalidated ? Dictionary() : scripts_metadata;
}
_FORCE_INLINE_ ManagedCallableMiddleman *get_managed_callable_middleman() const { return managed_callable_middleman; }

_FORCE_INLINE_ const Dictionary &get_scripts_metadata() {
if (scripts_metadata_invalidated) {
_load_scripts_metadata();
}
return scripts_metadata;
}
void lookup_scripts_in_assembly(GDMonoAssembly *p_assembly);

_FORCE_INLINE_ ManagedCallableMiddleman *get_managed_callable_middleman() const { return managed_callable_middleman; }
const DotNetScriptLookupInfo *lookup_dotnet_script(const String &p_script_path) const {
return dotnet_script_lookup_map.getptr(p_script_path);
}

String get_name() const override;

Expand Down
18 changes: 18 additions & 0 deletions modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
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
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators", "Godot.SourceGenerators\Godot.SourceGenerators.csproj", "{32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators.Sample", "Godot.SourceGenerators.Sample\Godot.SourceGenerators.Sample.csproj", "{7297A614-8DF5-43DE-9EAD-99671B26BD1F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotSharp", "..\..\glue\GodotSharp\GodotSharp\GodotSharp.csproj", "{AEBF0036-DA76-4341-B651-A3F2856AB2FA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -12,5 +18,17 @@ Global
{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
{32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}.Release|Any CPU.Build.0 = Release|Any CPU
{7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Release|Any CPU.Build.0 = Release|Any CPU
{AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,33 @@

<PackageId>Godot.NET.Sdk</PackageId>
<Version>4.0.0</Version>
<PackageProjectUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk</PackageProjectUrl>
<PackageVersion>$(PackageVersion_Godot_NET_Sdk)</PackageVersion>
<RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk</RepositoryUrl>
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
<PackageType>MSBuildSdk</PackageType>
<PackageTags>MSBuildSdk</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>

<PropertyGroup>
<NuspecFile>Godot.NET.Sdk.nuspec</NuspecFile>
<GenerateNuspecDependsOn>$(GenerateNuspecDependsOn);SetNuSpecProperties</GenerateNuspecDependsOn>
<!-- Exclude target framework from the package dependencies as we don't include the build output -->
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
<IncludeBuildOutput>false</IncludeBuildOutput>
</PropertyGroup>

<Target Name="ReadGodotNETSdkVersion" BeforeTargets="BeforeBuild;BeforeRebuild;CoreCompile">
<PropertyGroup>
<PackageVersion>$([System.IO.File]::ReadAllText('$(ProjectDir)Godot.NET.Sdk_PackageVersion.txt').Trim())</PackageVersion>
</PropertyGroup>
</Target>
<ItemGroup>
<!-- Package Sdk\Sdk.props and Sdk\Sdk.targets file -->
<None Include="Sdk\Sdk.props" Pack="true" PackagePath="Sdk" Visible="false" />
<None Include="Sdk\Sdk.targets" Pack="true" PackagePath="Sdk" Visible="false" />
<!-- SdkPackageVersions.props -->

<Target Name="SetNuSpecProperties" Condition=" Exists('$(NuspecFile)') " DependsOnTargets="ReadGodotNETSdkVersion">
<PropertyGroup>
<NuspecProperties>
id=$(PackageId);
description=$(Description);
authors=$(Authors);
version=$(PackageVersion);
packagetype=$(PackageType);
tags=$(PackageTags);
projecturl=$(PackageProjectUrl)
</NuspecProperties>
</PropertyGroup>
</Target>
<None Include="..\..\..\SdkPackageVersions.props" Pack="true" PackagePath="Sdk" Visible="false" />
</ItemGroup>

<Target Name="CopyNupkgToSConsOutputDir" AfterTargets="Pack">
<PropertyGroup>
<GodotSourceRootPath>$(SolutionDir)\..\..\..\..\</GodotSourceRootPath>
<GodotOutputDataDir>$(GodotSourceRootPath)\bin\GodotSharp\</GodotOutputDataDir>
</PropertyGroup>
<Copy SourceFiles="$(OutputPath)$(PackageId).$(PackageVersion).nupkg"
DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" />
<Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" />
</Target>
</Project>

This file was deleted.

This file was deleted.

Loading