Add a step for building cursor definition map. (#474)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9801108..9980c10 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+# 7.1.0
+- Handle declarations with definition accessible from a different entry-point.
+
 # 7.0.0
 
 - Fix typedef include/exclude config.
diff --git a/lib/src/header_parser/data.dart b/lib/src/header_parser/data.dart
index 1381fd3..5bd4e90 100644
--- a/lib/src/header_parser/data.dart
+++ b/lib/src/header_parser/data.dart
@@ -21,6 +21,10 @@
 Clang get clang => _clang;
 late Clang _clang;
 
+// Cursor index.
+CursorIndex get cursorIndex => _cursorIndex;
+CursorIndex _cursorIndex = CursorIndex();
+
 // Tracks seen status for bindings
 BindingsIndex get bindingsIndex => _bindingsIndex;
 BindingsIndex _bindingsIndex = BindingsIndex();
@@ -47,6 +51,7 @@
   _incrementalNamer = IncrementalNamer();
   _savedMacros = {};
   _unnamedEnumConstants = [];
+  _cursorIndex = CursorIndex();
   _bindingsIndex = BindingsIndex();
   _objCBuiltInFunctions = ObjCBuiltInFunctions();
 }
diff --git a/lib/src/header_parser/parser.dart b/lib/src/header_parser/parser.dart
index 837d158..080d2f8 100644
--- a/lib/src/header_parser/parser.dart
+++ b/lib/src/header_parser/parser.dart
@@ -82,6 +82,9 @@
   // Log all headers for user.
   _logger.info('Input Headers: ${config.headers.entryPoints}');
 
+  final tuList = <Pointer<clang_types.CXTranslationUnitImpl>>[];
+
+  // Parse all translation units from entry points.
   for (final headerLocation in config.headers.entryPoints) {
     _logger.fine('Creating TranslationUnit for header: $headerLocation');
 
@@ -105,11 +108,24 @@
     }
 
     logTuDiagnostics(tu, _logger, headerLocation);
-    final rootCursor = clang.clang_getTranslationUnitCursor(tu);
+    tuList.add(tu);
+  }
 
+  final tuCursors =
+      tuList.map((tu) => clang.clang_getTranslationUnitCursor(tu));
+
+  // Build usr to CXCusror map from translation units.
+  for (final rootCursor in tuCursors) {
+    buildUsrCursorDefinitionMap(rootCursor);
+  }
+
+  // Parse definitions from translation units.
+  for (final rootCursor in tuCursors) {
     bindings.addAll(parseTranslationUnit(rootCursor));
+  }
 
-    // Cleanup.
+  // Dispose translation units.
+  for (final tu in tuList) {
     clang.clang_disposeTranslationUnit(tu);
   }
 
diff --git a/lib/src/header_parser/sub_parsers/compounddecl_parser.dart b/lib/src/header_parser/sub_parsers/compounddecl_parser.dart
index 81c9761..6d98247 100644
--- a/lib/src/header_parser/sub_parsers/compounddecl_parser.dart
+++ b/lib/src/header_parser/sub_parsers/compounddecl_parser.dart
@@ -106,9 +106,7 @@
   }
 
   // Parse the cursor definition instead, if this is a forward declaration.
-  if (isForwardDeclaration(cursor)) {
-    cursor = clang.clang_getCursorDefinition(cursor);
-  }
+  cursor = cursorIndex.getDefinition(cursor);
   final declUsr = cursor.usr();
   final String declName;
 
@@ -163,6 +161,7 @@
   /// generate these as opaque if `dependency-only` was set to opaque).
   bool pointerReference = false,
 }) {
+  cursor = cursorIndex.getDefinition(cursor);
   final compoundType = compound.compoundType;
 
   // Skip dependencies if already seen OR user has specified `dependency-only`
diff --git a/lib/src/header_parser/sub_parsers/enumdecl_parser.dart b/lib/src/header_parser/sub_parsers/enumdecl_parser.dart
index b33e3e7..76cccd2 100644
--- a/lib/src/header_parser/sub_parsers/enumdecl_parser.dart
+++ b/lib/src/header_parser/sub_parsers/enumdecl_parser.dart
@@ -40,9 +40,7 @@
   _stack.push(_ParsedEnum());
 
   // Parse the cursor definition instead, if this is a forward declaration.
-  if (isForwardDeclaration(cursor)) {
-    cursor = clang.clang_getCursorDefinition(cursor);
-  }
+  cursor = cursorIndex.getDefinition(cursor);
 
   final enumUsr = cursor.usr();
   final String enumName;
diff --git a/lib/src/header_parser/translation_unit_parser.dart b/lib/src/header_parser/translation_unit_parser.dart
index b34b1b1..ea9d594 100644
--- a/lib/src/header_parser/translation_unit_parser.dart
+++ b/lib/src/header_parser/translation_unit_parser.dart
@@ -27,6 +27,12 @@
                 clang_types.CXCursor, clang_types.CXCursor, Pointer<Void>)>>?
     _rootCursorVisitorPtr;
 
+Pointer<
+        NativeFunction<
+            Int32 Function(
+                clang_types.CXCursor, clang_types.CXCursor, Pointer<Void>)>>?
+    _cursorDefinitionVisitorPtr;
+
 /// Parses the translation unit and returns the generated bindings.
 Set<Binding> parseTranslationUnit(clang_types.CXCursor translationUnitCursor) {
   _bindings = {};
@@ -94,3 +100,29 @@
   final t = getCodeGenType(cursor.type(), ignoreFilter: false);
   return t is BindingType ? t : null;
 }
+
+/// Visits all cursors and builds a map of usr and [CXCursor].
+void buildUsrCursorDefinitionMap(clang_types.CXCursor translationUnitCursor) {
+  _bindings = {};
+  final resultCode = clang.clang_visitChildren(
+    translationUnitCursor,
+    _cursorDefinitionVisitorPtr ??= Pointer.fromFunction(
+        _definitionCursorVisitor, exceptional_visitor_return),
+    nullptr,
+  );
+
+  visitChildrenResultChecker(resultCode);
+}
+
+/// Child visitor invoked on translationUnitCursor [CXCursorKind.CXCursor_TranslationUnit].
+int _definitionCursorVisitor(clang_types.CXCursor cursor,
+    clang_types.CXCursor parent, Pointer<Void> clientData) {
+  try {
+    cursorIndex.saveDefinition(cursor);
+  } catch (e, s) {
+    _logger.severe(e);
+    _logger.severe(s);
+    rethrow;
+  }
+  return clang_types.CXChildVisitResult.CXChildVisit_Continue;
+}
diff --git a/lib/src/header_parser/utils.dart b/lib/src/header_parser/utils.dart
index 5f9bd33..614d5e5 100644
--- a/lib/src/header_parser/utils.dart
+++ b/lib/src/header_parser/utils.dart
@@ -13,6 +13,8 @@
 import 'data.dart';
 import 'type_extractor/extractor.dart';
 
+final _logger = Logger('ffigen.header_parser.utils');
+
 const exceptional_visitor_return =
     clang_types.CXChildVisitResult.CXChildVisit_Break;
 
@@ -242,11 +244,6 @@
   return sb.toString().trim();
 }
 
-bool isForwardDeclaration(clang_types.CXCursor cursor) {
-  return clang.clang_Cursor_isNull(clang.clang_getCursorDefinition(cursor)) ==
-      0;
-}
-
 extension CXTypeExt on clang_types.CXType {
   /// Get code_gen [Type] representation of [clang_types.CXType].
   Type toCodeGenType() {
@@ -399,3 +396,43 @@
   void addObjCBlockToSeen(String key, ObjCBlock t) => _objcBlocks[key] = t;
   ObjCBlock? getSeenObjCBlock(String key) => _objcBlocks[key];
 }
+
+class CursorIndex {
+  final _usrCursorDefinition = <String, clang_types.CXCursor>{};
+
+  /// Returns the Cursor definition (if found) or itself.
+  clang_types.CXCursor getDefinition(clang_types.CXCursor cursor) {
+    final cursorDefinition = clang.clang_getCursorDefinition(cursor);
+    if (clang.clang_Cursor_isNull(cursorDefinition) == 0) {
+      return cursorDefinition;
+    } else {
+      final usr = cursor.usr();
+      if (_usrCursorDefinition.containsKey(usr)) {
+        return _usrCursorDefinition[cursor.usr()]!;
+      } else {
+        _logger.warning(
+            "No definition found for declaration - ${cursor.completeStringRepr()}");
+        return cursor;
+      }
+    }
+  }
+
+  /// Saves cursor definition based on its kind.
+  void saveDefinition(clang_types.CXCursor cursor) {
+    switch (cursor.kind) {
+      case clang_types.CXCursorKind.CXCursor_StructDecl:
+      case clang_types.CXCursorKind.CXCursor_UnionDecl:
+      case clang_types.CXCursorKind.CXCursor_EnumDecl:
+        final usr = cursor.usr();
+        if (!_usrCursorDefinition.containsKey(usr)) {
+          final cursorDefinition = clang.clang_getCursorDefinition(cursor);
+          if (clang.clang_Cursor_isNull(cursorDefinition) == 0) {
+            _usrCursorDefinition[usr] = cursorDefinition;
+          } else {
+            _logger.finest(
+                "Missing cursor definition in current translation unit: ${cursor.completeStringRepr()}");
+          }
+        }
+    }
+  }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 3ac3822..c13dfb5 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.0.0
+version: 7.1.0
 description: Generator for FFI bindings, using LibClang to parse C header files.
 repository: https://github.com/dart-lang/ffigen
 
diff --git a/test/header_parser_tests/separate_definition.h b/test/header_parser_tests/separate_definition.h
new file mode 100644
index 0000000..4c8584b
--- /dev/null
+++ b/test/header_parser_tests/separate_definition.h
@@ -0,0 +1,11 @@
+// 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.
+
+// Forward declaration to SeparatelyDefinedStruct, with definition in
+// separate_definition_base.h
+struct SeparatelyDefinedStruct;
+
+void func(struct SeparatelyDefinedStruct s);
+
+void func2(struct SeparatelyDefinedStruct *s);
diff --git a/test/header_parser_tests/separate_definition_base.h b/test/header_parser_tests/separate_definition_base.h
new file mode 100644
index 0000000..2342bdf
--- /dev/null
+++ b/test/header_parser_tests/separate_definition_base.h
@@ -0,0 +1,8 @@
+// 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 SeparatelyDefinedStruct{
+    int a;
+    int b;
+};
diff --git a/test/header_parser_tests/separate_definition_test.dart b/test/header_parser_tests/separate_definition_test.dart
new file mode 100644
index 0000000..6b62ade
--- /dev/null
+++ b/test/header_parser_tests/separate_definition_test.dart
@@ -0,0 +1,50 @@
+// Copyright (c) 2020, 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/code_generator.dart';
+import 'package:ffigen/src/config_provider.dart';
+import 'package:ffigen/src/header_parser.dart' as parser;
+import 'package:ffigen/src/strings.dart' as strings;
+import 'package:logging/logging.dart';
+import 'package:test/test.dart';
+import 'package:yaml/yaml.dart' as yaml;
+
+import '../test_utils.dart';
+
+late Library actual, expected;
+
+void main() {
+  group('separate_definition', () {
+    setUpAll(() {
+      logWarnings(Level.SEVERE);
+    });
+    test('different header order', () {
+      final entryPoints = [
+        "test/header_parser_tests/separate_definition_base.h",
+        "test/header_parser_tests/separate_definition.h"
+      ];
+      final library1String = parser.parse(_makeConfig(entryPoints)).generate();
+      final library2String =
+          parser.parse(_makeConfig(entryPoints.reversed.toList())).generate();
+
+      expect(library1String, library2String);
+    });
+  });
+}
+
+Config _makeConfig(List<String> entryPoints) {
+  final entryPointBuilder = StringBuffer();
+  for (final ep in entryPoints) {
+    entryPointBuilder.writeln("    - $ep");
+  }
+  final config = Config.fromYaml(yaml.loadYaml('''
+${strings.name}: 'Bindings'
+${strings.output}: 'unused'
+
+${strings.headers}:
+  ${strings.entryPoints}:
+${entryPointBuilder.toString()}
+''') as yaml.YamlMap);
+  return config;
+}