Symbol file import export (#468)
diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml
index fdc7a01..f6deae3 100644
--- a/.github/workflows/test-package.yml
+++ b/.github/workflows/test-package.yml
@@ -27,7 +27,7 @@
sdk: ${{ matrix.sdk }}
- id: install
name: Install dependencies
- run: dart pub get
+ run: dart pub get && dart pub get --directory="example/shared_bindings"
- name: Check formatting
run: dart format --output=none --set-exit-if-changed .
if: always() && steps.install.outcome == 'success'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9980c10..1a577da 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 7.2.0
+- Added support for sharing bindings using `symbol-file` config. (See `README.md`
+and examples/shared_bindings).
+
# 7.1.0
- Handle declarations with definition accessible from a different entry-point.
diff --git a/README.md b/README.md
index c72249b..76e944f 100644
--- a/README.md
+++ b/README.md
@@ -92,6 +92,12 @@
```yaml
output: 'generated_bindings.dart'
```
+or
+```yaml
+output:
+ bindings: 'generated_bindings.dart'
+ ...
+```
</td>
</tr>
<tr>
@@ -521,6 +527,36 @@
```
</td>
</tr>
+ <tr>
+ <td>output -> symbol-file</td>
+ <td>Generates a symbol file yaml containing all types defined in the generated output.</td>
+ <td>
+
+```yaml
+output:
+ ...
+ symbol-file:
+ # Although file paths are supported here, prefer Package Uri's here
+ # so that other pacakges can use them.
+ output: 'package:some_pkg/symbols.yaml'
+ import-path: 'package:some_pkg/base.dart'
+```
+</td>
+ </tr>
+ <tr>
+ <td>import -> symbol-files</td>
+ <td>Import symbols from a symbol file. Used for sharing type definitions from other pacakges.</td>
+ <td>
+
+```yaml
+import:
+ symbol-files:
+ # Both package Uri and file paths are supported here.
+ - 'package:some_pkg/symbols.yaml'
+ - 'path/to/some/symbol_file.yaml'
+```
+</td>
+ </tr>
</tbody>
</table>
@@ -757,3 +793,16 @@
Level options are - `[all, fine, info (default), warning, severe]`.
The `all` and `fine` will print a ton of logs are meant for debugging
purposes only.
+
+### How can type definitions be shared?
+
+Ffigen can share type definitions using symbol files.
+- A package can generate a symbol file using the `output -> symbol-file` config.
+- And another package can then import this, using `import -> symbol-files` config.
+- Doing so will reuse all the types such as Struct/Unions, and will automatically
+ exclude generating other types (E.g functions, enums, macros).
+
+Checkout `examples/shared_bindings` for details.
+
+For manually reusing definitions from another package, the `library-imports`
+and `type-map` config can be used.
diff --git a/example/shared_bindings/.gitignore b/example/shared_bindings/.gitignore
new file mode 100644
index 0000000..1b05164
--- /dev/null
+++ b/example/shared_bindings/.gitignore
@@ -0,0 +1,11 @@
+# Files and directories created by pub.
+.dart_tool/
+.packages
+# Remove the following pattern if you wish to check in your lock file.
+pubspec.lock
+
+# Conventional directory for build outputs.
+build/
+
+# Directory created by dartdoc.
+doc/api/
diff --git a/example/shared_bindings/README.md b/example/shared_bindings/README.md
new file mode 100644
index 0000000..6a81fdd
--- /dev/null
+++ b/example/shared_bindings/README.md
@@ -0,0 +1,10 @@
+# Shared bindings
+
+An example to showcase how bindings can share types
+with other bindings.
+
+## Generating bindings
+At the root of this example (`example/shared_bindings`), run -
+```
+dart run generate.dart
+```
diff --git a/example/shared_bindings/ffigen_configs/a.yaml b/example/shared_bindings/ffigen_configs/a.yaml
new file mode 100644
index 0000000..55a720e
--- /dev/null
+++ b/example/shared_bindings/ffigen_configs/a.yaml
@@ -0,0 +1,12 @@
+# Copyright (c) 2022, 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.
+
+name: NativeLibraryA
+description: Bindings to `headers/a.h`.
+output: '../lib/generated/a_gen.dart'
+headers:
+ entry-points:
+ - '../headers/a.h'
+preamble: |
+ // ignore_for_file: non_constant_identifier_names, camel_case_types
diff --git a/example/shared_bindings/ffigen_configs/a_shared_base.yaml b/example/shared_bindings/ffigen_configs/a_shared_base.yaml
new file mode 100644
index 0000000..0adcc20
--- /dev/null
+++ b/example/shared_bindings/ffigen_configs/a_shared_base.yaml
@@ -0,0 +1,16 @@
+# Copyright (c) 2022, 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.
+
+name: NativeLibraryASharedB
+description: Bindings to `headers/a.h` with shared definitions from `headers/base.h`.
+output: '../lib/generated/a_shared_b_gen.dart'
+headers:
+ entry-points:
+ - '../headers/a.h'
+import:
+ symbol-files:
+ # Both package Uri and file paths are supported here.
+ - 'package:shared_bindings/generated/base_symbols.yaml'
+preamble: |
+ // ignore_for_file: non_constant_identifier_names, camel_case_types
diff --git a/example/shared_bindings/ffigen_configs/base.yaml b/example/shared_bindings/ffigen_configs/base.yaml
new file mode 100644
index 0000000..474d0f9
--- /dev/null
+++ b/example/shared_bindings/ffigen_configs/base.yaml
@@ -0,0 +1,17 @@
+# Copyright (c) 2022, 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.
+
+name: NativeLibraryBase
+description: Bindings to `headers/base.h`.
+output:
+ bindings: '../lib/generated/base_gen.dart'
+ symbol-file:
+ # Although file paths are supported here, prefer Package Uri's here.
+ output: 'package:shared_bindings/generated/base_symbols.yaml'
+ import-path: 'package:shared_bindings/generated/base_gen.dart'
+headers:
+ entry-points:
+ - '../headers/base.h'
+preamble: |
+ // ignore_for_file: non_constant_identifier_names, camel_case_types
diff --git a/example/shared_bindings/generate.dart b/example/shared_bindings/generate.dart
new file mode 100644
index 0000000..8e565cf
--- /dev/null
+++ b/example/shared_bindings/generate.dart
@@ -0,0 +1,32 @@
+import 'dart:io';
+
+import 'package:cli_util/cli_util.dart';
+import 'package:path/path.dart' as p;
+
+ProcessResult runFfigenForConfig(String sdkPath, String configPath) {
+ return Process.runSync(
+ p.join(sdkPath, 'bin', 'dart'),
+ [
+ 'run',
+ 'ffigen',
+ '--config=$configPath',
+ ],
+ runInShell: Platform.isWindows,
+ );
+}
+
+void main() {
+ final sdkPath = getSdkPath();
+ final configPaths = [
+ 'ffigen_configs/base.yaml',
+ 'ffigen_configs/a.yaml',
+ 'ffigen_configs/a_shared_base.yaml'
+ ];
+ for (final configPath in configPaths) {
+ final res = runFfigenForConfig(sdkPath, configPath);
+ print(res.stdout.toString());
+ if (res.exitCode != 0) {
+ throw Exception("Some error occurred: ${res.stderr.toString()}");
+ }
+ }
+}
diff --git a/example/shared_bindings/headers/a.h b/example/shared_bindings/headers/a.h
new file mode 100644
index 0000000..ef3bbea
--- /dev/null
+++ b/example/shared_bindings/headers/a.h
@@ -0,0 +1,21 @@
+
+#include "base.h"
+
+struct A_Struct1{
+ int a;
+};
+
+union A_Union1{
+ int a;
+};
+
+enum A_Enum{
+ A_ENUM_1,
+ A_ENUM_2,
+};
+
+#define A_MACRO_1 1;
+
+void a_func1();
+
+void a_func2(struct BaseStruct2 s, union BaseUnion2 u, BaseTypedef2 t);
diff --git a/example/shared_bindings/headers/base.h b/example/shared_bindings/headers/base.h
new file mode 100644
index 0000000..345cac6
--- /dev/null
+++ b/example/shared_bindings/headers/base.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2022, 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.
+
+struct BaseStruct1{
+ int a;
+};
+
+union BaseUnion1{
+ int a;
+};
+
+struct BaseStruct2{
+ int a;
+};
+
+union BaseUnion2{
+ int a;
+};
+
+typedef struct BaseStruct1 BaseTypedef1;
+typedef struct BaseStruct2 BaseTypedef2;
+
+enum BaseEnum{
+ BASE_ENUM_1,
+ BASE_ENUM_2,
+};
+
+#define BASE_MACRO_1 1;
+
+void base_func1(BaseTypedef1 t1, BaseTypedef2 t2);
diff --git a/example/shared_bindings/lib/generated/a_gen.dart b/example/shared_bindings/lib/generated/a_gen.dart
new file mode 100644
index 0000000..631aff8
--- /dev/null
+++ b/example/shared_bindings/lib/generated/a_gen.dart
@@ -0,0 +1,112 @@
+// ignore_for_file: non_constant_identifier_names, camel_case_types
+
+// AUTO GENERATED FILE, DO NOT EDIT.
+//
+// Generated by `package:ffigen`.
+import 'dart:ffi' as ffi;
+
+/// Bindings to `headers/a.h`.
+class NativeLibraryA {
+ /// Holds the symbol lookup function.
+ final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
+ _lookup;
+
+ /// The symbols are looked up in [dynamicLibrary].
+ NativeLibraryA(ffi.DynamicLibrary dynamicLibrary)
+ : _lookup = dynamicLibrary.lookup;
+
+ /// The symbols are looked up with [lookup].
+ NativeLibraryA.fromLookup(
+ ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
+ lookup)
+ : _lookup = lookup;
+
+ void base_func1(
+ BaseTypedef1 t1,
+ BaseTypedef2 t2,
+ ) {
+ return _base_func1(
+ t1,
+ t2,
+ );
+ }
+
+ late final _base_func1Ptr = _lookup<
+ ffi.NativeFunction<ffi.Void Function(BaseTypedef1, BaseTypedef2)>>(
+ 'base_func1');
+ late final _base_func1 =
+ _base_func1Ptr.asFunction<void Function(BaseTypedef1, BaseTypedef2)>();
+
+ void a_func1() {
+ return _a_func1();
+ }
+
+ late final _a_func1Ptr =
+ _lookup<ffi.NativeFunction<ffi.Void Function()>>('a_func1');
+ late final _a_func1 = _a_func1Ptr.asFunction<void Function()>();
+
+ void a_func2(
+ BaseStruct2 s,
+ BaseUnion2 u,
+ BaseTypedef2 t,
+ ) {
+ return _a_func2(
+ s,
+ u,
+ t,
+ );
+ }
+
+ late final _a_func2Ptr = _lookup<
+ ffi.NativeFunction<
+ ffi.Void Function(BaseStruct2, BaseUnion2, BaseTypedef2)>>('a_func2');
+ late final _a_func2 = _a_func2Ptr
+ .asFunction<void Function(BaseStruct2, BaseUnion2, BaseTypedef2)>();
+}
+
+class BaseStruct1 extends ffi.Struct {
+ @ffi.Int()
+ external int a;
+}
+
+class BaseUnion1 extends ffi.Union {
+ @ffi.Int()
+ external int a;
+}
+
+class BaseStruct2 extends ffi.Struct {
+ @ffi.Int()
+ external int a;
+}
+
+class BaseUnion2 extends ffi.Union {
+ @ffi.Int()
+ external int a;
+}
+
+abstract class BaseEnum {
+ static const int BASE_ENUM_1 = 0;
+ static const int BASE_ENUM_2 = 1;
+}
+
+typedef BaseTypedef1 = BaseStruct1;
+typedef BaseTypedef2 = BaseStruct2;
+
+class A_Struct1 extends ffi.Struct {
+ @ffi.Int()
+ external int a;
+}
+
+class A_Union1 extends ffi.Union {
+ @ffi.Int()
+ external int a;
+}
+
+abstract class A_Enum {
+ static const int A_ENUM_1 = 0;
+ static const int A_ENUM_2 = 1;
+}
+
+const int BASE_MACRO_1 = 1;
+
+const int A_MACRO_1 = 1;
diff --git a/example/shared_bindings/lib/generated/a_shared_b_gen.dart b/example/shared_bindings/lib/generated/a_shared_b_gen.dart
new file mode 100644
index 0000000..fbf9c16
--- /dev/null
+++ b/example/shared_bindings/lib/generated/a_shared_b_gen.dart
@@ -0,0 +1,70 @@
+// ignore_for_file: non_constant_identifier_names, camel_case_types
+
+// AUTO GENERATED FILE, DO NOT EDIT.
+//
+// Generated by `package:ffigen`.
+import 'dart:ffi' as ffi;
+import 'package:shared_bindings/generated/base_gen.dart' as _imp1;
+
+/// Bindings to `headers/a.h` with shared definitions from `headers/base.h`.
+class NativeLibraryASharedB {
+ /// Holds the symbol lookup function.
+ final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
+ _lookup;
+
+ /// The symbols are looked up in [dynamicLibrary].
+ NativeLibraryASharedB(ffi.DynamicLibrary dynamicLibrary)
+ : _lookup = dynamicLibrary.lookup;
+
+ /// The symbols are looked up with [lookup].
+ NativeLibraryASharedB.fromLookup(
+ ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
+ lookup)
+ : _lookup = lookup;
+
+ void a_func1() {
+ return _a_func1();
+ }
+
+ late final _a_func1Ptr =
+ _lookup<ffi.NativeFunction<ffi.Void Function()>>('a_func1');
+ late final _a_func1 = _a_func1Ptr.asFunction<void Function()>();
+
+ void a_func2(
+ _imp1.BaseStruct2 s,
+ _imp1.BaseUnion2 u,
+ _imp1.BaseTypedef2 t,
+ ) {
+ return _a_func2(
+ s,
+ u,
+ t,
+ );
+ }
+
+ late final _a_func2Ptr = _lookup<
+ ffi.NativeFunction<
+ ffi.Void Function(_imp1.BaseStruct2, _imp1.BaseUnion2,
+ _imp1.BaseTypedef2)>>('a_func2');
+ late final _a_func2 = _a_func2Ptr.asFunction<
+ void Function(_imp1.BaseStruct2, _imp1.BaseUnion2, _imp1.BaseTypedef2)>();
+}
+
+class A_Struct1 extends ffi.Struct {
+ @ffi.Int()
+ external int a;
+}
+
+class A_Union1 extends ffi.Union {
+ @ffi.Int()
+ external int a;
+}
+
+abstract class A_Enum {
+ static const int A_ENUM_1 = 0;
+ static const int A_ENUM_2 = 1;
+}
+
+const int BASE_MACRO_1 = 1;
+
+const int A_MACRO_1 = 1;
diff --git a/example/shared_bindings/lib/generated/base_gen.dart b/example/shared_bindings/lib/generated/base_gen.dart
new file mode 100644
index 0000000..d4cfdb5
--- /dev/null
+++ b/example/shared_bindings/lib/generated/base_gen.dart
@@ -0,0 +1,69 @@
+// ignore_for_file: non_constant_identifier_names, camel_case_types
+
+// AUTO GENERATED FILE, DO NOT EDIT.
+//
+// Generated by `package:ffigen`.
+import 'dart:ffi' as ffi;
+
+/// Bindings to `headers/base.h`.
+class NativeLibraryBase {
+ /// Holds the symbol lookup function.
+ final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
+ _lookup;
+
+ /// The symbols are looked up in [dynamicLibrary].
+ NativeLibraryBase(ffi.DynamicLibrary dynamicLibrary)
+ : _lookup = dynamicLibrary.lookup;
+
+ /// The symbols are looked up with [lookup].
+ NativeLibraryBase.fromLookup(
+ ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
+ lookup)
+ : _lookup = lookup;
+
+ void base_func1(
+ BaseTypedef1 t1,
+ BaseTypedef2 t2,
+ ) {
+ return _base_func1(
+ t1,
+ t2,
+ );
+ }
+
+ late final _base_func1Ptr = _lookup<
+ ffi.NativeFunction<ffi.Void Function(BaseTypedef1, BaseTypedef2)>>(
+ 'base_func1');
+ late final _base_func1 =
+ _base_func1Ptr.asFunction<void Function(BaseTypedef1, BaseTypedef2)>();
+}
+
+class BaseStruct1 extends ffi.Struct {
+ @ffi.Int()
+ external int a;
+}
+
+class BaseUnion1 extends ffi.Union {
+ @ffi.Int()
+ external int a;
+}
+
+class BaseStruct2 extends ffi.Struct {
+ @ffi.Int()
+ external int a;
+}
+
+class BaseUnion2 extends ffi.Union {
+ @ffi.Int()
+ external int a;
+}
+
+abstract class BaseEnum {
+ static const int BASE_ENUM_1 = 0;
+ static const int BASE_ENUM_2 = 1;
+}
+
+typedef BaseTypedef1 = BaseStruct1;
+typedef BaseTypedef2 = BaseStruct2;
+
+const int BASE_MACRO_1 = 1;
diff --git a/example/shared_bindings/lib/generated/base_symbols.yaml b/example/shared_bindings/lib/generated/base_symbols.yaml
new file mode 100644
index 0000000..ad455fa
--- /dev/null
+++ b/example/shared_bindings/lib/generated/base_symbols.yaml
@@ -0,0 +1,22 @@
+format_version: 1.0.0
+files:
+ package:shared_bindings/generated/base_gen.dart:
+ used-config:
+ ffi-native: false
+ symbols:
+ c:@E@BaseEnum:
+ name: BaseEnum
+ c:@F@base_func1:
+ name: base_func1
+ c:@S@BaseStruct1:
+ name: BaseStruct1
+ c:@S@BaseStruct2:
+ name: BaseStruct2
+ c:@U@BaseUnion1:
+ name: BaseUnion1
+ c:@U@BaseUnion2:
+ name: BaseUnion2
+ c:base.h@T@BaseTypedef1:
+ name: BaseTypedef1
+ c:base.h@T@BaseTypedef2:
+ name: BaseTypedef2
diff --git a/example/shared_bindings/pubspec.yaml b/example/shared_bindings/pubspec.yaml
new file mode 100644
index 0000000..b89304f
--- /dev/null
+++ b/example/shared_bindings/pubspec.yaml
@@ -0,0 +1,15 @@
+# Copyright (c) 2022, 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.
+
+name: shared_bindings
+
+environment:
+ sdk: '>=2.17.0 <3.0.0'
+
+dependencies:
+ ffi: ^2.0.1
+dev_dependencies:
+ ffigen:
+ path: '../../'
+ lints: ^1.0.1
diff --git a/lib/src/code_generator/func.dart b/lib/src/code_generator/func.dart
index 4496e71..590f63c 100644
--- a/lib/src/code_generator/func.dart
+++ b/lib/src/code_generator/func.dart
@@ -86,11 +86,13 @@
_exposedCFunctionTypealias = Typealias(
name: 'Native$upperCaseName',
type: functionType,
+ isInternal: true,
);
_exposedDartFunctionTypealias = Typealias(
name: 'Dart$upperCaseName',
type: functionType,
useDartType: true,
+ isInternal: true,
);
}
}
diff --git a/lib/src/code_generator/library.dart b/lib/src/code_generator/library.dart
index 7c7ce47..befdfce 100644
--- a/lib/src/code_generator/library.dart
+++ b/lib/src/code_generator/library.dart
@@ -9,6 +9,7 @@
import 'package:ffigen/src/config_provider/config_types.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as p;
+import 'package:yaml_edit/yaml_edit.dart';
import '../strings.dart' as strings;
import 'utils.dart';
@@ -138,6 +139,19 @@
}
}
+ /// Generates [file] with symbol output yaml.
+ void generateSymbolOutputFile(File file, String importPath) {
+ if (!file.existsSync()) file.createSync(recursive: true);
+ final symbolFileYamlMap = writer.generateSymbolOutputYamlMap(importPath);
+ final yamlEditor = YamlEditor("");
+ yamlEditor.update([], wrapAsYamlNode(symbolFileYamlMap));
+ var yamlString = yamlEditor.toString();
+ if (!yamlString.endsWith('\n')) {
+ yamlString += "\n";
+ }
+ file.writeAsStringSync(yamlString);
+ }
+
/// Formats a file using the Dart formatter.
void _dartFormat(String path) {
final sdkPath = getSdkPath();
diff --git a/lib/src/code_generator/typealias.dart b/lib/src/code_generator/typealias.dart
index f42e863..6a47f61 100644
--- a/lib/src/code_generator/typealias.dart
+++ b/lib/src/code_generator/typealias.dart
@@ -29,12 +29,14 @@
///
/// E.g if C type is ffi.Void func(ffi.Int32), Dart type is void func(int).
bool useDartType = false,
+ bool isInternal = false,
}) : _useDartType = useDartType,
super(
usr: usr,
name: name,
dartDoc: dartDoc,
originalName: originalName,
+ isInternal: isInternal,
);
@override
diff --git a/lib/src/code_generator/writer.dart b/lib/src/code_generator/writer.dart
index 1f054fc..748275d 100644
--- a/lib/src/code_generator/writer.dart
+++ b/lib/src/code_generator/writer.dart
@@ -4,6 +4,11 @@
import 'package:ffigen/src/code_generator.dart';
import 'package:ffigen/src/code_generator/utils.dart';
+import 'package:logging/logging.dart';
+
+import '../strings.dart' as strings;
+
+final _logger = Logger('ffigen.code_generator.writer');
/// To store generated String bindings.
class Writer {
@@ -75,6 +80,11 @@
/// Guaranteed to be a unique prefix.
String get arrayHelperClassPrefix => _arrayHelperClassPrefix;
+ /// Set true after calling [generate]. Indicates if
+ /// [generateSymbolOutputYamlMap] can be called.
+ bool get canGenerateSymbolOutput => _canGenerateSymbolOutput;
+ bool _canGenerateSymbolOutput = false;
+
/// [_usedUpNames] should contain names of all the declarations which are
/// already used. This is used to avoid name collisions.
Writer({
@@ -256,8 +266,55 @@
}
result.write(s);
+ _canGenerateSymbolOutput = true;
return result.toString();
}
+
+ Map<String, dynamic> generateSymbolOutputYamlMap(String importFilePath) {
+ final bindings = <Binding>[
+ ...noLookUpBindings,
+ ...ffiNativeBindings,
+ ...lookUpBindings
+ ];
+ if (!canGenerateSymbolOutput) {
+ throw Exception(
+ "Invalid state: generateSymbolOutputYamlMap() called before generate()");
+ }
+ final result = <String, dynamic>{};
+
+ result[strings.formatVersion] = strings.symbolFileFormatVersion;
+ result[strings.files] = <String, dynamic>{};
+ result[strings.files][importFilePath] = <String, dynamic>{};
+
+ final fileConfig = result[strings.files][importFilePath];
+ fileConfig[strings.usedConfig] = <String, dynamic>{};
+
+ // Warn for macros.
+ final hasMacroBindings = bindings.any(
+ (element) => element is Constant && element.usr.contains('@macro@'));
+ if (hasMacroBindings) {
+ _logger.info(
+ 'Removing all Macros from symbol file since they cannot be cross referenced reliably.');
+ }
+ // Remove internal bindings and macros.
+ bindings.removeWhere((element) {
+ return element.isInternal ||
+ (element is Constant && element.usr.contains('@macro@'));
+ });
+ // Sort bindings alphabetically by USR.
+ bindings.sort((a, b) => a.usr.compareTo(b.usr));
+ fileConfig[strings.usedConfig][strings.ffiNative] = bindings
+ .whereType<Func>()
+ .any((element) => element.ffiNativeConfig.enabled);
+ fileConfig[strings.symbols] = <String, dynamic>{};
+ final symbolConfig = fileConfig[strings.symbols];
+ for (final binding in bindings) {
+ symbolConfig[binding.usr] = {
+ strings.name: binding.name,
+ };
+ }
+ return result;
+ }
}
/// Manages the generated `_SymbolAddress` class.
diff --git a/lib/src/config_provider/config.dart b/lib/src/config_provider/config.dart
index 3457c91..9c094ed 100644
--- a/lib/src/config_provider/config.dart
+++ b/lib/src/config_provider/config.dart
@@ -8,6 +8,7 @@
import 'package:ffigen/src/code_generator.dart';
import 'package:logging/logging.dart';
+import 'package:package_config/package_config_types.dart';
import 'package:yaml/yaml.dart';
import '../strings.dart' as strings;
@@ -24,6 +25,10 @@
String? get filename => _filename;
String? _filename;
+ /// Package config.
+ PackageConfig? get packageConfig => _packageConfig;
+ PackageConfig? _packageConfig;
+
/// Location for llvm/lib folder.
String get libclangDylib => _libclangDylib;
late String _libclangDylib;
@@ -32,6 +37,10 @@
String get output => _output;
late String _output;
+ /// Symbol file config.
+ SymbolFile? get symbolFile => _symbolFile;
+ late SymbolFile? _symbolFile;
+
/// Language that ffigen is consuming.
Language get language => _language;
late Language _language;
@@ -98,6 +107,10 @@
Map<String, LibraryImport> get libraryImports => _libraryImports;
late Map<String, LibraryImport> _libraryImports;
+ /// Stores all the symbol file maps name to ImportedType mappings specified by user.
+ Map<String, ImportedType> get usrTypeMappings => _usrTypeMappings;
+ late Map<String, ImportedType> _usrTypeMappings;
+
/// Stores typedef name to ImportedType mappings specified by user.
Map<String, ImportedType> get typedefTypeMappings => _typedefTypeMappings;
late Map<String, ImportedType> _typedefTypeMappings;
@@ -159,11 +172,12 @@
FfiNativeConfig get ffiNativeConfig => _ffiNativeConfig;
late FfiNativeConfig _ffiNativeConfig;
- Config._(this._filename);
+ Config._(this._filename, this._packageConfig);
/// Create config from Yaml map.
- factory Config.fromYaml(YamlMap map, [String? filename]) {
- final configspecs = Config._(filename);
+ factory Config.fromYaml(YamlMap map,
+ {String? filename, PackageConfig? packageConfig}) {
+ final configspecs = Config._(filename, packageConfig);
_logger.finest('Config Map: ' + map.toString());
final specs = configspecs._getSpecs();
@@ -178,11 +192,12 @@
}
/// Create config from a file.
- factory Config.fromFile(File file) {
+ factory Config.fromFile(File file, {PackageConfig? packageConfig}) {
// Throws a [YamlException] if it's unable to parse the Yaml.
final configYaml = loadYaml(file.readAsStringSync()) as YamlMap;
- return Config.fromYaml(configYaml, file.path);
+ return Config.fromYaml(configYaml,
+ filename: file.path, packageConfig: packageConfig);
}
/// Add compiler options for clang. If [highPriority] is true these are added
@@ -245,11 +260,15 @@
_libclangDylib = result as String;
},
),
- [strings.output]: Specification<String>(
+ [strings.output]: Specification<OutputConfig>(
requirement: Requirement.yes,
validator: outputValidator,
- extractor: (dynamic value) => outputExtractor(value, filename),
- extractedResult: (dynamic result) => _output = result as String,
+ extractor: (dynamic value) =>
+ outputExtractor(value, filename, packageConfig),
+ extractedResult: (dynamic result) {
+ _output = (result as OutputConfig).output;
+ _symbolFile = result.symbolFile;
+ },
),
[strings.language]: Specification<Language>(
requirement: Requirement.no,
@@ -378,6 +397,16 @@
_libraryImports = result as Map<String, LibraryImport>;
},
),
+ [strings.import, strings.symbolFilesImport]:
+ Specification<Map<String, ImportedType>>(
+ validator: symbolFileImportValidator,
+ extractor: (value) => symbolFileImportExtractor(
+ value, _libraryImports, filename, packageConfig),
+ defaultValue: () => <String, ImportedType>{},
+ extractedResult: (dynamic result) {
+ _usrTypeMappings = result as Map<String, ImportedType>;
+ },
+ ),
[strings.typeMap, strings.typeMapTypedefs]:
Specification<Map<String, List<String>>>(
validator: typeMapValidator,
diff --git a/lib/src/config_provider/config_types.dart b/lib/src/config_provider/config_types.dart
index e3965c0..8521bdb 100644
--- a/lib/src/config_provider/config_types.dart
+++ b/lib/src/config_provider/config_types.dart
@@ -409,3 +409,17 @@
const FfiNativeConfig({required this.enabled, this.asset});
}
+
+class SymbolFile {
+ final String importPath;
+ final String output;
+
+ SymbolFile(this.importPath, this.output);
+}
+
+class OutputConfig {
+ final String output;
+ final SymbolFile? symbolFile;
+
+ OutputConfig(this.output, this.symbolFile);
+}
diff --git a/lib/src/config_provider/spec_utils.dart b/lib/src/config_provider/spec_utils.dart
index 53c8ce9..176646e 100644
--- a/lib/src/config_provider/spec_utils.dart
+++ b/lib/src/config_provider/spec_utils.dart
@@ -5,9 +5,11 @@
import 'dart:io';
import 'package:ffigen/src/code_generator.dart';
+import 'package:ffigen/src/code_generator/utils.dart';
import 'package:file/local.dart';
import 'package:glob/glob.dart';
import 'package:logging/logging.dart';
+import 'package:package_config/package_config.dart';
import 'package:path/path.dart' as p;
import 'package:quiver/pattern.dart' as quiver;
import 'package:yaml/yaml.dart';
@@ -143,6 +145,99 @@
return true;
}
+void loadImportedTypes(YamlMap fileConfig,
+ Map<String, ImportedType> usrTypeMappings, LibraryImport libraryImport) {
+ final symbols = fileConfig['symbols'] as YamlMap;
+ for (final key in symbols.keys) {
+ final usr = key as String;
+ final value = symbols[usr]! as YamlMap;
+ usrTypeMappings[usr] = ImportedType(
+ libraryImport, value['name'] as String, value['name'] as String);
+ }
+}
+
+YamlMap loadSymbolFile(String symbolFilePath, String? configFileName,
+ PackageConfig? packageConfig) {
+ final path = symbolFilePath.startsWith('package:')
+ ? packageConfig!.resolve(Uri.parse(symbolFilePath))!.toFilePath()
+ : _normalizePath(symbolFilePath, configFileName);
+
+ return loadYaml(File(path).readAsStringSync()) as YamlMap;
+}
+
+Map<String, ImportedType> symbolFileImportExtractor(
+ dynamic yamlConfig,
+ Map<String, LibraryImport> libraryImports,
+ String? configFileName,
+ PackageConfig? packageConfig) {
+ final resultMap = <String, ImportedType>{};
+ for (final item in (yamlConfig as YamlList)) {
+ String symbolFilePath;
+ if (item is String) {
+ symbolFilePath = item;
+ } else {
+ symbolFilePath = item[strings.symbolFile] as String;
+ }
+ final symbolFile =
+ loadSymbolFile(symbolFilePath, configFileName, packageConfig);
+ final formatVersion = symbolFile[strings.formatVersion] as String;
+ if (formatVersion.split('.')[0] !=
+ strings.symbolFileFormatVersion.split('.')[0]) {
+ _logger.severe(
+ 'Incompatible format versions for file $symbolFilePath: ${strings.symbolFileFormatVersion}(ours), $formatVersion(theirs).');
+ exit(1);
+ }
+ final uniqueNamer = UniqueNamer(libraryImports.keys
+ .followedBy([strings.defaultSymbolFileImportPrefix]).toSet());
+ for (final file in (symbolFile[strings.files] as YamlMap).keys) {
+ final existingImports =
+ libraryImports.values.where((element) => element.importPath == file);
+ if (existingImports.isEmpty) {
+ final name =
+ uniqueNamer.makeUnique(strings.defaultSymbolFileImportPrefix);
+ libraryImports[name] = LibraryImport(name, file as String);
+ }
+ final libraryImport = libraryImports.values.firstWhere(
+ (element) => element.importPath == file,
+ );
+ loadImportedTypes(
+ symbolFile[strings.files][file] as YamlMap, resultMap, libraryImport);
+ }
+ }
+ return resultMap;
+}
+
+bool symbolFileImportValidator(List<String> name, dynamic yamlConfig) {
+ if (!checkType<YamlList>(name, yamlConfig)) {
+ return false;
+ }
+ var result = true;
+ (yamlConfig as YamlList).asMap().forEach((idx, value) {
+ if (value is YamlMap) {
+ if (!value.keys.contains(strings.symbolFile)) {
+ result = false;
+ _logger
+ .severe('Key $name -> $idx -> ${strings.symbolFile} is required.');
+ }
+ for (final key in value.keys) {
+ if (key == strings.symbolFile) {
+ if (!checkType<String>(
+ [...name, idx.toString(), key as String], value[key])) {
+ result = false;
+ }
+ } else {
+ result = false;
+ _logger.severe('Unknown key : $name -> $idx -> $key.');
+ }
+ }
+ } else if (value is! String) {
+ result = false;
+ _logger.severe('Expected $name -> $idx should be a String or Map.');
+ }
+ });
+ return result;
+}
+
Map<String, List<String>> typeMapExtractor(dynamic yamlConfig) {
// Key - type_name, Value - [lib, cType, dartType].
final resultMap = <String, List<String>>{};
@@ -482,11 +577,91 @@
return true;
}
-String outputExtractor(dynamic value, String? configFilename) =>
- _normalizePath(value as String, configFilename);
+OutputConfig outputExtractor(
+ dynamic value, String? configFilename, PackageConfig? packageConfig) {
+ if (value is String) {
+ return OutputConfig(_normalizePath(value, configFilename), null);
+ }
+ value = value as YamlMap;
+ return OutputConfig(
+ _normalizePath((value)[strings.bindings] as String, configFilename),
+ value.containsKey(strings.symbolFile)
+ ? symbolFileOutputExtractor(
+ value[strings.symbolFile], configFilename, packageConfig)
+ : null,
+ );
+}
-bool outputValidator(List<String> name, dynamic value) =>
- checkType<String>(name, value);
+bool outputValidator(List<String> name, dynamic value) {
+ if (value is String) {
+ return true;
+ } else if (value is YamlMap) {
+ final keys = value.keys;
+ var result = true;
+ for (final key in keys) {
+ if (key == strings.bindings) {
+ if (!checkType<String>([...name, key as String], value[key])) {
+ result = false;
+ }
+ } else if (key == strings.symbolFile) {
+ result = symbolFileOutputValidator(
+ [...name, strings.symbolFile], value[key]);
+ } else {
+ result = false;
+ _logger.severe("Unknown key '$key' in '$name'.");
+ }
+ }
+ return result;
+ } else {
+ _logger.severe(
+ "Expected value of key '${name.join(' -> ')}' to be a String or Map.");
+ return false;
+ }
+}
+
+SymbolFile symbolFileOutputExtractor(
+ dynamic value, String? configFilename, PackageConfig? packageConfig) {
+ value = value as YamlMap;
+ var output = value[strings.output] as String;
+ if (Uri.parse(output).scheme != "package") {
+ _logger.warning(
+ 'Consider using a Package Uri for ${strings.symbolFile} -> ${strings.output}: $output so that external packages can use it.');
+ output = _normalizePath(output, configFilename);
+ } else {
+ output = packageConfig!.resolve(Uri.parse(output))!.toFilePath();
+ }
+ final importPath = value[strings.importPath] as String;
+ if (Uri.parse(importPath).scheme != "package") {
+ _logger.warning(
+ 'Consider using a Package Uri for ${strings.symbolFile} -> ${strings.importPath}: $importPath so that external packages can use it.');
+ }
+ return SymbolFile(importPath, output);
+}
+
+bool symbolFileOutputValidator(List<String> name, dynamic value) {
+ if (!checkType<YamlMap>(name, value)) {
+ return false;
+ }
+ if (!(value as YamlMap).containsKey(strings.output)) {
+ _logger.severe("Required '$name -> ${strings.output}'.");
+ return false;
+ }
+ if (!(value).containsKey(strings.importPath)) {
+ _logger.severe("Required '$name -> ${strings.importPath}'.");
+ return false;
+ }
+ for (final key in value.keys) {
+ if (key == strings.output || key == strings.importPath) {
+ if (!checkType<String>([...name, key as String], value[key])) {
+ return false;
+ }
+ } else {
+ _logger.severe("Unknown key '$key' in '$name'.");
+ return false;
+ }
+ }
+ return true;
+}
Language languageExtractor(dynamic value) {
if (value == strings.langC) {
diff --git a/lib/src/executables/ffigen.dart b/lib/src/executables/ffigen.dart
index b3e7c41..c37cb4f 100644
--- a/lib/src/executables/ffigen.dart
+++ b/lib/src/executables/ffigen.dart
@@ -9,6 +9,7 @@
import 'package:cli_util/cli_logging.dart' show Ansi;
import 'package:ffigen/ffigen.dart';
import 'package:logging/logging.dart';
+import 'package:package_config/package_config.dart';
import 'package:yaml/yaml.dart' as yaml;
final _logger = Logger('ffigen.ffigen');
@@ -34,7 +35,7 @@
return '${_ansi.red}$str${_ansi.none}';
}
-void main(List<String> args) {
+void main(List<String> args) async {
// Parses the cmd args. This will print usage and exit if --help was passed.
final argResult = getArgResults(args);
@@ -44,7 +45,7 @@
// Create a config object.
Config config;
try {
- config = getConfig(argResult);
+ config = getConfig(argResult, await findPackageConfig(Directory.current));
} on FormatException {
_logger.severe('Please fix configuration errors and re-run the tool.');
exit(1);
@@ -58,17 +59,25 @@
library.generateFile(gen);
_logger
.info(successPen('Finished, Bindings generated in ${gen.absolute.path}'));
+
+ if (config.symbolFile != null) {
+ final symbolFileGen = File(config.symbolFile!.output);
+ library.generateSymbolOutputFile(
+ symbolFileGen, config.symbolFile!.importPath);
+ _logger.info(successPen(
+ 'Finished, Symbol Output generated in ${symbolFileGen.absolute.path}'));
+ }
}
-Config getConfig(ArgResults result) {
+Config getConfig(ArgResults result, PackageConfig? packageConfig) {
_logger.info('Running in ${Directory.current}');
Config config;
// Parse config from yaml.
if (result.wasParsed(conf)) {
- config = getConfigFromCustomYaml(result[conf] as String);
+ config = getConfigFromCustomYaml(result[conf] as String, packageConfig);
} else {
- config = getConfigFromPubspec();
+ config = getConfigFromPubspec(packageConfig);
}
// Add compiler options from command line.
@@ -82,7 +91,7 @@
}
/// Extracts configuration from pubspec file.
-Config getConfigFromPubspec() {
+Config getConfigFromPubspec(PackageConfig? packageConfig) {
final pubspecFile = File(pubspecName);
if (!pubspecFile.existsSync()) {
@@ -101,11 +110,12 @@
_logger.severe("Couldn't find an entry for '$configKey' in $pubspecName.");
exit(1);
}
- return Config.fromYaml(bindingsConfigMap, pubspecFile.path);
+ return Config.fromYaml(bindingsConfigMap,
+ filename: pubspecFile.path, packageConfig: packageConfig);
}
/// Extracts configuration from a custom yaml file.
-Config getConfigFromCustomYaml(String yamlPath) {
+Config getConfigFromCustomYaml(String yamlPath, PackageConfig? packageConfig) {
final yamlFile = File(yamlPath);
if (!yamlFile.existsSync()) {
@@ -113,7 +123,7 @@
exit(1);
}
- return Config.fromFile(yamlFile);
+ return Config.fromFile(yamlFile, packageConfig: packageConfig);
}
/// Parses the cmd line arguments.
diff --git a/lib/src/header_parser/includer.dart b/lib/src/header_parser/includer.dart
index 415f6d9..5e55aa2 100644
--- a/lib/src/header_parser/includer.dart
+++ b/lib/src/header_parser/includer.dart
@@ -16,6 +16,8 @@
bool Function(String, bool) configIncludes) {
if (isSeenDecl(usr) || name == '') {
return false;
+ } else if (config.usrTypeMappings.containsKey(usr)) {
+ return false;
} else if (configIncludes(name, config.excludeAllByDefault)) {
return true;
} else {
diff --git a/lib/src/header_parser/type_extractor/extractor.dart b/lib/src/header_parser/type_extractor/extractor.dart
index 065e789..05691d3 100644
--- a/lib/src/header_parser/type_extractor/extractor.dart
+++ b/lib/src/header_parser/type_extractor/extractor.dart
@@ -174,11 +174,16 @@
// those two types are ABI compatible, so just return bool regardless.
return _CreateTypeFromCursorResult(BooleanType());
}
+ final usr = cursor.usr();
if (config.typedefTypeMappings.containsKey(spelling)) {
_logger.fine(' Type $spelling mapped from type-map');
return _CreateTypeFromCursorResult(
config.typedefTypeMappings[spelling]!);
}
+ if (config.usrTypeMappings.containsKey(usr)) {
+ _logger.fine(' Type $spelling mapped from usr');
+ return _CreateTypeFromCursorResult(config.usrTypeMappings[usr]!);
+ }
// Get name from supported typedef name if config allows.
if (config.useSupportedTypedefs) {
if (suportedTypedefToSuportedNativeType.containsKey(spelling)) {
@@ -249,6 +254,7 @@
if (cursorKind == clang_types.CXCursorKind.CXCursor_StructDecl ||
cursorKind == clang_types.CXCursorKind.CXCursor_UnionDecl) {
final declSpelling = cursor.spelling();
+ final declUsr = cursor.usr();
// Set includer functions according to compoundType.
final CompoundType compoundType;
@@ -268,10 +274,12 @@
}
// Also add a struct binding, if its unseen.
- // TODO(23): Check if we should auto add compound declarations.
if (compoundTypeMappings.containsKey(declSpelling)) {
_logger.fine(' Type Mapped from type-map');
return compoundTypeMappings[declSpelling]!;
+ } else if (config.usrTypeMappings.containsKey(declUsr)) {
+ _logger.fine(' Type Mapped from usr');
+ return config.usrTypeMappings[declUsr]!;
} else {
final struct = parseCompoundDeclaration(
cursor,
diff --git a/lib/src/strings.dart b/lib/src/strings.dart
index 99d10c2..4c99ba4 100644
--- a/lib/src/strings.dart
+++ b/lib/src/strings.dart
@@ -29,6 +29,10 @@
const output = 'output';
+// Sub-keys of output.
+const bindings = "bindings";
+const symbolFile = 'symbol-file';
+
const language = 'language';
// String mappings for the Language enum.
@@ -131,6 +135,29 @@
// Library imports.
const libraryImports = 'library-imports';
+// Sub Keys of symbol file.
+const symbols = 'symbols';
+
+// Symbol file yaml.
+const formatVersion = "format_version";
+
+/// Current symbol file format version.
+///
+/// This is generated when generating any symbol file. When importing any other
+/// symbol file, this version is compared according to `semantic` versioning
+/// to determine compatibility.
+const symbolFileFormatVersion = "1.0.0";
+const files = "files";
+const usedConfig = "used-config";
+
+const import = 'import';
+const defaultSymbolFileImportPrefix = '_imp';
+
+// Sub keys of import.
+const symbolFilesImport = 'symbol-files';
+// Sub-Sub keys of symbolFilesImport.
+const importPath = 'import-path';
+
final predefinedLibraryImports = {
ffiImport.name: ffiImport,
ffiPkgImport.name: ffiPkgImport
diff --git a/pubspec.yaml b/pubspec.yaml
index c13dfb5..1608ea8 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -3,7 +3,7 @@
# BSD-style license that can be found in the LICENSE file.
name: ffigen
-version: 7.1.0
+version: 7.2.0
description: Generator for FFI bindings, using LibClang to parse C header files.
repository: https://github.com/dart-lang/ffigen
@@ -20,6 +20,8 @@
cli_util: ^0.3.0
glob: ^2.0.0
file: ^6.0.0
+ package_config: ^2.1.0
+ yaml_edit: ^2.0.3
dev_dependencies:
lints: ^1.0.1
diff --git a/test/example_tests/shared_bindings_example_test.dart b/test/example_tests/shared_bindings_example_test.dart
new file mode 100644
index 0000000..290ea40
--- /dev/null
+++ b/test/example_tests/shared_bindings_example_test.dart
@@ -0,0 +1,67 @@
+// Copyright (c) 2022, 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 'package:ffigen/src/config_provider/config.dart';
+import 'package:ffigen/src/header_parser.dart';
+import 'package:ffigen/src/strings.dart' as strings;
+import 'package:logging/logging.dart';
+import 'package:test/test.dart';
+import 'package:yaml/yaml.dart';
+
+import '../test_utils.dart';
+
+void main() {
+ group('shared_bindings_example', () {
+ setUpAll(() {
+ logWarnings(Level.SEVERE);
+ });
+
+ test('a_shared_base bindings', () {
+ final config = Config.fromYaml(loadYaml('''
+${strings.name}: NativeLibraryASharedB
+${strings.description}: Bindings to `headers/a.h` with shared definitions from `headers/base.h`.
+${strings.output}: 'lib/generated/a_shared_b_gen.dart'
+${strings.headers}:
+ ${strings.entryPoints}:
+ - 'example/shared_bindings/headers/a.h'
+${strings.import}:
+ ${strings.symbolFilesImport}:
+ - 'example/shared_bindings/lib/generated/base_symbols.yaml'
+${strings.preamble}: |
+ // ignore_for_file: non_constant_identifier_names, camel_case_types
+''') as YamlMap);
+ final library = parse(config);
+
+ matchLibraryWithExpected(
+ library,
+ 'example_shared_bindings.dart',
+ ['example', 'shared_bindings', config.output],
+ );
+ });
+
+ test('base symbol file output', () {
+ final config = Config.fromYaml(loadYaml('''
+${strings.name}: NativeLibraryBase
+${strings.description}: Bindings to `headers/base.h`.
+${strings.output}:
+ ${strings.bindings}: 'lib/generated/base_gen.dart'
+ ${strings.symbolFile}:
+ ${strings.output}: 'lib/generated/base_symbols.yaml'
+ ${strings.importPath}: 'package:shared_bindings/generated/base_gen.dart'
+${strings.headers}:
+ ${strings.entryPoints}:
+ - 'example/shared_bindings/headers/base.h'
+${strings.preamble}: |
+ // ignore_for_file: non_constant_identifier_names, camel_case_types
+''') as YamlMap);
+ final library = parse(config);
+ matchLibrarySymbolFileWithExpected(
+ library,
+ 'example_shared_bindings.yaml',
+ ['example', 'shared_bindings', config.symbolFile!.output],
+ config.symbolFile!.importPath,
+ );
+ });
+ });
+}
diff --git a/test/example_tests/swift_example_test.dart b/test/example_tests/swift_example_test.dart
index 151b895..05c854c 100644
--- a/test/example_tests/swift_example_test.dart
+++ b/test/example_tests/swift_example_test.dart
@@ -46,8 +46,8 @@
final pubspecFile = File('example/swift/pubspec.yaml');
final pubspecYaml = loadYaml(pubspecFile.readAsStringSync()) as YamlMap;
- final config =
- Config.fromYaml(pubspecYaml['ffigen'] as YamlMap, pubspecFile.path);
+ final config = Config.fromYaml(pubspecYaml['ffigen'] as YamlMap,
+ filename: pubspecFile.path);
final output = parse(config).generate();
// Verify that the output contains all the methods and classes that the
diff --git a/test/test_utils.dart b/test/test_utils.dart
index 8aa8b44..93ea295 100644
--- a/test/test_utils.dart
+++ b/test/test_utils.dart
@@ -49,17 +49,52 @@
return codeNormalizer(noCR);
}
-/// Generates actual file using library and tests using [expect] with expected
+/// Generates actual file using library and tests using [expect] with expected.
///
/// This will not delete the actual debug file incase [expect] throws an error.
void matchLibraryWithExpected(
Library library, String pathForActual, List<String> pathToExpected,
{String Function(String)? codeNormalizer}) {
+ _matchFileWithExpected(
+ library: library,
+ pathForActual: pathForActual,
+ pathToExpected: pathToExpected,
+ fileWriter: ({required Library library, required File file}) =>
+ library.generateFile(file),
+ codeNormalizer: codeNormalizer,
+ );
+}
+
+/// Generates actual file using library and tests using [expect] with expected.
+///
+/// This will not delete the actual debug file incase [expect] throws an error.
+void matchLibrarySymbolFileWithExpected(Library library, String pathForActual,
+ List<String> pathToExpected, String importPath) {
+ _matchFileWithExpected(
+ library: library,
+ pathForActual: pathForActual,
+ pathToExpected: pathToExpected,
+ fileWriter: ({required Library library, required File file}) {
+ if (!library.writer.canGenerateSymbolOutput) library.generate();
+ library.generateSymbolOutputFile(file, importPath);
+ });
+}
+
+/// Generates actual file using library and tests using [expect] with expected.
+///
+/// This will not delete the actual debug file incase [expect] throws an error.
+void _matchFileWithExpected({
+ required Library library,
+ required String pathForActual,
+ required List<String> pathToExpected,
+ required void Function({required Library library, required File file})
+ fileWriter,
+ String Function(String)? codeNormalizer,
+}) {
final file = File(
path.join(strings.tmpDir, pathForActual),
);
- library.generateFile(file);
-
+ fileWriter(library: library, file: file);
try {
final actual =
_normalizeGeneratedCode(file.readAsStringSync(), codeNormalizer);