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);