Skip to content

Commit 11c5a48

Browse files
authored
Add .NET 10 build targets (microsoft#454)
1 parent 7c464d6 commit 11c5a48

32 files changed

+215
-142
lines changed

Directory.Build.props

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
<Project>
22
<PropertyGroup>
3-
<TargetFrameworks Condition=" '$(TargetFrameworks)' == '' and ! $([MSBuild]::IsOsPlatform('Windows')) ">net9.0;net8.0;netstandard2.0</TargetFrameworks>
4-
<TargetFrameworks Condition=" '$(TargetFrameworks)' == '' and $([MSBuild]::IsOsPlatform('Windows')) ">net9.0;net8.0;netstandard2.0;net472</TargetFrameworks>
5-
<TargetFrameworks Condition=" '$(PublishAot)' == 'true' ">net9.0</TargetFrameworks>
3+
<IsDotNet10SdkAvailable Condition="$([MSBuild]::VersionGreaterThanOrEquals('$(MSBuildVersion)', '17.15'))">true</IsDotNet10SdkAvailable>
4+
<AotTargetFramework Condition="'$(IsDotNet10SdkAvailable)' != 'true'">net9.0</AotTargetFramework>
5+
<AotTargetFramework Condition="'$(IsDotNet10SdkAvailable)' == 'true'">net10.0</AotTargetFramework>
6+
</PropertyGroup>
7+
<PropertyGroup Condition="'$(TargetFramework)' == ''">
8+
<TargetFrameworks>net9.0;net8.0;netstandard2.0</TargetFrameworks>
9+
<TargetFrameworks Condition="'$(IsDotNet10SdkAvailable)' == 'true'">net10.0;$(TargetFrameworks)</TargetFrameworks>
10+
<TargetFrameworks Condition="$([MSBuild]::IsOsPlatform('Windows'))">$(TargetFrameworks);net472</TargetFrameworks>
11+
<TargetFrameworks Condition="'$(PublishAot)' == 'true'">$(AotTargetFramework)</TargetFrameworks>
12+
</PropertyGroup>
13+
<PropertyGroup>
614
<LangVersion>12</LangVersion>
715
<Nullable>enable</Nullable>
816
<Configuration Condition="'$(Configuration)'==''">Debug</Configuration>

bench/Directory.Build.props

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
<Project>
22
<!-- The main purpose of this file is to prevent use of the root Directory.Build.props file,
33
because Benchmark.NET does not like the build output paths to be redirected. -->
4+
<PropertyGroup Condition="'$(TargetFramework)' == ''">
5+
<IsDotNet10SdkAvailable Condition="$([MSBuild]::VersionGreaterThanOrEquals('$(MSBuildVersion)', '17.15'))">true</IsDotNet10SdkAvailable>
6+
<TargetFrameworks>net9.0;net8.0;netstandard2.0</TargetFrameworks>
7+
<TargetFrameworks Condition="'$(IsDotNet10SdkAvailable)' == 'true'">net10.0;$(TargetFrameworks)</TargetFrameworks>
8+
<TargetFrameworks Condition="$([MSBuild]::IsOsPlatform('Windows'))">$(TargetFrameworks);net472</TargetFrameworks>
9+
</PropertyGroup>
410
<PropertyGroup>
5-
<TargetFrameworks Condition=" '$(TargetFrameworks)' == '' and ! $([MSBuild]::IsOsPlatform('Windows')) ">net9.0;net8.0</TargetFrameworks>
6-
<TargetFrameworks Condition=" '$(TargetFrameworks)' == '' and $([MSBuild]::IsOsPlatform('Windows')) ">net9.0;net8.0;net472</TargetFrameworks>
711
<LangVersion>12</LangVersion>
812
<Nullable>enable</Nullable>
913
<PackRelease>false</PackRelease>

src/NodeApi.DotNetHost/NodeApi.DotNetHost.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<ItemGroup>
1616
<ProjectReference Include="..\NodeApi\NodeApi.csproj" GeneratePathProperty="true" PrivateAssets="all" />
1717
</ItemGroup>
18-
18+
1919
<ItemGroup>
2020
<None Pack="true" PackagePath="\" Include="..\..\README.md" />
2121
</ItemGroup>
@@ -28,7 +28,7 @@
2828
It's necessary to use <Exec/> here rather than a recursive <MSBuild/> task because
2929
PublishAot modifies properties/targets which would not be re-computed by the task.
3030
-->
31-
<Exec Command="dotnet publish $(MSBuildThisFileDirectory)..\NodeApi\NodeApi.csproj -c $(Configuration) -f net9.0 --self-contained -p:PublishAot=true" />
31+
<Exec Command="dotnet publish $(MSBuildThisFileDirectory)..\NodeApi\NodeApi.csproj -c $(Configuration) -f $(AotTargetFramework) --self-contained -p:PublishAot=true" />
3232
</Target>
3333

3434
<Target Name="NpmPack"

src/NodeApi.Generator/SymbolExtensions.cs

Lines changed: 100 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -249,21 +249,40 @@ private static TypeInfo BuildSymbolicEnumType(
249249
return enumBuilder.CreateTypeInfo()!;
250250
}
251251

252-
private static TypeAttributes GetTypeAttributes(TypeKind typeKind)
252+
private static TypeAttributes GetTypeAttributes(ITypeSymbol typeSymbol)
253253
{
254254
TypeAttributes attributes = TypeAttributes.Public;
255-
if (typeKind == TypeKind.Interface)
256-
{
257-
attributes |= TypeAttributes.Interface;
258-
}
259-
else if (typeKind == TypeKind.Delegate)
260-
{
261-
attributes |= TypeAttributes.Sealed;
262-
}
263-
if (typeKind != TypeKind.Enum)
255+
256+
switch (typeSymbol.TypeKind)
264257
{
265-
attributes |= TypeAttributes.Abstract;
258+
case TypeKind.Interface:
259+
attributes |= TypeAttributes.Interface | TypeAttributes.Abstract;
260+
break;
261+
262+
case TypeKind.Class:
263+
if (typeSymbol.IsAbstract || typeSymbol.IsStatic)
264+
{
265+
attributes |= TypeAttributes.Abstract;
266+
}
267+
if (typeSymbol.IsSealed || typeSymbol.IsStatic)
268+
{
269+
attributes |= TypeAttributes.Sealed;
270+
}
271+
break;
272+
273+
case TypeKind.Struct:
274+
attributes |= TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit;
275+
break;
276+
277+
case TypeKind.Delegate:
278+
attributes |= TypeAttributes.Sealed;
279+
break;
280+
281+
case TypeKind.Enum:
282+
attributes |= TypeAttributes.Abstract;
283+
break;
266284
}
285+
267286
return attributes;
268287
}
269288

@@ -291,7 +310,7 @@ private static Type BuildSymbolicObjectType(
291310
{
292311
typeBuilder = ModuleBuilder.DefineType(
293312
name: typeFullName,
294-
GetTypeAttributes(typeSymbol.TypeKind),
313+
GetTypeAttributes(typeSymbol),
295314
parent: baseType);
296315

297316
if (typeSymbol.TypeParameters.Length > 0)
@@ -347,7 +366,15 @@ static PropertyInfo GetAttributeProperty(Type type, string name)
347366
return thisType;
348367
}
349368

350-
return typeBuilder.CreateTypeInfo()!;
369+
try
370+
{
371+
return typeBuilder.CreateTypeInfo()!;
372+
}
373+
catch (Exception ex)
374+
{
375+
throw new InvalidOperationException(
376+
$"Failed to create type info for type: {typeSymbol.Name}", ex);
377+
}
351378
}
352379

353380
/// <summary>
@@ -390,7 +417,15 @@ static void BuildType(
390417
if (SymbolicTypes.TryGetValue(typeFullName, out Type? type) &&
391418
type is TypeBuilder typeBuilder)
392419
{
393-
typeBuilder.CreateTypeInfo();
420+
try
421+
{
422+
typeBuilder.CreateTypeInfo();
423+
}
424+
catch (Exception ex)
425+
{
426+
throw new InvalidOperationException(
427+
$"Failed to create type info for type: {typeSymbol.Name}", ex);
428+
}
394429
}
395430
}
396431

@@ -449,7 +484,10 @@ private static void BuildSymbolicTypeMembers(
449484
}
450485

451486
foreach (ISymbol memberSymbol in typeSymbol.GetMembers()
452-
.Where((m) => m.DeclaredAccessibility == Accessibility.Public))
487+
.Where((m) => m.DeclaredAccessibility == Accessibility.Public ||
488+
(m.DeclaredAccessibility == Accessibility.Private &&
489+
(m is IMethodSymbol ms && ms.ExplicitInterfaceImplementations.Length > 0) ||
490+
(m is IPropertySymbol ps && ps.ExplicitInterfaceImplementations.Length > 0))))
453491
{
454492
if (memberSymbol is IMethodSymbol constructorSymbol &&
455493
constructorSymbol.MethodKind == MethodKind.Constructor)
@@ -458,6 +496,7 @@ private static void BuildSymbolicTypeMembers(
458496
}
459497
else if (memberSymbol is IMethodSymbol methodSymbol &&
460498
(methodSymbol.MethodKind == MethodKind.Ordinary ||
499+
methodSymbol.MethodKind == MethodKind.ExplicitInterfaceImplementation ||
461500
methodSymbol.MethodKind == MethodKind.DelegateInvoke))
462501
{
463502
BuildSymbolicMethod(typeBuilder, methodSymbol, genericTypeParameters);
@@ -476,7 +515,7 @@ private static void BuildSymbolicTypeMembers(
476515
}
477516
else if (memberSymbol is INamedTypeSymbol nestedTypeSymbol)
478517
{
479-
TypeAttributes attributes = GetTypeAttributes(nestedTypeSymbol.TypeKind);
518+
TypeAttributes attributes = GetTypeAttributes(nestedTypeSymbol);
480519
attributes &= ~TypeAttributes.Public;
481520
attributes |= TypeAttributes.NestedPublic;
482521

@@ -533,9 +572,17 @@ private static void BuildSymbolicMethod(
533572
Type[]? genericTypeParameters)
534573
{
535574
bool isDelegateMethod = typeBuilder.BaseType == typeof(MulticastDelegate);
536-
MethodAttributes attributes = MethodAttributes.Public | (methodSymbol.IsStatic ?
537-
MethodAttributes.Static : MethodAttributes.Virtual | (isDelegateMethod ?
538-
MethodAttributes.HideBySig : MethodAttributes.Abstract));
575+
// All nonstatic methods are declared virtual on symbolic types.
576+
// This allows any struct/class methods to implement interface methods.
577+
MethodAttributes attributes =
578+
(methodSymbol.DeclaredAccessibility == Accessibility.Public ?
579+
MethodAttributes.Public : MethodAttributes.Private) |
580+
(methodSymbol.IsStatic ? MethodAttributes.Static : MethodAttributes.Virtual) |
581+
(methodSymbol.IsAbstract ? MethodAttributes.Abstract : default) |
582+
(methodSymbol.ExplicitInterfaceImplementations.Length > 0 ?
583+
MethodAttributes.Final : default) |
584+
(isDelegateMethod ? MethodAttributes.HideBySig : default);
585+
539586
MethodBuilder methodBuilder = typeBuilder.DefineMethod(
540587
methodSymbol.Name,
541588
attributes,
@@ -565,29 +612,41 @@ private static void BuildSymbolicMethod(
565612
methodBuilder.SetImplementationFlags(
566613
MethodImplAttributes.Runtime | MethodImplAttributes.Managed);
567614
}
568-
else if (methodSymbol.IsStatic)
615+
else if (!methodSymbol.IsAbstract)
569616
{
570-
// Static methods cannot be abstract; emit a minimal body.
617+
// Emit a minimal method body.
571618
methodBuilder.GetILGenerator().Emit(OpCodes.Ret);
572619
}
620+
621+
if (methodSymbol.ExplicitInterfaceImplementations.Length > 0)
622+
{
623+
MethodInfo interfaceMethod =
624+
methodSymbol.ExplicitInterfaceImplementations[0].AsMethodInfo();
625+
typeBuilder.DefineMethodOverride(methodBuilder, interfaceMethod);
626+
}
573627
}
574628

575629
private static void BuildSymbolicProperty(
576630
TypeBuilder typeBuilder,
577631
IPropertySymbol propertySymbol,
578632
Type[]? genericTypeParameters)
579633
{
634+
MethodAttributes attributes = MethodAttributes.SpecialName |
635+
(propertySymbol.DeclaredAccessibility == Accessibility.Public ?
636+
MethodAttributes.Public : MethodAttributes.Private) |
637+
(propertySymbol.IsStatic ? MethodAttributes.Static : MethodAttributes.Virtual) |
638+
(propertySymbol.IsAbstract ? MethodAttributes.Abstract : default) |
639+
(propertySymbol.IsVirtual ? MethodAttributes.Virtual : default) |
640+
(propertySymbol.ExplicitInterfaceImplementations.Length > 0 ?
641+
MethodAttributes.Final : default);
642+
580643
PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(
581644
propertySymbol.Name,
582645
PropertyAttributes.None,
583646
propertySymbol.Type.AsType(genericTypeParameters, buildType: false),
584647
propertySymbol.Parameters.Select(
585648
(p) => p.Type.AsType(genericTypeParameters, buildType: false)).ToArray());
586649

587-
MethodAttributes attributes = MethodAttributes.SpecialName | MethodAttributes.Public |
588-
(propertySymbol.IsStatic ? MethodAttributes.Static :
589-
MethodAttributes.Abstract | MethodAttributes.Virtual);
590-
591650
if (propertySymbol.GetMethod != null)
592651
{
593652
MethodBuilder getMethodBuilder = typeBuilder.DefineMethod(
@@ -598,8 +657,15 @@ private static void BuildSymbolicProperty(
598657
propertySymbol.GetMethod.Parameters.Select(
599658
(p) => p.Type.AsType(genericTypeParameters, buildType: false)).ToArray());
600659
BuildSymbolicParameters(getMethodBuilder, propertySymbol.GetMethod.Parameters);
601-
if (propertySymbol.IsStatic) getMethodBuilder.GetILGenerator().Emit(OpCodes.Ret);
660+
if (!propertySymbol.IsAbstract) getMethodBuilder.GetILGenerator().Emit(OpCodes.Ret);
602661
propertyBuilder.SetGetMethod(getMethodBuilder);
662+
663+
if (propertySymbol.ExplicitInterfaceImplementations.Length > 0)
664+
{
665+
MethodInfo interfaceGetMethod =
666+
propertySymbol.ExplicitInterfaceImplementations[0].GetMethod!.AsMethodInfo();
667+
typeBuilder.DefineMethodOverride(getMethodBuilder, interfaceGetMethod);
668+
}
603669
}
604670

605671
if (propertySymbol.SetMethod != null)
@@ -612,8 +678,15 @@ private static void BuildSymbolicProperty(
612678
propertySymbol.SetMethod.Parameters.Select(
613679
(p) => p.Type.AsType(genericTypeParameters, buildType: false)).ToArray());
614680
BuildSymbolicParameters(setMethodBuilder, propertySymbol.SetMethod.Parameters);
615-
if (propertySymbol.IsStatic) setMethodBuilder.GetILGenerator().Emit(OpCodes.Ret);
681+
if (!propertySymbol.IsAbstract) setMethodBuilder.GetILGenerator().Emit(OpCodes.Ret);
616682
propertyBuilder.SetSetMethod(setMethodBuilder);
683+
684+
if (propertySymbol.ExplicitInterfaceImplementations.Length > 0)
685+
{
686+
MethodInfo interfaceSetMethod =
687+
propertySymbol.ExplicitInterfaceImplementations[0].SetMethod!.AsMethodInfo();
688+
typeBuilder.DefineMethodOverride(setMethodBuilder, interfaceSetMethod);
689+
}
617690
}
618691
}
619692

src/NodeApi/NodeApi.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<UseSystemResourceKeys>true</UseSystemResourceKeys><!-- Trim detailed system exception messages. -->
1919
</PropertyGroup>
2020

21-
<PropertyGroup Condition=" '$(TargetFramework)' == 'net9.0' ">
21+
<PropertyGroup Condition=" '$(TargetFramework)' == '$(AotTargetFramework)' ">
2222
<!-- Enable AOT compatibility checks during compilation even when not publishing the AOT binary. -->
2323
<IsAotCompatible>true</IsAotCompatible>
2424
</PropertyGroup>

src/node-api-dotnet/pack.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ if (!packageName || !configuration || rids.length === 0) {
2121
const assemblyName = 'Microsoft.JavaScript.NodeApi';
2222

2323
const targetFrameworks = ['net9.0', 'net8.0'];
24+
const dotnetGlobalJson = require('../../global.json');
25+
if (dotnetGlobalJson.sdk.version.startsWith('10.')) targetFrameworks.unshift('net10.0');
2426
if (process.platform === 'win32') targetFrameworks.push('net472');
2527

2628
const fs = require('fs');
@@ -169,7 +171,7 @@ function copyFrameworkSpecificBinaries(targetFrameworks, packageStageDir, ...bin
169171
binFileName.startsWith('System.') &&
170172
!binFileName.includes('MetadataLoadContext')
171173
) return;
172-
174+
173175
// Exclude Microsoft.Bcl.AsyncInterfaces from new platforms
174176
if (
175177
tfm.includes('.') &&

test/JSProjectTests.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,19 @@ public void Test(string id)
4747

4848
string compileLogFilePath = GetBuildLogFilePath(
4949
projectName + "-" + moduleName, "projects");
50-
string tsConfigFile = "tsconfig." + Path.GetFileNameWithoutExtension(moduleName) + ".json";
50+
51+
string tsConfigFile = "tsconfig.json";
52+
if (IsCurrentTargetFramework(moduleName))
53+
{
54+
tsConfigFile = $"tsconfig.{moduleName}.json";
55+
File.WriteAllText(
56+
Path.Combine(ProjectDir(projectName), tsConfigFile),
57+
$"{{\n" +
58+
$" \"extends\": \"./tsconfig.json\",\n" +
59+
$" \"include\": [\"{moduleName}.ts\", \"{moduleName}.js\", \"bin/*.js\"]\n" +
60+
$"}}");
61+
}
62+
5163
BuildTestProjectTypeScript(projectName,
5264
compileLogFilePath,
5365
File.Exists(Path.Combine(ProjectDir(projectName), tsConfigFile)) ?
@@ -165,7 +177,7 @@ private static void BuildTestProjectTypeScript(
165177
};
166178

167179
logWriter.WriteLine();
168-
logWriter.WriteLine("tsc");
180+
logWriter.WriteLine("node " + nodeArgs);
169181

170182
Process nodeProcess = Process.Start(nodeStartInfo)!;
171183
errorOutput = LogOutput(nodeProcess, logWriter);

test/NativeAotTests.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
#if NET9_0_OR_GREATER
5-
64
using System;
75
using System.Collections.Generic;
86
using System.Diagnostics;
@@ -107,5 +105,3 @@ private static void BuildTestModuleTypeScript(string _ /*testCaseName*/)
107105
// Reference the generated type definitions from the C#?
108106
}
109107
}
110-
111-
#endif // NET9_0_OR_GREATER

test/NodeApi.Test.csproj

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4+
<IsDotNet10SdkAvailable Condition="$([MSBuild]::VersionGreaterThanOrEquals('$(MSBuildVersion)', '17.15'))">true</IsDotNet10SdkAvailable>
5+
<AotTargetFramework Condition="'$(IsDotNet10SdkAvailable)' != 'true'">net9.0</AotTargetFramework>
6+
<AotTargetFramework Condition="'$(IsDotNet10SdkAvailable)' == 'true'">net10.0</AotTargetFramework>
7+
</PropertyGroup>
8+
<PropertyGroup Condition="'$(TargetFramework)' == ''">
49
<!-- Exclude netstandard2.0 from target frameworks for tests. -->
5-
<TargetFrameworks Condition=" ! $([MSBuild]::IsOsPlatform('Windows')) ">net9.0;net8.0</TargetFrameworks>
6-
<TargetFrameworks Condition=" $([MSBuild]::IsOsPlatform('Windows')) ">net9.0;net8.0;net472</TargetFrameworks>
10+
<TargetFrameworks>net9.0;net8.0</TargetFrameworks>
11+
<TargetFrameworks Condition="'$(IsDotNet10SdkAvailable)' == 'true'">net10.0;$(TargetFrameworks)</TargetFrameworks>
12+
<TargetFrameworks Condition="$([MSBuild]::IsOsPlatform('Windows'))">$(TargetFrameworks);net472</TargetFrameworks>
13+
</PropertyGroup>
14+
<PropertyGroup>
715
<TestTfmsInParallel>false</TestTfmsInParallel>
8-
916
<RootNamespace>Microsoft.JavaScript.NodeApi.Test</RootNamespace>
1017
<AssemblyName>Microsoft.JavaScript.NodeApi.Test</AssemblyName>
1118
<IsPublishable>false</IsPublishable>
@@ -17,6 +24,7 @@
1724
<None Include="TestCases\**\*.ts" />
1825
<None Include="TestCases\**\*.js" />
1926
<Compile Remove="TestCases\**" />
27+
<Compile Condition="'$(TargetFramework)' != '$(AotTargetFramework)'" Remove="NativeAotTests.cs" />
2028
</ItemGroup>
2129

2230
<ItemGroup>

0 commit comments

Comments
 (0)