Skip to content
Open
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
155 changes: 154 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,169 @@ on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+*'
workflow_dispatch:

permissions:
contents: read

jobs:
build-prebuilt:
name: Build ${{ matrix.target }}
runs-on: ${{ matrix.os }}
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
include:
- target: linux-x64
os: ubuntu-22.04
library: libwebcrypto.so
configure_args: ''
- target: macos-arm64
os: macos-15
library: libwebcrypto.dylib
configure_args: '-DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 -DCMAKE_OSX_ARCHITECTURES=arm64'
- target: macos-x64
os: macos-15-intel
library: libwebcrypto.dylib
configure_args: '-DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 -DCMAKE_OSX_ARCHITECTURES=x86_64'
- target: windows-x64
os: windows-2022
library: webcrypto.dll
configure_args: '-A x64'

steps:
- uses: actions/checkout@v4

- uses: ilammy/setup-nasm@72793074d3c8cdda771dba85f6deafe00623038b # v1.5.2
if: runner.os == 'Windows'

- name: Build prebuilt asset
shell: pwsh
run: |
$install = Join-Path $env:GITHUB_WORKSPACE 'build/prebuilt-install'
$artifact = Join-Path $env:GITHUB_WORKSPACE 'artifact/prebuilt/${{ matrix.target }}'
$configureArgs = @(
'-S', 'src',
'-B', 'build/prebuilt',
'-DCMAKE_BUILD_TYPE=Release',
"-DCMAKE_INSTALL_PREFIX=$install"
)
if ('${{ matrix.configure_args }}' -ne '') {
$configureArgs += '${{ matrix.configure_args }}'.Split(' ')
}

cmake @configureArgs
cmake --build build/prebuilt --config Release --target install

New-Item -ItemType Directory -Force -Path $artifact | Out-Null
Copy-Item -LiteralPath (Join-Path $install '${{ matrix.library }}') -Destination $artifact

- name: Verify Linux architecture
if: runner.os == 'Linux'
shell: pwsh
run: |
$library = 'artifact/prebuilt/${{ matrix.target }}/${{ matrix.library }}'
$description = & file $library
$description
if ($description -notmatch 'x86-64') {
throw 'Expected an x86-64 ELF library.'
}

- name: Verify macOS architecture
if: runner.os == 'macOS'
shell: pwsh
run: |
$library = 'artifact/prebuilt/${{ matrix.target }}/${{ matrix.library }}'
$architecture = (& lipo -archs $library).Trim()
$expected = if ('${{ matrix.target }}' -eq 'macos-arm64') { 'arm64' } else { 'x86_64' }
if ($architecture -ne $expected) {
throw "Expected $expected, got $architecture."
}

- name: Verify Windows architecture
if: runner.os == 'Windows'
shell: pwsh
run: |
$library = 'artifact/prebuilt/${{ matrix.target }}/${{ matrix.library }}'
$stream = [System.IO.File]::OpenRead($library)
$reader = [System.IO.BinaryReader]::new($stream)
try {
$stream.Position = 0x3c
$peHeaderOffset = $reader.ReadInt32()
$stream.Position = $peHeaderOffset
if ($reader.ReadUInt32() -ne 0x00004550) {
throw 'Expected a PE library.'
}
$machine = $reader.ReadUInt16()
if ($machine -ne 0x8664) {
throw ('Expected an x64 PE library, got machine type 0x{0:x4}.' -f $machine)
}
} finally {
$reader.Dispose()
$stream.Dispose()
}

- name: Upload prebuilt asset
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: webcrypto-${{ matrix.target }}
path: artifact/
if-no-files-found: error

validate-prebuilt:
name: Validate prebuilt package
needs: build-prebuilt
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
pattern: webcrypto-*
path: .
merge-multiple: true
- uses: dart-lang/setup-dart@v1
- uses: subosito/flutter-action@v2
with:
channel: 'stable'
- name: Verify prebuilt layout
shell: pwsh
run: |
@(
'prebuilt/linux-x64/libwebcrypto.so',
'prebuilt/macos-arm64/libwebcrypto.dylib',
'prebuilt/macos-x64/libwebcrypto.dylib',
'prebuilt/windows-x64/webcrypto.dll'
) | ForEach-Object {
if (-not (Test-Path -LiteralPath $_ -PathType Leaf)) {
throw "Missing prebuilt asset: $_"
}
}
- run: flutter pub get
- name: Test prebuilt asset
run: dart test -p vm test/aes_ctr_counter_wrap_test.dart
- name: Validate package contents
run: flutter pub publish --dry-run

publish:
name: Publish to pub.dev
runs-on: ubuntu-latest
needs:
- build-prebuilt
- validate-prebuilt
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-24.04
environment: pub.dev
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
pattern: webcrypto-*
path: .
merge-multiple: true
- uses: dart-lang/setup-dart@v1
- uses: subosito/flutter-action@v2
with:
Expand Down
41 changes: 41 additions & 0 deletions doc/prebuilt-native-assets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Prebuilt Native Assets

Published versions of `package:webcrypto` include trusted prebuilt native
libraries for the initial desktop target matrix:

```text
prebuilt/
linux-x64/
libwebcrypto.so
macos-arm64/
libwebcrypto.dylib
macos-x64/
libwebcrypto.dylib
windows-x64/
webcrypto.dll
```

The build hook uses a matching prebuilt library when one is available. Targets
without a packaged prebuilt continue to build the native library from source
with CMake.

## Building From Source

Consumers and CI can bypass a packaged prebuilt explicitly:

```yaml
hooks:
user_defines:
webcrypto:
build_from_source: true
```

## Publishing

The prebuilt libraries are not committed to the repository. The
`.github/workflows/publish.yml` workflow builds them from the tagged source,
validates the assembled package, and includes them in the package published to
pub.dev.

Manually dispatching the workflow builds and validates the same artifacts but
does not publish a package.
47 changes: 45 additions & 2 deletions hook/build.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import 'package:hooks/hooks.dart';
import 'package:native_toolchain_cmake/native_toolchain_cmake.dart';

const _assetName = 'webcrypto.dart';
const _libraryName = 'webcrypto';
const _buildFromSourceDefine = 'build_from_source';

Future<void> main(List<String> args) async {
await build(args, (input, output) async {
Expand All @@ -33,14 +35,32 @@ Future<void> main(List<String> args) async {
final packageRoot = input.packageRoot;
final installDir = input.outputDirectory.resolve('install/');
final sourceDir = packageRoot.resolve('src/');
final prebuiltAsset = input.prebuiltAsset;

if (!input.userDefines.buildFromSource && prebuiltAsset.existsSync()) {
stdout.writeln(
'webcrypto: using prebuilt native asset for '
'${input.targetName}.',
);
output.assets.code.add(
CodeAsset(
package: input.packageName,
name: _assetName,
linkMode: DynamicLoadingBundled(),
file: prebuiltAsset.uri,
),
);
output.dependencies.add(prebuiltAsset.uri);
return;
}

stdout.writeln(
'webcrypto: building native asset for '
'${input.config.code.targetOS}-${input.config.code.targetArchitecture}.',
'${input.targetName}.',
);

final builder = CMakeBuilder.create(
name: 'webcrypto',
name: _libraryName,
sourceDir: sourceDir,
defines: {
'CMAKE_BUILD_TYPE': 'Release',
Expand Down Expand Up @@ -69,6 +89,29 @@ Future<void> main(List<String> args) async {
});
}

extension on BuildInput {
File get prebuiltAsset {
final libraryFileName = config.code.targetOS.dylibFileName(_libraryName);
return File.fromUri(
packageRoot.resolve('prebuilt/$targetName/$libraryFileName'),
);
}

String get targetName {
final code = config.code;
final os = code.targetOS;
final arch = code.targetArchitecture;
if (os == OS.iOS) {
return '${os.name}-${code.iOS.targetSdk.type}-${arch.name}';
}
return '${os.name}-${arch.name}';
}
}

extension on HookInputUserDefines {
bool get buildFromSource => this[_buildFromSourceDefine] == true;
}

final _buildDependencyExtensions = {
'.S',
'.asm',
Expand Down
Loading