Skip to content
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: 11 additions & 0 deletions .github/workflows/amplify_auth_cognito_dart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
- '.github/workflows/amplify_auth_cognito_dart.yaml'
- '.github/workflows/dart_native.yaml'
- '.github/workflows/dart_vm.yaml'
- '.github/workflows/ffigen_validate.yaml'
- 'packages/amplify_core/lib/**/*.dart'
- 'packages/amplify_core/pubspec.yaml'
- 'packages/amplify_lints/lib/**/*.yaml'
Expand Down Expand Up @@ -42,6 +43,7 @@ on:
- '.github/workflows/amplify_auth_cognito_dart.yaml'
- '.github/workflows/dart_native.yaml'
- '.github/workflows/dart_vm.yaml'
- '.github/workflows/ffigen_validate.yaml'
- 'packages/amplify_core/lib/**/*.dart'
- 'packages/amplify_core/pubspec.yaml'
- 'packages/amplify_lints/lib/**/*.yaml'
Expand Down Expand Up @@ -102,3 +104,12 @@ jobs:
with:
package-name: amplify_auth_cognito_dart
working-directory: packages/auth/amplify_auth_cognito_dart
ffigen_macos_test:
needs: test
uses: ./.github/workflows/ffigen_validate.yaml
secrets: inherit
with:
package-name: amplify_auth_cognito_dart
working-directory: packages/auth/amplify_auth_cognito_dart
ffigen-configs: 'ffigen.macos.yaml'
os: macos-26
20 changes: 20 additions & 0 deletions .github/workflows/amplify_secure_storage_dart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
paths:
- '.github/workflows/amplify_secure_storage_dart.yaml'
- '.github/workflows/dart_vm.yaml'
- '.github/workflows/ffigen_validate.yaml'
- 'packages/amplify_lints/lib/**/*.yaml'
- 'packages/amplify_lints/pubspec.yaml'
- 'packages/aws_common/lib/**/*.dart'
Expand All @@ -24,6 +25,7 @@ on:
paths:
- '.github/workflows/amplify_secure_storage_dart.yaml'
- '.github/workflows/dart_vm.yaml'
- '.github/workflows/ffigen_validate.yaml'
- 'packages/amplify_lints/lib/**/*.yaml'
- 'packages/amplify_lints/pubspec.yaml'
- 'packages/aws_common/lib/**/*.dart'
Expand Down Expand Up @@ -61,3 +63,21 @@ jobs:
with:
package-name: amplify_secure_storage_dart
working-directory: packages/secure_storage/amplify_secure_storage_dart
ffigen_macos_test:
needs: test
uses: ./.github/workflows/ffigen_validate.yaml
secrets: inherit
with:
package-name: amplify_secure_storage_dart
working-directory: packages/secure_storage/amplify_secure_storage_dart
ffigen-configs: 'ffigen.cupertino.coreFoundation.config.yaml ffigen.cupertino.security.config.yaml'
os: macos-26
ffigen_linux_test:
needs: test
uses: ./.github/workflows/ffigen_validate.yaml
secrets: inherit
with:
package-name: amplify_secure_storage_dart
working-directory: packages/secure_storage/amplify_secure_storage_dart
ffigen-configs: 'ffigen.gio.config.yaml ffigen.glib.config.yaml ffigen.libsecret.config.yaml'
os: ubuntu-latest
77 changes: 77 additions & 0 deletions .github/workflows/ffigen_validate.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: FFI Gen Validate
on:
workflow_call:
inputs:
package-name:
description: The name of the package being tested
required: true
type: string
working-directory:
description: The working directory relative to the repo root
required: true
type: string
ffigen-configs:
description: Space-separated list of ffigen config files to validate
required: true
type: string
os:
description: The runner OS to use (e.g. macos-14, ubuntu-latest)
required: true
type: string

permissions:
contents: read

jobs:
ffigen-validate:
runs-on: ${{ inputs.os }}
timeout-minutes: 30
steps:
- name: Cache Pub dependencies
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # 5.0.3
with:
path: |
~/.pub-cache/hosted
~/.pub-cache/git
key: os:${{ inputs.os }};sdk:stable;packages:${{ inputs.working-directory }};ffigen
restore-keys: |
os:${{ inputs.os }};sdk:stable;packages:${{ inputs.working-directory }}
os:${{ inputs.os }};sdk:stable
os:${{ inputs.os }}

- name: Git Checkout
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # 4.0.0

- name: Git Submodules
run: git submodule update --init

- name: Setup Dart
uses: dart-lang/setup-dart@21e68f4d54916e6c2f68f8c290b2aed9b02cbd3e # main
with:
sdk: stable

- name: Setup aft
shell: bash
run: dart pub global activate -spath packages/aft

- name: Bootstrap
id: bootstrap
timeout-minutes: 20
run: aft bootstrap --fail-fast --include=${{ inputs.package-name }} --verbose

- name: Run ffigen and check for changes
if: "always() && steps.bootstrap.conclusion == 'success'"
shell: bash
working-directory: ${{ inputs.working-directory }}
run: |
configs="${{ inputs.ffigen-configs }}"
for config in $configs; do
echo "Running ffigen with config: $config"
dart run ffigen --config="$config"
done
if git diff --exit-code -- '*.g.dart'; then
echo "✅ ffigen output is up to date."
else
echo "❌ ffigen output is out of date. Please regenerate bindings."
exit 1
fi
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'dart:io';

import 'package:aft/aft.dart';
import 'package:path/path.dart' as p;
import 'package:yaml/yaml.dart';

/// Command for generating GitHub Actions workflows for all packages in the
/// repo.
Expand Down Expand Up @@ -265,11 +266,17 @@ ${dependabotGroups.join('\n')}
final hasGoldens =
package.flavor == PackageFlavor.flutter &&
package.goldensTestDirectory != null;
// Detect ffigen configurations
const ffigenWorkflow = 'ffigen_validate.yaml';
final ffigenConfigs = _detectFfigenConfigs(package);
final hasFfigen = ffigenConfigs.isNotEmpty;

final workflows = <String>[
analyzeAndTestWorkflow,
if (needsNativeTest) nativeWorkflow,
if (needsWebTest) ...[ddcWorkflow, dart2JsWorkflow],
if (needsE2ETest) ...e2eWorkflows.values,
if (hasFfigen) ffigenWorkflow,
];

// Collect all the paths for which this workflow will run. This includes
Expand Down Expand Up @@ -415,6 +422,25 @@ jobs:
}
}

// Add ffigen validation jobs
if (hasFfigen) {
for (final MapEntry(key: os, value: configs) in ffigenConfigs.entries) {
final osLabel = os.startsWith('macos') ? 'macos' : 'linux';
final configFiles = configs.join(' ');
workflowContents.write('''
ffigen_${osLabel}_test:
needs: test
uses: ./.github/workflows/$ffigenWorkflow
secrets: inherit
with:
package-name: ${package.name}
working-directory: $repoRelativePath
ffigen-configs: '$configFiles'
os: $os
''');
}
}

writeWorkflowFile(workflowFile, workflowContents.toString());

await generateAndroidUnitTestWorkflow(
Expand Down Expand Up @@ -619,6 +645,73 @@ jobs:
writeWorkflowFile(iosWorkflowFile, iosWorkflowContents);
}

/// Detects ffigen configuration files in the package directory and groups
/// them by the OS runner needed.
///
/// Returns a map of runner OS -> list of config file names.
Map<String, List<String>> _detectFfigenConfigs(PackageInfo package) {
final packageDir = Directory(package.path);
final ffigenConfigFiles = packageDir
.listSync()
.whereType<File>()
.where(
(f) =>
p.basename(f.path).startsWith('ffigen') &&
p.basename(f.path).endsWith('.yaml'),
)
.toList();

if (ffigenConfigFiles.isEmpty) {
return {};
}

// Group configs by OS runner
final configsByOs = <String, List<String>>{};
for (final configFile in ffigenConfigFiles) {
final configName = p.basename(configFile.path);
final os = _ffigenConfigOs(configFile);
configsByOs.putIfAbsent(os, () => []).add(configName);
}

// Sort config names within each OS group for deterministic output
for (final configs in configsByOs.values) {
configs.sort();
}

// Return with sorted keys for deterministic job ordering
return Map.fromEntries(
configsByOs.entries.toList()..sort((a, b) => a.key.compareTo(b.key)),
);
}

/// Determines the OS runner needed for a given ffigen config file.
///
/// Checks for macOS indicators (Xcode paths, ObjC language) in the config.
/// Defaults to `ubuntu-latest` for Linux-based configs.
String _ffigenConfigOs(File configFile) {
try {
final content = configFile.readAsStringSync();
final yaml = loadYaml(content);
if (yaml is YamlMap) {
// Check for ObjC language (requires macOS)
final language = yaml['language'];
if (language is String && language == 'objc') {
return 'macos-26';
}

// Check for macOS SDK paths in headers or compiler-opts
if (content.contains('MacOSX.platform') ||
content.contains('MacOSX.sdk') ||
content.contains('Xcode.app')) {
return 'macos-26';
}
}
} on Object {
// If we can't parse the config, default to Linux
}
return 'ubuntu-latest';
}

void writeWorkflowFile(File workflowFile, String content) {
if (!workflowFile.existsSync()) {
workflowFile.createSync();
Expand Down
Loading
Loading