diff --git a/.github/workflows/win32_prebuilt_assets.yml b/.github/workflows/win32_prebuilt_assets.yml new file mode 100644 index 000000000..a9a7f5fb5 --- /dev/null +++ b/.github/workflows/win32_prebuilt_assets.yml @@ -0,0 +1,80 @@ +name: win32_prebuilt_assets + +permissions: + contents: write + +on: + pull_request: + branches: [main] + paths: + - .github/workflows/win32_prebuilt_assets.yml + - packages/win32/hook/** + - packages/win32/lib/** + - packages/win32/src/** + - packages/win32/tool/** + push: + tags: + - win32-prebuilt-assets-* + workflow_dispatch: + +jobs: + build: + runs-on: windows-latest + + defaults: + run: + working-directory: packages/win32 + + steps: + - name: 📚 Git Checkout + uses: actions/checkout@v6 + + - name: 🐦 Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: stable + cache: true + cache-key: flutter-:os:-:channel:-:version:-:arch:-:hash:-${{ hashFiles('**/pubspec.lock') }} + + - name: 📦 Install Dependencies + run: flutter pub get + + - name: 👷 Build Windows host + run: | + dart run tool/build.dart -owindows -aarm64 + dart run tool/build.dart -owindows -ax64 + + - name: 📡 Upload artifacts + uses: actions/upload-artifact@v7 + with: + name: windows-host + path: packages/win32/.dart_tool/win32/**/*.dll + if-no-files-found: error + + release: + needs: build + if: startsWith(github.ref, 'refs/tags/win32-prebuilt-assets') + runs-on: ubuntu-latest + + defaults: + run: + working-directory: packages/win32 + + steps: + - name: 📚 Git Checkout + uses: actions/checkout@v6 + + - name: ⬇️ Download assets + uses: actions/download-artifact@v8 + with: + merge-multiple: true + path: packages/win32/.dart_tool/win32/ + + - name: 🔍 Display structure of downloaded assets + run: ls -R .dart_tool/win32/ + + - name: 🚀 Create GitHub Release + uses: softprops/action-gh-release@v3 + with: + files: packages/win32/.dart_tool/win32/** + fail_on_unmatched_files: true diff --git a/packages/generator/bin/generator.dart b/packages/generator/bin/generator.dart index 24d0dc272..80608648f 100644 --- a/packages/generator/bin/generator.dart +++ b/packages/generator/bin/generator.dart @@ -272,18 +272,21 @@ void generateDynamicLibrary( writeToFile(filePath, buffer.toString()); } -/// Updates the two constants in `packages/win32/hook/build.dart`: +/// Updates the two constants in +/// `packages/win32/lib/src/hook_helpers/resources.g.dart`: /// - `dynamicLibraries`: all dynamic libraries projected from the Win32 API /// - `sources`: all dynamic libraries with wrappers preserving `GetLastError()` /// results /// /// These constants are used in the build process to determine which dynamic /// libraries to include as code assets. -void updateDynamicLibrariesConstant( +void updateResources( Set dynamicLibraries, Set wrappedLibraries, ) { final generatedBlock = StringBuffer() + ..writeln('// THIS FILE IS AUTOGENERATED. DO NOT EDIT THIS FILE DIRECTLY.') + ..writeln() ..writeln('const dynamicLibraries = {') ..writeAll(dynamicLibraries.map((l) => " '${l.name.toLowerCase()}',\n")) ..writeln('};') @@ -294,21 +297,11 @@ void updateDynamicLibrariesConstant( ) ..writeln('];'); - final buildFile = io.File( - io.Platform.script.resolve('../../win32/hook/build.dart').toFilePath(), - ); - - final content = buildFile.readAsStringSync(); - final insertionPoint = content.indexOf('const dynamicLibraries = {'); - if (insertionPoint == -1) { - throw StateError( - 'Could not find `const dynamicLibraries` anchor in ${buildFile.path}', - ); - } - - buildFile.writeAsStringSync( - content.substring(0, insertionPoint) + generatedBlock.toString(), - ); + io.File( + io.Platform.script + .resolve('../../win32/lib/src/hook_helpers/resources.g.dart') + .toFilePath(), + ).writeAsStringSync(generatedBlock.toString()); } /// Generates a Dart file that exports all the dynamic libraries projected from @@ -594,7 +587,7 @@ void generateFunctions() { ); } - updateDynamicLibrariesConstant(dynamicLibraries, wrappedLibraries); + updateResources(dynamicLibraries, wrappedLibraries); generateDynamicLibraryExports(dynamicLibraries); logger.info('🚀 Total functions generated: ${functions.length}'); } diff --git a/packages/win32/hook/build.dart b/packages/win32/hook/build.dart index 741ae9f67..acf2fdebe 100644 --- a/packages/win32/hook/build.dart +++ b/packages/win32/hook/build.dart @@ -2,175 +2,47 @@ import 'dart:io'; import 'package:code_assets/code_assets.dart'; import 'package:hooks/hooks.dart'; -import 'package:logging/logging.dart'; -import 'package:native_toolchain_c/native_toolchain_c.dart'; - -const win32ImportLibraries = [ - 'Advapi32', - 'Bthprops', - 'Crypt32', - 'Dbghelp', - 'Dxva2', - 'Gdi32', - 'Kernel32', - 'KtmW32', - 'Magnification', - 'ole32', - 'OleAut32', - 'Psapi', - 'runtimeobject', - 'Setupapi', - 'Shell32', - 'User32', - 'Version', - 'Wevtapi', - 'Winscard', - 'Winspool', - 'Wlanapi', - 'Ws2_32', - 'Wtsapi32', -]; - -Logger createDefaultLogger() { - final logger = Logger.detached('CBuilder') - ..level = .INFO - ..onRecord.listen((record) { - if (record.message.contains( - 'Microsoft (R) C/C++ Optimizing Compiler Version', - )) { - // Skip logging compiler version info. - return; - } - - if (record.message.contains('Copyright (C) Microsoft Corporation.')) { - // Skip logging copyright info. - return; - } - - if (record.level >= .WARNING) { - stderr.writeln(record.message); - } else { - stdout.writeln(record.message); - } - if (record.error != null) { - stderr.writeln(record.error); - } - if (record.stackTrace != null) { - stderr.writeln(record.stackTrace); - } - }); - return logger; -} +import 'package:win32/src/hook_helpers/c_build.dart'; +import 'package:win32/src/hook_helpers/download.dart'; +import 'package:win32/src/hook_helpers/hashes.g.dart'; void main(List args) async { await build(args, (input, output) async { if (!input.config.buildCodeAssets) return; if (input.config.code.targetOS != .windows) return; - final packageName = input.packageName; - final cbuilder = CBuilder.library( - name: packageName, - assetName: '$packageName.dart', - sources: sources, - libraries: win32ImportLibraries, - flags: const ['/nologo'], - ); - - String normalizeDynamicLibraryName(String library) => - library.replaceAll('-', '_').split('.').first; - - CodeAsset createCodeAsset(String library) => .new( - package: packageName, - name: 'src/win32/${normalizeDynamicLibraryName(library)}.g.dart', - linkMode: DynamicLoadingSystem(.file(library)), - ); - - output.assets.code.addAll(dynamicLibraries.map(createCodeAsset)); - - await cbuilder.run( - input: input, - output: output, - logger: createDefaultLogger(), - ); + addDynamicLibrariesAsCodeAssets(output); + + final localBuild = input.userDefines['local_build'] as bool? ?? false; + if (localBuild) { + await runBuild(input, output); + } else { + final CodeConfig(:targetOS, :targetArchitecture) = input.config.code; + final outputDirectory = Directory.fromUri(input.outputDirectory); + final file = await downloadAsset( + targetOS, + targetArchitecture, + outputDirectory, + ); + final fileHash = await hashAsset(file); + final expectedHash = + assetHashes[targetOS.dylibFileName( + createTargetName(targetOS, targetArchitecture), + )]; + if (fileHash != expectedHash) { + throw Exception( + 'File $file was not downloaded correctly. ' + 'Found hash $fileHash, expected $expectedHash.', + ); + } + output.assets.code.add( + .new( + package: 'win32', + name: 'win32.dart', + linkMode: DynamicLoadingBundled(), + file: file.uri, + ), + ); + } }); } - -const dynamicLibraries = { - 'advapi32.dll', - 'api-ms-win-core-apiquery-l2-1-0.dll', - 'api-ms-win-core-comm-l1-1-1.dll', - 'api-ms-win-core-comm-l1-1-2.dll', - 'api-ms-win-core-handle-l1-1-0.dll', - 'api-ms-win-core-path-l1-1-0.dll', - 'api-ms-win-core-sysinfo-l1-2-3.dll', - 'api-ms-win-core-winrt-error-l1-1-0.dll', - 'api-ms-win-core-winrt-l1-1-0.dll', - 'api-ms-win-core-winrt-string-l1-1-0.dll', - 'api-ms-win-ro-typeresolution-l1-1-0.dll', - 'api-ms-win-ro-typeresolution-l1-1-1.dll', - 'api-ms-win-service-core-l1-1-3.dll', - 'api-ms-win-service-core-l1-1-4.dll', - 'api-ms-win-service-core-l1-1-5.dll', - 'api-ms-win-shcore-scaling-l1-1-1.dll', - 'api-ms-win-wsl-api-l1-1-0.dll', - 'bluetoothapis.dll', - 'bthprops.cpl', - 'comctl32.dll', - 'comdlg32.dll', - 'crypt32.dll', - 'dbghelp.dll', - 'dwmapi.dll', - 'dxva2.dll', - 'gdi32.dll', - 'iphlpapi.dll', - 'kernel32.dll', - 'ktmw32.dll', - 'magnification.dll', - 'netapi32.dll', - 'ntdll.dll', - 'ole32.dll', - 'oleaut32.dll', - 'powrprof.dll', - 'propsys.dll', - 'psapi.dll', - 'rometadata.dll', - 'scarddlg.dll', - 'setupapi.dll', - 'shell32.dll', - 'shlwapi.dll', - 'user32.dll', - 'uxtheme.dll', - 'version.dll', - 'wevtapi.dll', - 'winmm.dll', - 'winscard.dll', - 'winspool.drv', - 'wlanapi.dll', - 'ws2_32.dll', - 'wtsapi32.dll', - 'xinput1_4.dll', -}; - -const sources = [ - 'src/advapi32.g.c', - 'src/bluetoothapis.g.c', - 'src/bthprops.g.c', - 'src/crypt32.g.c', - 'src/dbghelp.g.c', - 'src/dxva2.g.c', - 'src/gdi32.g.c', - 'src/kernel32.g.c', - 'src/ktmw32.g.c', - 'src/magnification.g.c', - 'src/psapi.g.c', - 'src/setupapi.g.c', - 'src/shell32.g.c', - 'src/user32.g.c', - 'src/version.g.c', - 'src/wevtapi.g.c', - 'src/winscard.g.c', - 'src/winspool.g.c', - 'src/wlanapi.g.c', - 'src/ws2_32.g.c', - 'src/wtsapi32.g.c', -]; diff --git a/packages/win32/lib/src/hook_helpers/c_build.dart b/packages/win32/lib/src/hook_helpers/c_build.dart new file mode 100644 index 000000000..3efe32943 --- /dev/null +++ b/packages/win32/lib/src/hook_helpers/c_build.dart @@ -0,0 +1,73 @@ +import 'package:code_assets/code_assets.dart'; +import 'package:hooks/hooks.dart'; +import 'package:native_toolchain_c/native_toolchain_c.dart'; + +import 'logger.dart'; +import 'resources.g.dart'; + +/// Builds the C code for the `package:win32`. +Future runBuild(BuildInput input, BuildOutputBuilder output) async { + final CodeConfig(:targetOS, :targetArchitecture) = input.config.code; + final name = createTargetName(targetOS, targetArchitecture); + final cbuilder = CBuilder.library( + name: name, + assetName: 'win32.dart', + sources: sources, + libraries: _importLibraries, + flags: const ['/nologo'], + ); + await cbuilder.run( + input: input, + output: output, + logger: createDefaultLogger(), + ); +} + +/// Adds dynamic libraries as code assets to the build output. +void addDynamicLibrariesAsCodeAssets(BuildOutputBuilder output) { + for (final library in dynamicLibraries) { + final assetName = 'src/win32/${_normalizeLibraryName(library)}.g.dart'; + output.assets.code.add( + .new( + package: 'win32', + name: assetName, + linkMode: DynamicLoadingSystem(.file(library)), + ), + ); + } +} + +/// Creates a target name based on the OS and architecture. +/// +/// For example, `win32_windows_arm64` or `win32_windows_x64`. +String createTargetName(OS targetOS, Architecture targetArchitecture) => + 'win32_${targetOS.name}_${targetArchitecture.name}'; + +const _importLibraries = [ + 'Advapi32', + 'Bthprops', + 'Crypt32', + 'Dbghelp', + 'Dxva2', + 'Gdi32', + 'Kernel32', + 'KtmW32', + 'Magnification', + 'ole32', + 'OleAut32', + 'Psapi', + 'runtimeobject', + 'Setupapi', + 'Shell32', + 'User32', + 'Version', + 'Wevtapi', + 'Winscard', + 'Winspool', + 'Wlanapi', + 'Ws2_32', + 'Wtsapi32', +]; + +String _normalizeLibraryName(String library) => + library.split('.').first.replaceAll('-', '_'); diff --git a/packages/win32/lib/src/hook_helpers/download.dart b/packages/win32/lib/src/hook_helpers/download.dart new file mode 100644 index 000000000..4e4cee708 --- /dev/null +++ b/packages/win32/lib/src/hook_helpers/download.dart @@ -0,0 +1,80 @@ +import 'dart:io'; + +import 'package:code_assets/code_assets.dart'; +import 'package:crypto/crypto.dart'; + +import 'c_build.dart'; +import 'version.dart'; + +/// Computes the MD5 hash of the given [assetFile]. +Future hashAsset(File assetFile) async => + md5.convert(await assetFile.readAsBytes()).toString(); + +/// Downloads an asset for the specified [targetOS] and [targetArchitecture]. +Future downloadAsset( + OS targetOS, + Architecture targetArchitecture, + Directory outputDirectory, { + int maxAttempts = 5, +}) async { + final targetName = targetOS.dylibFileName( + createTargetName(targetOS, targetArchitecture), + ); + final uri = _downloadUri(targetName); + final destination = File.fromUri(outputDirectory.uri.resolve(targetName)); + + Object? lastError; + StackTrace? lastStackTrace; + + for (var attempt = 1; attempt <= maxAttempts; attempt++) { + try { + if (attempt > 1) { + stderr.writeln( + '($attempt/$maxAttempts) Retrying downloading prebuilt asset "$targetName"...', + ); + } + return await _initiateDownload(uri, destination); + } catch (error, stackTrace) { + lastError = error; + lastStackTrace = stackTrace; + if (attempt == maxAttempts) break; + stderr.writeln( + '($attempt/$maxAttempts) Failed to download prebuilt asset "$targetName".' + '${stderr.lineTerminator}$error', + ); + await Future.delayed(.new(milliseconds: 250 * (1 << (attempt - 1)))); + } + } + + Error.throwWithStackTrace( + Exception( + 'Failed to download prebuilt asset "$targetName" after $maxAttempts ' + 'attempts.${stderr.lineTerminator}$lastError', + ), + lastStackTrace!, + ); +} + +/// Constructs the download URI for a given [target] file name. +Uri _downloadUri(String target) => .parse( + 'https://github.com/halildurmus/win32/releases/download/$version/$target', +); + +Future _initiateDownload(Uri uri, File destination) async { + final client = HttpClient() + // Respect the http(s)_proxy environment variables. + ..findProxy = HttpClient.findProxyFromEnvironment; + try { + final request = await client.getUrl(uri); + final response = await request.close(); + if (response.statusCode != 200) { + throw Exception( + 'The request to $uri failed with status ${response.statusCode}.', + ); + } + await response.pipe(destination.openWrite()); + return destination; + } finally { + client.close(); + } +} diff --git a/packages/win32/lib/src/hook_helpers/hashes.g.dart b/packages/win32/lib/src/hook_helpers/hashes.g.dart new file mode 100644 index 000000000..e358421de --- /dev/null +++ b/packages/win32/lib/src/hook_helpers/hashes.g.dart @@ -0,0 +1,12 @@ +// THIS FILE IS AUTOGENERATED. TO UPDATE, RUN +// +// dart run tool/generate_asset_hashes.dart +// + +/// A map from asset file names to their MD5 hashes. +/// +/// Used to verify the integrity of downloaded assets. +const assetHashes = { + 'win32_windows_arm64.dll': '93d3bc4a11b9ae4984861aa8f8b5cdd7', + 'win32_windows_x64.dll': 'f4c83eda1fd91b4214bf5af524f9d35c', +}; diff --git a/packages/win32/lib/src/hook_helpers/logger.dart b/packages/win32/lib/src/hook_helpers/logger.dart new file mode 100644 index 000000000..4a7055a0e --- /dev/null +++ b/packages/win32/lib/src/hook_helpers/logger.dart @@ -0,0 +1,34 @@ +import 'dart:io'; + +import 'package:logging/logging.dart'; + +Logger createDefaultLogger() { + final logger = Logger.detached('CBuilder') + ..level = .INFO + ..onRecord.listen((record) { + if (record.message.contains( + 'Microsoft (R) C/C++ Optimizing Compiler Version', + )) { + // Skip logging compiler version info. + return; + } + + if (record.message.contains('Copyright (C) Microsoft Corporation.')) { + // Skip logging copyright info. + return; + } + + if (record.level >= .WARNING) { + stderr.writeln(record.message); + } else { + stdout.writeln(record.message); + } + if (record.error != null) { + stderr.writeln(record.error); + } + if (record.stackTrace != null) { + stderr.writeln(record.stackTrace); + } + }); + return logger; +} diff --git a/packages/win32/lib/src/hook_helpers/resources.g.dart b/packages/win32/lib/src/hook_helpers/resources.g.dart new file mode 100644 index 000000000..c4799e79e --- /dev/null +++ b/packages/win32/lib/src/hook_helpers/resources.g.dart @@ -0,0 +1,81 @@ +// THIS FILE IS AUTOGENERATED. DO NOT EDIT THIS FILE DIRECTLY. + +const dynamicLibraries = { + 'advapi32.dll', + 'api-ms-win-core-apiquery-l2-1-0.dll', + 'api-ms-win-core-comm-l1-1-1.dll', + 'api-ms-win-core-comm-l1-1-2.dll', + 'api-ms-win-core-handle-l1-1-0.dll', + 'api-ms-win-core-path-l1-1-0.dll', + 'api-ms-win-core-sysinfo-l1-2-3.dll', + 'api-ms-win-core-winrt-error-l1-1-0.dll', + 'api-ms-win-core-winrt-l1-1-0.dll', + 'api-ms-win-core-winrt-string-l1-1-0.dll', + 'api-ms-win-ro-typeresolution-l1-1-0.dll', + 'api-ms-win-ro-typeresolution-l1-1-1.dll', + 'api-ms-win-service-core-l1-1-3.dll', + 'api-ms-win-service-core-l1-1-4.dll', + 'api-ms-win-service-core-l1-1-5.dll', + 'api-ms-win-shcore-scaling-l1-1-1.dll', + 'api-ms-win-wsl-api-l1-1-0.dll', + 'bluetoothapis.dll', + 'bthprops.cpl', + 'comctl32.dll', + 'comdlg32.dll', + 'crypt32.dll', + 'dbghelp.dll', + 'dwmapi.dll', + 'dxva2.dll', + 'gdi32.dll', + 'iphlpapi.dll', + 'kernel32.dll', + 'ktmw32.dll', + 'magnification.dll', + 'netapi32.dll', + 'ntdll.dll', + 'ole32.dll', + 'oleaut32.dll', + 'powrprof.dll', + 'propsys.dll', + 'psapi.dll', + 'rometadata.dll', + 'scarddlg.dll', + 'setupapi.dll', + 'shell32.dll', + 'shlwapi.dll', + 'user32.dll', + 'uxtheme.dll', + 'version.dll', + 'wevtapi.dll', + 'winmm.dll', + 'winscard.dll', + 'winspool.drv', + 'wlanapi.dll', + 'ws2_32.dll', + 'wtsapi32.dll', + 'xinput1_4.dll', +}; + +const sources = [ + 'src/advapi32.g.c', + 'src/bluetoothapis.g.c', + 'src/bthprops.g.c', + 'src/crypt32.g.c', + 'src/dbghelp.g.c', + 'src/dxva2.g.c', + 'src/gdi32.g.c', + 'src/kernel32.g.c', + 'src/ktmw32.g.c', + 'src/magnification.g.c', + 'src/psapi.g.c', + 'src/setupapi.g.c', + 'src/shell32.g.c', + 'src/user32.g.c', + 'src/version.g.c', + 'src/wevtapi.g.c', + 'src/winscard.g.c', + 'src/winspool.g.c', + 'src/wlanapi.g.c', + 'src/ws2_32.g.c', + 'src/wtsapi32.g.c', +]; diff --git a/packages/win32/lib/src/hook_helpers/targets.dart b/packages/win32/lib/src/hook_helpers/targets.dart new file mode 100644 index 000000000..ef75bda8f --- /dev/null +++ b/packages/win32/lib/src/hook_helpers/targets.dart @@ -0,0 +1,9 @@ +import 'package:code_assets/code_assets.dart'; + +/// A list of supported target combinations of OS and architecture. +/// +/// Used to determine which assets to download or build. +const supportedTargets = <(OS, Architecture)>[ + (.windows, .arm64), + (.windows, .x64), +]; diff --git a/packages/win32/lib/src/hook_helpers/version.dart b/packages/win32/lib/src/hook_helpers/version.dart new file mode 100644 index 000000000..41e132317 --- /dev/null +++ b/packages/win32/lib/src/hook_helpers/version.dart @@ -0,0 +1,5 @@ +/// The GitHub release tag used for downloading prebuilt assets. +/// +/// After updating this value, run `dart run tool/generate_asset_hashes.dart` +/// to regenerate the asset hashes in `hashes.g.dart`. +const version = 'win32-prebuilt-assets-v6.1.0'; diff --git a/packages/win32/pubspec.yaml b/packages/win32/pubspec.yaml index 2428a5a6b..44e6bfde5 100644 --- a/packages/win32/pubspec.yaml +++ b/packages/win32/pubspec.yaml @@ -34,6 +34,7 @@ platforms: dependencies: code_assets: ^1.0.0 + crypto: ^3.0.7 ffi: ^2.1.4 ffi_leak_tracker: ^0.1.1 hooks: ^1.0.0 diff --git a/packages/win32/tool/build.dart b/packages/win32/tool/build.dart new file mode 100644 index 000000000..c4c85b620 --- /dev/null +++ b/packages/win32/tool/build.dart @@ -0,0 +1,62 @@ +// Builds the assets for the package:win32. +// +// This tool is executed from a GitHub Action workflow, and is not intended to +// be run locally. + +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:code_assets/code_assets.dart'; +import 'package:hooks/hooks.dart'; +import 'package:win32/src/hook_helpers/c_build.dart'; + +void main(List args) async { + final (os: os, architecture: architecture) = parseArguments(args); + final input = createBuildInput(os, architecture); + final output = BuildOutputBuilder(); + await runBuild(input, output); +} + +({String architecture, String os}) parseArguments(List args) { + final parser = ArgParser() + ..addOption( + 'architecture', + abbr: 'a', + allowed: Architecture.values.map((a) => a.name), + mandatory: true, + ) + ..addOption( + 'os', + abbr: 'o', + allowed: OS.values.map((a) => a.name), + mandatory: true, + ); + final argResults = parser.parse(args); + final os = argResults.option('os'); + final architecture = argResults.option('architecture'); + if (os == null || architecture == null) { + print(parser.usage); + exit(1); + } + return (os: os, architecture: architecture); +} + +BuildInput createBuildInput(String osString, String architecture) { + final packageRoot = Platform.script.resolve('..'); + final inputBuilder = BuildInputBuilder() + ..setupShared( + packageRoot: packageRoot, + packageName: 'win32', + outputFile: packageRoot.resolve('.dart_tool/win32/output.json'), + outputDirectoryShared: packageRoot.resolve('.dart_tool/win32/shared/'), + ) + ..config.setupBuild(linkingEnabled: false) + ..addExtension( + CodeAssetExtension( + targetArchitecture: .fromString(architecture), + targetOS: .fromString(osString), + linkModePreference: .dynamic, + ), + ); + return inputBuilder.build(); +} diff --git a/packages/win32/tool/generate_asset_hashes.dart b/packages/win32/tool/generate_asset_hashes.dart new file mode 100644 index 000000000..30df18b0e --- /dev/null +++ b/packages/win32/tool/generate_asset_hashes.dart @@ -0,0 +1,56 @@ +import 'dart:io'; + +import 'package:win32/src/hook_helpers/download.dart'; +import 'package:win32/src/hook_helpers/targets.dart'; + +Future main(List args) async { + final assetsDir = Directory.fromUri( + Platform.script.resolve('../.dart_tool/win32/'), + ); + if (assetsDir.existsSync()) await assetsDir.delete(recursive: true); + await assetsDir.create(recursive: true); + await Future.wait([ + for (final (targetOS, targetArchitecture) in supportedTargets) + downloadAsset(targetOS, targetArchitecture, assetsDir), + ]); + final assetFiles = + assetsDir.listSync(recursive: true).whereType().toList() + ..sort((f1, f2) => f1.path.compareTo(f2.path)); + final assetHashes = {}; + for (final assetFile in assetFiles) { + final fileHash = await hashAsset(assetFile); + final target = assetFile.uri.pathSegments.lastWhere((e) => e.isNotEmpty); + assetHashes[target] = fileHash; + } + + await writeHashesFile(assetHashes); +} + +/// Writes the [assetHashes] map to `lib/src/hook_helpers/hashes.g.dart`. +Future writeHashesFile(Map assetHashes) async { + final hashesFile = File.fromUri( + Platform.script.resolve('../lib/src/hook_helpers/hashes.g.dart'), + ); + await hashesFile.create(recursive: true); + final buffer = StringBuffer() + ..writeln(''' +// THIS FILE IS AUTOGENERATED. DO NOT EDIT THIS FILE DIRECTLY. +// +// TO UPDATE, RUN: +// +// dart run tool/generate_asset_hashes.dart +''') + ..writeln() + ..writeln(''' +/// A map from asset file names to their MD5 hashes. +/// +/// Used to verify the integrity of downloaded assets. +const assetHashes = { +'''); + for (final hash in assetHashes.entries) { + buffer.write(" '${hash.key}': '${hash.value}',\n"); + } + buffer.writeln('};'); + await hashesFile.writeAsString(buffer.toString()); + await Process.run(Platform.executable, ['format', hashesFile.path]); +} diff --git a/pubspec.yaml b/pubspec.yaml index c7f692bcb..4b248bbde 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -40,6 +40,11 @@ dev_dependencies: path: ^1.9.1 win32: ^6.0.0 +hooks: + user_defines: + win32: + local_build: true + melos: categories: examples: