Skip to content

Commit def23a5

Browse files
authored
Framework multi-targeting improvements (#215)
1 parent 52a1d43 commit def23a5

25 files changed

+263
-251
lines changed

.github/workflows/build.yml

Lines changed: 26 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,27 @@ jobs:
1212
strategy:
1313
matrix:
1414
os: [ windows-latest, macos-latest, ubuntu-latest ]
15+
dotnet-version: [ net472, net6.0, net8.0]
1516
node-version: [ 18.x, 20.x ]
1617
configuration: [ Release ]
18+
exclude:
19+
# Exclude Node 18.x on .NET < 8, to thin the matrix.
20+
- dotnet-version: net6.0
21+
node-version: 18.x
22+
- dotnet-version: net472
23+
node-version: 18.x
24+
# Exclude .NET 4.x on non-Windows OS.
25+
- os: macos-latest
26+
dotnet-version: net472
27+
- os: ubuntu-latest
28+
dotnet-version: net472
29+
1730
fail-fast: false # Don't cancel other jobs when one job fails
1831

1932
runs-on: ${{ matrix.os }}
2033

2134
steps:
22-
- uses: actions/checkout@v3
35+
- uses: actions/checkout@v4
2336
with:
2437
fetch-depth: 0 # Deep clone is required for versioning on git commit height
2538

@@ -28,23 +41,21 @@ jobs:
2841
run: sudo ln -s /lib/x86_64-linux-gnu/libdl.so.2 /lib/x86_64-linux-gnu/libdl.so
2942

3043
- name: Setup .NET 6
31-
uses: actions/setup-dotnet@v3
44+
uses: actions/setup-dotnet@v4
3245
with:
3346
dotnet-version: 6.0.x
3447

48+
# The .NET 8 SDK is required even when the build matrix targets other .NET versions.
3549
- name: Setup .NET 8
36-
uses: actions/setup-dotnet@v3
50+
uses: actions/setup-dotnet@v4
3751
with:
3852
dotnet-version: 8.0.x
3953

4054
- name: Setup Node.js ${{ matrix.node-version }}
41-
uses: actions/setup-node@v3
55+
uses: actions/setup-node@v4
4256
with:
4357
node-version: ${{ matrix.node-version }}
4458

45-
- name: Build ${{ matrix.configuration }}
46-
run: dotnet build --configuration ${{ matrix.configuration }}
47-
4859
- name: Build packages
4960
id: pack
5061
run: dotnet pack --configuration ${{ matrix.configuration }}
@@ -56,48 +67,29 @@ jobs:
5667
# limit-access-to-actor: true
5768

5869
- name: Upload build artifacts
59-
uses: actions/upload-artifact@v3
70+
if: matrix.dotnet-version == 'net8.0' && matrix.node-version == '20.x'
71+
uses: actions/upload-artifact@v4
6072
with:
6173
name: ${{ matrix.os }}-${{ matrix.configuration }}-packages
6274
path: |
6375
out/pkg/*.nupkg
6476
out/pkg/*.tgz
6577
66-
- name: Test .NET 4.7.2
67-
if: matrix.os == 'windows-latest' && steps.pack.conclusion == 'success' && !cancelled()
68-
env:
69-
TRACE_NODE_API_HOST: 1
70-
run: >
71-
dotnet test -f net472
72-
--configuration ${{ matrix.configuration }}
73-
--logger trx
74-
--results-directory "out/test/netfx47-node${{ matrix.node-version }}-${{ matrix.configuration }}"
75-
76-
- name: Test .NET 6
77-
if: steps.pack.conclusion == 'success' && !cancelled()
78-
env:
79-
TRACE_NODE_API_HOST: 1
80-
run: >
81-
dotnet test -f net6.0
82-
--configuration ${{ matrix.configuration }}
83-
--logger trx
84-
--results-directory "out/test/dotnet6-node${{ matrix.node-version }}-${{ matrix.configuration }}"
85-
86-
- name: Test .NET 8
78+
- name: Test
8779
if: steps.pack.conclusion == 'success' && !cancelled()
8880
env:
8981
TRACE_NODE_API_HOST: 1
9082
run: >
91-
dotnet test -f net8.0
83+
dotnet test -f ${{ matrix.dotnet-version }}
9284
--configuration ${{ matrix.configuration }}
9385
--logger trx
94-
--results-directory "out/test/dotnet8-node${{ matrix.node-version }}-${{ matrix.configuration }}"
86+
--results-directory "out/test/${{matrix.dotnet-version}}-node${{matrix.node-version}}-${{matrix.configuration}}"
87+
continue-on-error: true
9588

9689
- name: Upload test logs
97-
if: always() # Update artifacts regardless if code succeeded, failed, or cancelled
98-
uses: actions/upload-artifact@v3
90+
uses: actions/upload-artifact@v4
9991
with:
100-
name: test-logs-${{ matrix.os }}-node${{ matrix.node-version }}-${{ matrix.configuration }}
92+
name: test-logs-${{ matrix.os }}-${{matrix.dotnet-version}}-node${{matrix.node-version}}-${{matrix.configuration}}
10193
path: |
10294
out/obj/${{ matrix.configuration }}/**/*.log
10395
out/obj/${{ matrix.configuration }}/**/*.rsp

.github/workflows/test-report.yml

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,30 @@ permissions:
1616
jobs:
1717
report:
1818
strategy:
19-
matrix:
19+
matrix: # This must be kept in sync with the PR build matrix.
2020
os: [ windows-latest, macos-latest, ubuntu-latest ]
21-
node-version: [ 18.x ]
21+
dotnet-version: [ net472, net6.0, net8.0]
22+
node-version: [ 18.x, 20.x ]
2223
configuration: [ Release ]
23-
fail-fast: false # Don't cancel other jobs when one job fails
24+
exclude:
25+
# Exclude Node 18.x on .NET < 8, to thin the matrix.
26+
- dotnet-version: net6.0
27+
node-version: 18.x
28+
- dotnet-version: net472
29+
node-version: 18.x
30+
# Exclude .NET 4.x on non-Windows OS.
31+
- os: macos-latest
32+
dotnet-version: net472
33+
- os: ubuntu-latest
34+
dotnet-version: net472
2435

2536
runs-on: ubuntu-latest
2637

2738
steps:
28-
- name: Publish test results (${{ matrix.os }}, node${{ matrix.node-version }}, ${{ matrix.configuration }})
39+
- name: Publish test results
2940
uses: dorny/test-reporter@v1
3041
with:
31-
artifact: test-logs-${{ matrix.os }}-node${{ matrix.node-version }}-${{ matrix.configuration }}
32-
name: test results (${{ matrix.os }}, node${{ matrix.node-version }}, ${{ matrix.configuration }})
42+
artifact: test-logs-${{ matrix.os }}-${{matrix.dotnet-version}}-node${{matrix.node-version}}-${{matrix.configuration}}
43+
name: test results (${{ matrix.os }}, ${{matrix.dotnet-version}}, node${{ matrix.node-version }}, ${{ matrix.configuration }})
3344
path: test/**/*.trx
3445
reporter: dotnet-trx

Directory.Packages.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<ItemGroup>
66
<PackageVersion Include="BenchmarkDotNet" Version="0.13.5" />
77
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="7.0.0" />
8-
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
8+
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.0" /><!-- 4.3.0 is compatible with .NET 6 -->
99
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
1010
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
1111
<PackageVersion Include="Nerdbank.GitVersioning" Version="3.5.119" />
@@ -16,4 +16,4 @@
1616
<PackageVersion Include="xunit.runner.visualstudio" Version="2.4.5" />
1717
<PackageVersion Include="Xunit.SkippableFact" Version="1.4.13" />
1818
</ItemGroup>
19-
</Project>
19+
</Project>

bench/Directory.Build.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
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. -->
44
<PropertyGroup>
5-
<TargetFrameworks Condition=" '$(TargetFrameworks)' == '' and ! $([MSBuild]::IsOsPlatform('Windows')) ">net8.0</TargetFrameworks>
6-
<TargetFrameworks Condition=" '$(TargetFrameworks)' == '' and $([MSBuild]::IsOsPlatform('Windows')) ">net8.0;net472</TargetFrameworks>
5+
<TargetFrameworks Condition=" '$(TargetFrameworks)' == '' and ! $([MSBuild]::IsOsPlatform('Windows')) ">net8.0;net6.0</TargetFrameworks>
6+
<TargetFrameworks Condition=" '$(TargetFrameworks)' == '' and $([MSBuild]::IsOsPlatform('Windows')) ">net8.0;net6.0;net472</TargetFrameworks>
77
<LangVersion>12</LangVersion>
88
<Nullable>enable</Nullable>
99
<PackRelease>false</PackRelease>

src/NodeApi.DotNetHost/NodeApi.DotNetHost.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@
3434
<NodeApiPackScript>$(MSBuildThisFileDirectory)..\node-api-dotnet\pack.js</NodeApiPackScript>
3535
<RuntimeIdentifierList Condition=" '$(RuntimeIdentifierList)' == '' ">$(RuntimeIdentifier)</RuntimeIdentifierList>
3636
</PropertyGroup>
37-
<Message Importance="High" Text="node $(NodeApiPackScript) $(Configuration) $(RuntimeIdentifierList)" />
38-
<Exec Command="node $(NodeApiPackScript) $(Configuration) $(RuntimeIdentifierList)" />
37+
<Message Importance="High" Text="node $(NodeApiPackScript) node-api-dotnet $(Configuration) $(RuntimeIdentifierList)" />
38+
<Exec Command="node $(NodeApiPackScript) node-api-dotnet $(Configuration) $(RuntimeIdentifierList)" />
3939
</Target>
4040

4141
</Project>

src/NodeApi.DotNetHost/TypeExporter.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ public void ExportAssemblyTypes(Assembly assembly, JSObject exports)
127127
TypeProxy typeProxy = new(parentNamespace, nestedType);
128128
parentNamespace.Types.Add(nestedTypeName, typeProxy);
129129
typeProxies.Add(typeProxy);
130-
Trace($" {parentNamespace}.{typeName}");
130+
Trace($" {parentNamespace}.{nestedTypeName}");
131131
count++;
132132
}
133133
}
@@ -199,7 +199,6 @@ private void ExportExtensionMethod(MethodInfo extensionMethod)
199199
}
200200

201201
string targetTypeName = TypeProxy.GetTypeProxyName(targetType);
202-
Trace($" +{targetTypeName}.{extensionMethod.Name}()");
203202

204203
// Target namespaces and types should be already loaded because either they are in the
205204
// current assembly (where types are loaded before extension methods) or in an assembly

src/NodeApi.Generator/NodeApi.Generator.csproj

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
1010
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
1111
<SelfContained>false</SelfContained>
12+
<RollForward>major</RollForward><!-- Enable running on .NET 6 or any later major version. -->
1213
</PropertyGroup>
1314

1415
<ItemGroup>
@@ -56,4 +57,16 @@
5657
</ItemGroup>
5758
</Target>
5859

60+
<Target Name="NpmPack"
61+
AfterTargets="Pack"
62+
Outputs="$(PackageOutputPath)node-api-dotnet-generator-$(PackageVersion).tgz"
63+
>
64+
<PropertyGroup>
65+
<NodeApiPackScript>$(MSBuildThisFileDirectory)..\node-api-dotnet\pack.js</NodeApiPackScript>
66+
<RuntimeIdentifierList Condition=" '$(RuntimeIdentifierList)' == '' ">$(RuntimeIdentifier)</RuntimeIdentifierList>
67+
</PropertyGroup>
68+
<Message Importance="High" Text="node $(NodeApiPackScript) node-api-dotnet-generator $(Configuration) $(RuntimeIdentifierList)" />
69+
<Exec Command="node $(NodeApiPackScript) node-api-dotnet-generator $(Configuration) $(RuntimeIdentifierList)" />
70+
</Target>
71+
5972
</Project>

src/NodeApi/JSError.cs

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -239,27 +239,34 @@ public static void ThrowError(Exception exception)
239239
JSValue error = (exception as JSException)?.Error?.Value ??
240240
JSValue.CreateError(code: null, (JSValue)message);
241241

242-
// When running on V8, the `Error.captureStackTrace()` function and `Error.stack` property
243-
// can be used to add the .NET stack info to the JS error stack.
244-
JSValue captureStackTrace = JSValue.Global["Error"]["captureStackTrace"];
245-
if (captureStackTrace.IsFunction())
242+
// A no-context scope is used when initializing the host. In that case, do not attempt
243+
// to override the stack property, because if initialization fails the scope may not
244+
// be available for the stack callback.
245+
if (JSValueScope.Current.ScopeType != JSValueScopeType.NoContext)
246246
{
247-
// Capture the stack trace of the .NET exception, which will be combined with
248-
// the JS stack trace when requested.
249-
JSValue dotnetStack = exception.StackTrace?.Replace("\r", string.Empty) ?? string.Empty;
250-
251-
// Capture the current JS stack trace as an object.
252-
// Defer formatting the stack as a string until requested.
253-
JSObject jsStack = new();
254-
captureStackTrace.Call(default, jsStack);
255-
256-
// Override the `stack` property of the JS Error object, and add private
257-
// properties that the overridden property getter uses to construct the stack.
258-
error.DefineProperties(
259-
JSPropertyDescriptor.Accessor(
260-
"stack", GetErrorStack, setter: null, JSPropertyAttributes.DefaultProperty),
261-
JSPropertyDescriptor.ForValue("__dotnetStack", dotnetStack),
262-
JSPropertyDescriptor.ForValue("__jsStack", jsStack));
247+
// When running on V8, the `Error.captureStackTrace()` function and `Error.stack`
248+
// property can be used to add the .NET stack info to the JS error stack.
249+
JSValue captureStackTrace = JSValue.Global["Error"]["captureStackTrace"];
250+
if (captureStackTrace.IsFunction())
251+
{
252+
// Capture the stack trace of the .NET exception, which will be combined with
253+
// the JS stack trace when requested.
254+
JSValue dotnetStack = exception.StackTrace?.Replace("\r", string.Empty)
255+
?? string.Empty;
256+
257+
// Capture the current JS stack trace as an object.
258+
// Defer formatting the stack as a string until requested.
259+
JSObject jsStack = new();
260+
captureStackTrace.Call(default, jsStack);
261+
262+
// Override the `stack` property of the JS Error object, and add private
263+
// properties that the overridden property getter uses to construct the stack.
264+
error.DefineProperties(
265+
JSPropertyDescriptor.Accessor(
266+
"stack", GetErrorStack, setter: null, JSPropertyAttributes.DefaultProperty),
267+
JSPropertyDescriptor.ForValue("__dotnetStack", dotnetStack),
268+
JSPropertyDescriptor.ForValue("__jsStack", jsStack));
269+
}
263270
}
264271

265272
napi_status status = error.Scope.Runtime.Throw(

src/NodeApi/JSObject.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,19 @@ public JSObject() : this(JSValue.CreateObject())
2424
{
2525
}
2626

27+
public JSObject(IEnumerable<KeyValuePair<JSValue, JSValue>> properties) : this()
28+
{
29+
foreach (KeyValuePair<JSValue, JSValue> property in properties)
30+
{
31+
_value.SetProperty(property.Key, property.Value);
32+
}
33+
}
34+
35+
public JSObject(params KeyValuePair<JSValue, JSValue>[] properties)
36+
: this((IEnumerable<KeyValuePair<JSValue, JSValue>>)properties)
37+
{
38+
}
39+
2740
int ICollection<KeyValuePair<JSValue, JSValue>>.Count
2841
=> _value.GetPropertyNames().GetArrayLength();
2942

src/node-api-dotnet/pack.js

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ if (nodeMajorVersion < 16) {
77
process.exit(1);
88
}
99

10+
const packageName = process.argv[2];
1011
const configuration = ['Debug', 'Release'].find(
11-
(c) => c.toLowerCase() == (process.argv[2] ?? '').toLowerCase());
12-
const rids = process.argv.slice(3);
12+
(c) => c.toLowerCase() == (process.argv[3] ?? '').toLowerCase());
13+
const rids = process.argv.slice(4);
1314

14-
if (!configuration || rids.length === 0) {
15-
console.error('Usage: node pack.js Debug|Release rids...');
15+
if (!packageName || !configuration || rids.length === 0) {
16+
console.error('Missing command arguments.');
17+
console.error('Usage: node pack.js package-name Debug|Release rids...');
1618
process.exit(1);
1719
}
1820

@@ -32,9 +34,15 @@ const outPkgDir = path.resolve(__dirname, '../../out/pkg');
3234

3335
if (!fs.existsSync(outPkgDir)) fs.mkdirSync(outPkgDir);
3436

35-
packMainPackage();
36-
console.log();
37-
packGeneratorPackage();
37+
if (packageName === 'node-api-dotnet') {
38+
packMainPackage();
39+
} else if (packageName === 'node-api-dotnet-generator') {
40+
packGeneratorPackage();
41+
} else {
42+
console.error('Invalid package name.');
43+
console.error('Usage: node pack.js package-name Debug|Release rids...');
44+
process.exit(1);
45+
}
3846

3947
function packMainPackage() {
4048
const packageJson = require('./package.json');

0 commit comments

Comments
 (0)