Skip to content

Commit

Permalink
[native_toolchain_c] Support Clang on Windows
Browse files Browse the repository at this point in the history
  • Loading branch information
dcharkes committed Jan 14, 2025
1 parent f179ec2 commit 0d59319
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 75 deletions.
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
22 changes: 16 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,6 +2,8 @@
// 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';

Expand All @@ -14,10 +16,18 @@ 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'),
),
InstallLocationResolver(
toolName: 'Clang',
paths: [
'C:/Program Files/LLVM/bin/clang.exe',
],
),
]),
),
),
);
Expand All @@ -32,7 +42,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 +58,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,6 +53,7 @@ void main() {
});

const dumpbinMachine = {
Architecture.arm64: 'ARM64',
Architecture.ia32: 'x86',
Architecture.x64: 'x64',
};
Expand All @@ -48,74 +66,79 @@ void main() {
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;
test(
'CBuilder ${compiler.name} $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: 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.release,
);
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]));
});
}
}
}
}
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

0 comments on commit 0d59319

Please sign in to comment.