blob: 48b400e3ae451ba86f53faa112b6ad35a5f9dca8 [file] [log] [blame] [edit]
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// 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 'dart:io';
import 'dart:ffi';
final repoRoot = File.fromUri(Platform.script).parent.parent.parent.path;
final runtimeRoot = '$repoRoot/runtime';
final buildtoolsRoot = '$repoRoot/buildtools';
final clangBinDir =
'$buildtoolsRoot/$currentPlatformBuildtoolsSubdir/clang/bin';
final clangFormatBin = '$clangBinDir/clang-format';
final currentPlatformBuildtoolsSubdir = switch (Abi.current()) {
Abi.macosX64 => 'mac-x64',
Abi.macosArm64 => 'mac-arm64',
Abi.linuxX64 => 'linux-x64',
Abi.linuxArm64 => 'linux-arm64',
Abi.windowsX64 => 'win-x64',
Abi.windowsArm64 => 'win-arm64',
_ => throw UnimplementedError(),
};
void main(List<String> args) {
final checkUpToDate = args.contains('--check-up-to-date');
final dartApiHFile = File('$runtimeRoot/include/dart_api.h');
final dartNativeApiHFile = File('$runtimeRoot/include/dart_native_api.h');
final dartApiWinCFile = File('$runtimeRoot/bin/dart_api_win.c');
final dartApiWinCTmpFile = File('$runtimeRoot/bin/dart_api_win_tmp.c');
final dartApiHContents = dartApiHFile.readAsStringSync();
final dartNativeApiHContents = dartNativeApiHFile.readAsStringSync();
final procedureRegexp = RegExp(
r'(DART_\w+\s+)+(?<returnType>[\w\s\*]+)\s+(?<name>\w+)\((?<arguments>[^)]*)\);',
multiLine: true,
);
final matches = [
...procedureRegexp.allMatches(dartApiHContents),
...procedureRegexp.allMatches(dartNativeApiHContents),
];
final procedures = <Procedure>[];
for (final match in matches) {
final returnType = match.namedGroup('returnType')!;
final name = match.namedGroup('name')!;
final argumentsString = match.namedGroup('arguments') ?? '';
final argumentList = argumentsString
.split(',')
.where((arg) => arg != 'void')
.map((arg) {
final parts = arg.trim().split(' ');
return (
type: parts.sublist(0, parts.length - 1).join(' '),
name: parts[parts.length - 1],
);
})
.toList();
procedures.add((
name: name,
returnType: returnType,
arguments: argumentList,
));
}
final buffer = StringBuffer();
buffer.writeln('''
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// 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.
// DO NOT EDIT. This file is generated by runtime/tools/generate_dart_api_win_c.dart.
#include <windows.h>
#include <include/dart_api.h>
#include <include/dart_native_api.h>
''');
// Generate typedefs for all procedures.
for (final procedure in procedures) {
buffer.write('typedef ');
buffer.write(procedure.returnType);
buffer.write(' (*');
buffer.write(procedure.typedefName);
buffer.write(')(');
for (final (i, argument) in procedure.arguments.indexed) {
buffer.write(argument.type);
if (i < procedure.arguments.length - 1) {
buffer.write(', ');
}
}
buffer.writeln(');');
}
buffer.writeln();
// Generate function pointers for all procedures.
for (final procedure in procedures) {
buffer.write('static ');
buffer.write(procedure.typedefName);
buffer.write(' ');
buffer.write(procedure.functionPointerName);
buffer.writeln(' = NULL;');
}
buffer.writeln();
// Generate the DllMain function that initializes all function pointers.
buffer.writeln('''
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
if (fdwReason == DLL_PROCESS_ATTACH) {
HMODULE process = GetModuleHandle(NULL);
''');
for (final procedure in procedures) {
buffer.write(' ');
buffer.write(procedure.functionPointerName);
buffer.write(' = (');
buffer.write(procedure.typedefName);
buffer.write(') GetProcAddress(process, "');
buffer.write(procedure.name);
buffer.writeln('");');
}
buffer.writeln('''
}
return TRUE;
}''');
buffer.writeln();
// Generate redirecting implementations for all procedures.
for (final procedure in procedures) {
final (:name, :returnType, :arguments) = procedure;
buffer.write(returnType);
buffer.write(' ');
buffer.write(name);
buffer.write('(');
for (final (i, (:name, :type)) in arguments.indexed) {
buffer.write(type);
buffer.write(' ');
buffer.write(name);
if (i < arguments.length - 1) {
buffer.write(', ');
}
}
buffer.writeln(') {');
buffer.write(' ');
if (procedure.returnType != 'void') {
buffer.write('return ');
}
buffer.write(procedure.functionPointerName);
buffer.write('(');
for (final (i, argument) in arguments.indexed) {
buffer.write(argument.name);
if (i < arguments.length - 1) {
buffer.write(', ');
}
}
buffer.writeln(');');
buffer.writeln('}');
buffer.writeln();
}
buffer.writeln();
dartApiWinCTmpFile.writeAsStringSync(buffer.toString());
try {
// Run clang-format on the generated file.
final clangFormatResult = Process.runSync(
clangFormatBin,
['-i', dartApiWinCTmpFile.path],
// Allows us to specify the path to the clang-format binary without the
// .exe extension on Windows.
runInShell: Platform.isWindows,
);
if (clangFormatResult.exitCode != 0) {
print(clangFormatResult.stdout);
print(clangFormatResult.stderr);
exitCode = 1;
} else {
final changed =
!dartApiWinCFile.existsSync() ||
dartApiWinCTmpFile.readAsStringSync() !=
dartApiWinCFile.readAsStringSync();
if (changed) {
if (checkUpToDate) {
exitCode = 1;
} else {
dartApiWinCTmpFile.copySync(dartApiWinCFile.path);
}
}
}
} finally {
dartApiWinCTmpFile.deleteSync();
}
}
typedef Procedure = ({
String name,
String returnType,
List<({String name, String type})> arguments,
});
extension on Procedure {
String get typedefName => '${name}Type';
String get functionPointerName => '${name}Fn';
}