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

[native_toolchain_c] Support Clang on Windows #1893

Merged
merged 3 commits into from
Jan 17, 2025
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
11 changes: 10 additions & 1 deletion pkgs/native_toolchain_c/lib/src/cbuilder/run_cbuilder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@ class RunCBuilder {
'${targetAndroidNdkApi!}',
'--sysroot=${androidSysroot(toolInstance).toFilePath()}',
],
if (codeConfig.targetOS == OS.windows)
'--target=${clangWindowsTargetFlags[architecture]!}',
if (codeConfig.targetOS == OS.macOS)
'--target=${appleClangMacosTargetFlags[architecture]!}',
if (codeConfig.targetOS == OS.iOS)
Expand All @@ -244,7 +246,8 @@ class RunCBuilder {
installName!.toFilePath(),
],
if (pic != null)
if (toolInstance.tool.isClangLike) ...[
if (toolInstance.tool.isClangLike &&
codeConfig.targetOS != OS.windows) ...[
if (pic!) ...[
if (dynamicLibrary != null) '-fPIC',
// Using PIC for static libraries allows them to be linked into
Expand Down Expand Up @@ -419,6 +422,12 @@ class RunCBuilder {
},
};

static const clangWindowsTargetFlags = {
Architecture.arm64: 'arm64-pc-windows-msvc',
Architecture.ia32: 'i386-pc-windows-msvc',
Architecture.x64: 'x86_64-pc-windows-msvc',
};

static const defaultCppLinkStdLib = {
OS.android: 'c++_shared',
OS.fuchsia: 'c++',
Expand Down
28 changes: 22 additions & 6 deletions pkgs/native_toolchain_c/lib/src/native_toolchain/clang.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:native_assets_cli/code_assets.dart';

import '../tool/tool.dart';
import '../tool/tool_resolver.dart';
import 'msvc.dart';

/// The Clang compiler.
///
Expand All @@ -14,10 +17,23 @@ final Tool clang = Tool(
wrappedResolver: CliFilter(
cliArguments: ['--version'],
keepIf: ({required String stdout}) => !stdout.contains('Apple clang'),
wrappedResolver: PathToolResolver(
toolName: 'Clang',
executableName: 'clang',
),
wrappedResolver: ToolResolvers([
PathToolResolver(
toolName: 'Clang',
executableName: OS.current.executableFileName('clang'),
),
RelativeToolResolver(
toolName: 'Clang',
wrappedResolver: visualStudio.defaultResolver!,
relativePath: Uri(path: './VC/Tools/Llvm/bin/clang.exe'),
),
InstallLocationResolver(
toolName: 'Clang',
paths: [
'C:/Program Files/LLVM/bin/clang.exe',
],
),
]),
),
),
);
Expand All @@ -32,7 +48,7 @@ final Tool llvmAr = Tool(
RelativeToolResolver(
toolName: 'LLVM archiver',
wrappedResolver: clang.defaultResolver!,
relativePath: Uri.file('llvm-ar'),
relativePath: Uri.file(OS.current.executableFileName('llvm-ar')),
),
]),
),
Expand All @@ -48,7 +64,7 @@ final Tool lld = Tool(
RelativeToolResolver(
toolName: 'LLD',
wrappedResolver: clang.defaultResolver!,
relativePath: Uri.file('ld.lld'),
relativePath: Uri.file(OS.current.executableFileName('ld.lld')),
),
]),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,36 @@ library;
import 'dart:io';

import 'package:native_toolchain_c/native_toolchain_c.dart';
import 'package:native_toolchain_c/src/native_toolchain/clang.dart';
import 'package:native_toolchain_c/src/native_toolchain/msvc.dart';
import 'package:native_toolchain_c/src/utils/run_process.dart';
import 'package:test/test.dart';

import '../helpers.dart';

void main() {
void main() async {
if (!Platform.isWindows) {
// Avoid needing status files on Dart SDK CI.
return;
}

final compilers = {
// Either provided to be MSVC or null which defaults to MSVC.
msvc: () async => cCompiler,
// Clang on Windows.
clang: () async => CCompilerConfig(
archiver:
(await llvmAr.defaultResolver!.resolve(logger: logger)).first.uri,
compiler:
(await clang.defaultResolver!.resolve(logger: logger)).first.uri,
linker:
(await lld.defaultResolver!.resolve(logger: logger)).first.uri,
)
};

const targets = [
// TODO(/~https://github.com/dart-lang/native/issues/170): Support arm64.
// Architecture.arm64,
Architecture.ia32,
Architecture.x64,
];
Expand All @@ -36,86 +53,95 @@ void main() {
});

const dumpbinMachine = {
Architecture.arm64: 'ARM64',
Architecture.ia32: 'x86',
Architecture.x64: 'x64',
};

const optimizationLevels = OptimizationLevel.values;
var selectOptimizationLevel = 0;
var selectBuildMode = 0;

final dumpbinFileType = {
DynamicLoadingBundled(): 'DLL',
StaticLinking(): 'LIBRARY',
};

for (final linkMode in [DynamicLoadingBundled(), StaticLinking()]) {
for (final target in targets) {
// Cycle through all optimization levels.
final optimizationLevel = optimizationLevels[selectOptimizationLevel];
selectOptimizationLevel =
(selectOptimizationLevel + 1) % optimizationLevels.length;
test('CBuilder $linkMode library $target $optimizationLevel', () async {
final tempUri = await tempDirForTest();
final tempUri2 = await tempDirForTest();
final addCUri =
packageUri.resolve('test/cbuilder/testfiles/add/src/add.c');
const name = 'add';

final buildInputBuilder = BuildInputBuilder()
..setupShared(
packageName: name,
packageRoot: tempUri,
outputFile: tempUri.resolve('output.json'),
outputDirectory: tempUri,
outputDirectoryShared: tempUri2,
)
..config.setupBuild(
linkingEnabled: false,
dryRun: false,
)
..config.setupShared(buildAssetTypes: [CodeAsset.type])
..config.setupCode(
targetOS: OS.windows,
targetArchitecture: target,
linkModePreference: linkMode == DynamicLoadingBundled()
? LinkModePreference.dynamic
: LinkModePreference.static,
cCompiler: cCompiler,
for (final compiler in compilers.keys) {
for (final linkMode in [DynamicLoadingBundled(), StaticLinking()]) {
for (final target in targets) {
// Cycle through all optimization levels.
final optimizationLevel = optimizationLevels[selectOptimizationLevel];
selectOptimizationLevel =
(selectOptimizationLevel + 1) % optimizationLevels.length;
final buildMode = BuildMode.values[selectBuildMode];
selectBuildMode = (selectBuildMode + 1) % BuildMode.values.length;
Copy link
Member

Choose a reason for hiding this comment

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

Is this some kind of fuzzy testing?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Heh, it would be fuzz testing if we would randomize the params and run it nightly. Now it's a poor mans combination of trying to cover some combination of params 😄

test(
'CBuilder ${compiler.name} $linkMode library $target'
' $optimizationLevel $buildMode', () async {
final tempUri = await tempDirForTest();
final tempUri2 = await tempDirForTest();
final addCUri =
packageUri.resolve('test/cbuilder/testfiles/add/src/add.c');
const name = 'add';

final buildInputBuilder = BuildInputBuilder()
..setupShared(
packageName: name,
packageRoot: tempUri,
outputFile: tempUri.resolve('output.json'),
outputDirectory: tempUri,
outputDirectoryShared: tempUri2,
)
..config.setupBuild(
linkingEnabled: false,
dryRun: false,
)
..config.setupShared(buildAssetTypes: [CodeAsset.type])
..config.setupCode(
targetOS: OS.windows,
targetArchitecture: target,
linkModePreference: linkMode == DynamicLoadingBundled()
? LinkModePreference.dynamic
: LinkModePreference.static,
cCompiler: await (compilers[compiler]!)(),
);

final buildInput = BuildInput(buildInputBuilder.json);
final buildOutput = BuildOutputBuilder();

final cbuilder = CBuilder.library(
name: name,
assetName: name,
sources: [addCUri.toFilePath()],
optimizationLevel: optimizationLevel,
buildMode: buildMode,
);
await cbuilder.run(
input: buildInput,
output: buildOutput,
logger: logger,
);

final buildInput = BuildInput(buildInputBuilder.json);
final buildOutput = BuildOutputBuilder();

final cbuilder = CBuilder.library(
name: name,
assetName: name,
sources: [addCUri.toFilePath()],
optimizationLevel: optimizationLevel,
buildMode: BuildMode.release,
);
await cbuilder.run(
input: buildInput,
output: buildOutput,
logger: logger,
);

final libUri =
tempUri.resolve(OS.windows.libraryFileName(name, linkMode));
expect(await File.fromUri(libUri).exists(), true);
final result = await runProcess(
executable: dumpbinUri,
arguments: ['/HEADERS', libUri.toFilePath()],
logger: logger,
);
expect(result.exitCode, 0);
final machine =
result.stdout.split('\n').firstWhere((e) => e.contains('machine'));
expect(machine, contains(dumpbinMachine[target]));
final fileType = result.stdout
.split('\n')
.firstWhere((e) => e.contains('File Type'));
expect(fileType, contains(dumpbinFileType[linkMode]));
});
final libUri =
tempUri.resolve(OS.windows.libraryFileName(name, linkMode));
expect(await File.fromUri(libUri).exists(), true);
final result = await runProcess(
executable: dumpbinUri,
arguments: ['/HEADERS', libUri.toFilePath()],
logger: logger,
);
expect(result.exitCode, 0);
final machine = result.stdout
.split('\n')
.firstWhere((e) => e.contains('machine'));
expect(machine, contains(dumpbinMachine[target]));
final fileType = result.stdout
.split('\n')
.firstWhere((e) => e.contains('File Type'));
expect(fileType, contains(dumpbinFileType[linkMode]));
});
}
}
}
}
7 changes: 7 additions & 0 deletions pkgs/native_toolchain_c/test/cbuilder/testfiles/add/src/add.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

#ifdef DEBUG
#include <stdio.h>

#if _WIN32
#include <wchar.h>
#endif
#endif

#if _WIN32
Expand All @@ -17,6 +21,9 @@
FFI_EXPORT int32_t add(int32_t a, int32_t b) {
#ifdef DEBUG
printf("Adding %i and %i.\n", a, b);
#if _WIN32
wprintf("Adding %i and %i.\n", a, b);
#endif
#endif
return a + b;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ import 'package:test/test.dart';
import '../helpers.dart';

void main() {
if (!Platform.isLinux) {
if (Platform.isMacOS ||
(Platform.isWindows &&
Platform.environment['DART_HOOK_TESTING_C_COMPILER__CC']
?.endsWith('cl.exe') ==
true)) {
// Avoid needing status files on Dart SDK CI.
return;
}
Expand Down
Loading