Skip to content

Commit 3b66a14

Browse files
authored
[native_toolchain_c] Add Linking for Windows (#2367)
1 parent b0e8676 commit 3b66a14

File tree

15 files changed

+144
-147
lines changed

15 files changed

+144
-147
lines changed

pkgs/native_toolchain_c/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.16.6
2+
3+
* Support linking for Windows.
4+
15
## 0.16.5
26

37
* Support linking for iOS.

pkgs/native_toolchain_c/lib/src/cbuilder/clinker.dart

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ import 'output_type.dart';
1818
import 'run_cbuilder.dart';
1919

2020
/// Specification for linking an artifact with a C linker.
21-
//TODO(mosuem): This is currently only implemented for linux.
22-
// See also https://github.com/dart-lang/native/issues/1376
2321
class CLinker extends CTool implements Linker {
2422
final LinkerOptions linkerOptions;
2523

@@ -53,14 +51,6 @@ class CLinker extends CTool implements Linker {
5351
required LinkOutputBuilder output,
5452
required Logger? logger,
5553
}) async {
56-
const supportedTargetOSs = [OS.linux, OS.android, OS.macOS, OS.iOS];
57-
if (!supportedTargetOSs.contains(input.config.code.targetOS)) {
58-
throw UnsupportedError(
59-
'This feature is only supported when targeting '
60-
'${supportedTargetOSs.join(', ')}. '
61-
'See also https://github.com/dart-lang/native/issues/1376',
62-
);
63-
}
6454
final outDir = input.outputDirectory;
6555
final packageRoot = input.packageRoot;
6656
await Directory.fromUri(outDir).create(recursive: true);

pkgs/native_toolchain_c/lib/src/cbuilder/linker_options.dart

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:io';
66

77
import 'package:code_assets/code_assets.dart';
88

9+
import '../native_toolchain/msvc.dart';
910
import '../native_toolchain/tool_likeness.dart';
1011
import '../tool/tool.dart';
1112

@@ -94,15 +95,35 @@ extension LinkerOptionsExt on LinkerOptions {
9495
Tool tool,
9596
Iterable<String> sourceFiles,
9697
OS targetOS,
98+
Architecture targetArchitecture,
9799
) {
98-
final includeAllSymbols = _symbolsToKeep == null;
100+
if (tool.isClangLike || tool.isLdLike) {
101+
return _sourceFilesToFlagsForClangLike(tool, sourceFiles, targetOS);
102+
} else if (tool == cl) {
103+
return _sourceFilesToFlagsForCl(
104+
tool,
105+
sourceFiles,
106+
targetOS,
107+
targetArchitecture,
108+
);
109+
} else {
110+
throw UnimplementedError('This package does not know how to run $tool.');
111+
}
112+
}
99113

114+
bool get _includeAllSymbols => _symbolsToKeep == null;
115+
116+
Iterable<String> _sourceFilesToFlagsForClangLike(
117+
Tool tool,
118+
Iterable<String> sourceFiles,
119+
OS targetOS,
120+
) {
100121
switch (targetOS) {
101122
case OS.macOS || OS.iOS:
102123
return [
103-
if (!includeAllSymbols) ...sourceFiles,
124+
if (!_includeAllSymbols) ...sourceFiles,
104125
..._toLinkerSyntax(tool, [
105-
if (includeAllSymbols) ...sourceFiles.map((e) => '-force_load,$e'),
126+
if (_includeAllSymbols) ...sourceFiles.map((e) => '-force_load,$e'),
106127
..._linkerFlags,
107128
..._symbolsToKeep?.map((symbol) => '-u,_$symbol') ?? [],
108129
if (stripDebug) '-S',
@@ -113,7 +134,7 @@ extension LinkerOptionsExt on LinkerOptions {
113134
case OS.android || OS.linux:
114135
final wholeArchiveSandwich =
115136
sourceFiles.any((source) => source.endsWith('.a')) ||
116-
includeAllSymbols;
137+
_includeAllSymbols;
117138
return [
118139
if (wholeArchiveSandwich)
119140
..._toLinkerSyntax(tool, ['--whole-archive']),
@@ -132,4 +153,23 @@ extension LinkerOptionsExt on LinkerOptions {
132153
throw UnimplementedError();
133154
}
134155
}
156+
157+
Iterable<String> _sourceFilesToFlagsForCl(
158+
Tool tool,
159+
Iterable<String> sourceFiles,
160+
OS targetOS,
161+
Architecture targetArch,
162+
) => [
163+
...sourceFiles,
164+
'/link',
165+
if (_includeAllSymbols) ...sourceFiles.map((e) => '/WHOLEARCHIVE:$e'),
166+
..._linkerFlags,
167+
..._symbolsToKeep?.map(
168+
(symbol) =>
169+
'/INCLUDE:${targetArch == Architecture.ia32 ? '_' : ''}$symbol',
170+
) ??
171+
[],
172+
if (stripDebug) '/PDBSTRIPPED',
173+
if (gcSections) '/OPT:REF',
174+
];
135175
}

pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ class RunCBuilder {
310310
toolInstance.tool,
311311
sourceFiles,
312312
codeConfig.targetOS,
313+
codeConfig.targetArchitecture,
313314
)
314315
else
315316
...sourceFiles,
@@ -353,6 +354,7 @@ class RunCBuilder {
353354
if (isStaticLib) {
354355
archiver_ = await archiver();
355356
}
357+
final sourceFiles = sources.map((e) => e.toFilePath());
356358

357359
final result = await runProcess(
358360
executable: tool.uri,
@@ -368,18 +370,25 @@ class RunCBuilder {
368370
for (final forcedInclude in forcedIncludes)
369371
'/FI${forcedInclude.toFilePath()}',
370372
if (executable != null) ...[
371-
...sources.map((e) => e.toFilePath()),
372-
'/link',
373-
'/out:${outDir.resolveUri(executable!).toFilePath()}',
373+
'/Fe:${outDir.resolveUri(executable!).toFilePath()}',
374374
] else if (dynamicLibrary != null) ...[
375-
...sources.map((e) => e.toFilePath()),
376-
'/link',
377-
'/DLL',
378-
'/out:${outDir.resolveUri(dynamicLibrary!).toFilePath()}',
375+
'/LD',
376+
'/Fe:${outDir.resolveUri(dynamicLibrary!).toFilePath()}',
379377
] else if (staticLibrary != null) ...[
380378
'/c',
381-
...sources.map((e) => e.toFilePath()),
382379
],
380+
if (linkerOptions != null)
381+
...linkerOptions!.sourceFilesToFlags(
382+
tool.tool,
383+
sourceFiles,
384+
codeConfig.targetOS,
385+
codeConfig.targetArchitecture,
386+
)
387+
else ...[
388+
...sourceFiles,
389+
'/link',
390+
],
391+
'/MACHINE:${clTargetFlags[codeConfig.targetArchitecture]}',
383392
if (executable != null || dynamicLibrary != null) ...[
384393
for (final directory in libraryDirectories)
385394
'/LIBPATH:${directory.toFilePath()}',
@@ -435,6 +444,12 @@ class RunCBuilder {
435444
Architecture.x64: 'x86_64-pc-windows-msvc',
436445
};
437446

447+
static const clTargetFlags = {
448+
Architecture.arm64: 'ARM64',
449+
Architecture.ia32: 'X86',
450+
Architecture.x64: 'X64',
451+
};
452+
438453
static const defaultCppLinkStdLib = {
439454
OS.android: 'c++_shared',
440455
OS.fuchsia: 'c++',

pkgs/native_toolchain_c/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: native_toolchain_c
22
description: >-
33
A library to invoke the native C compiler installed on the host machine.
4-
version: 0.16.5
4+
version: 0.16.6
55
repository: https://github.com/dart-lang/native/tree/main/pkgs/native_toolchain_c
66

77
topics:

pkgs/native_toolchain_c/test/clinker/objects_cross_test.dart

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,12 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
// TODO(mosuem): Enable for windows.
6-
// See https://github.com/dart-lang/native/issues/1376.
7-
@TestOn('linux || mac-os')
8-
library;
9-
10-
import 'dart:io';
11-
125
import 'package:code_assets/code_assets.dart';
13-
import 'package:test/test.dart';
146

157
import '../helpers.dart';
168
import 'objects_helper.dart';
179

1810
void main() {
19-
if (!Platform.isLinux && !Platform.isMacOS) {
20-
// Avoid needing status files on Dart SDK CI.
21-
return;
22-
}
23-
2411
final architectures = supportedArchitecturesFor(OS.current)
2512
..remove(Architecture.current); // See objects_test.dart for current arch.
2613

pkgs/native_toolchain_c/test/clinker/objects_helper.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ void runObjectsTests(
9595
final asset = codeAssets.first;
9696
expect(asset, isA<CodeAsset>());
9797
expect(
98-
await nmReadSymbols(asset, targetOS),
98+
await readSymbols(asset, targetOS),
9999
stringContainsInOrder(['my_func', 'my_other_func']),
100100
);
101101
});

pkgs/native_toolchain_c/test/clinker/objects_test.dart

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,12 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
// TODO(mosuem): Enable for windows.
6-
// See https://github.com/dart-lang/native/issues/1376.
7-
@TestOn('linux || mac-os')
8-
library;
9-
10-
import 'dart:io';
11-
125
import 'package:code_assets/code_assets.dart';
13-
import 'package:test/test.dart';
146

157
import '../helpers.dart';
168
import 'objects_helper.dart';
179

1810
void main() {
19-
if (!Platform.isLinux && !Platform.isMacOS) {
20-
// Avoid needing status files on Dart SDK CI.
21-
return;
22-
}
23-
2411
runObjectsTests(
2512
OS.current,
2613
[Architecture.current],
Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
#include <stdio.h>
22

3-
void my_func()
3+
#if _WIN32
4+
#define FFI_EXPORT __declspec(dllexport)
5+
#else
6+
#define FFI_EXPORT
7+
#endif
8+
9+
FFI_EXPORT void my_func()
410
{
5-
printf("42");
11+
// Using a long (512B) string here so that treeshaking this function actually affects binary size on disk.
12+
// On windows, treeshaking just a small string (<512B) out of a binary has no effect on raw size because of padding
13+
// and alignment (FileAlignment of sections defaults to 512B in a Windows DLL).
14+
printf(
15+
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis dui lacus, volutpat nec consequat at, hendrerit et "
16+
"lorem. Etiam porttitor velit massa, vitae aliquet elit bibendum quis. Vivamus vestibulum ligula ac nisl faucibus "
17+
"convallis. Morbi accumsan elit vel varius gravida. Ut nec interdum sapien, vitae mattis libero. Donec ac placerat "
18+
"lorem. Proin mattis mauris et felis consectetur, et tempor diam tincidunt. Nunc malesuada massa at lectus "
19+
"volutpat, luctus congue lectus facilisis. Vivamus in cursus velit."
20+
);
621
}
Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
#include <stdio.h>
22

3-
void my_other_func()
3+
#if _WIN32
4+
#define FFI_EXPORT __declspec(dllexport)
5+
#else
6+
#define FFI_EXPORT
7+
#endif
8+
9+
FFI_EXPORT void my_other_func()
410
{
5-
printf("42+1");
11+
printf("Hello World");
612
}

pkgs/native_toolchain_c/test/clinker/throws_test.dart

Lines changed: 0 additions & 59 deletions
This file was deleted.

pkgs/native_toolchain_c/test/clinker/treeshake_cross_test.dart

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,13 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
// TODO(mosuem): Enable for windows.
6-
// See https://github.com/dart-lang/native/issues/1376.
7-
@TestOn('linux || mac-os')
8-
library;
9-
10-
import 'dart:io';
11-
125
import 'package:code_assets/code_assets.dart';
136
import 'package:test/test.dart';
147

158
import '../helpers.dart';
169
import 'treeshake_helper.dart';
1710

1811
void main() {
19-
if (!Platform.isLinux && !Platform.isMacOS) {
20-
// Avoid needing status files on Dart SDK CI.
21-
return;
22-
}
23-
2412
final architectures = supportedArchitecturesFor(OS.current)
2513
..remove(Architecture.current); // See treeshake_test.dart for current arch.
2614
for (final architecture in architectures) {

pkgs/native_toolchain_c/test/clinker/treeshake_helper.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ void runTreeshakeTests(
125125
targetOS,
126126
);
127127

128-
final symbols = await nmReadSymbols(asset, targetOS);
128+
final symbols = await readSymbols(asset, targetOS);
129129
if (clinker.linker != linkerAutoEmpty) {
130130
expect(symbols, contains('my_other_func'));
131131
expect(symbols, isNot(contains('my_func')));

0 commit comments

Comments
 (0)