Support for Packed struct (#205)

diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml
index 2ee8d6a..642fed7 100644
--- a/.github/workflows/test-package.yml
+++ b/.github/workflows/test-package.yml
@@ -19,7 +19,7 @@
     strategy:
       fail-fast: false
       matrix:
-        sdk: [2.12.0]
+        sdk: [2.13.0-211.6.beta] # TODO: revert to 2.13.
     steps:
       - uses: actions/checkout@v2
       - uses: dart-lang/setup-dart@v1.0
@@ -43,7 +43,7 @@
       - uses: actions/checkout@v2
       - uses: dart-lang/setup-dart@v1.0
         with:
-          sdk: 2.12.0
+          sdk: 2.13.0-211.6.beta # TODO: revert to 2.13.
       - name: Install dependencies
         run: dart pub get
       - name: Install libclang-10-dev
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1b811cc..82b5807 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+# 2.5.0-beta.1
+- Added support for `Packed` structs. Packed annotations are generated
+automatically but can be overriden using `structs -> pack` config.
+- Updated sdk constraints to `>=2.13.0-211.6.beta`.
+
 # 2.4.2
 - Fix issues due to declarations having duplicate names.
 - Fix name conflict of declaration with ffi library prefix.
diff --git a/README.md b/README.md
index cc1aeac..d8cac98 100644
--- a/README.md
+++ b/README.md
@@ -232,6 +232,27 @@
   </td>
   </tr>
   <tr>
+    <td>structs -> pack</td>
+    <td>Override the @Packed(X) annotation for generated structs.<br><br>
+    <i>Options - none, 1, 2, 4, 8, 16</i><br>
+    You can use RegExp to match with the <b>generated</b> names.<br><br>
+    Note: Ffigen can only reliably identify packing specified using
+    __attribute__((__packed__)). However, structs packed using
+    `#pragma pack(...)` or any other way could <i>potentially</i> be incorrect
+    in which case you can override the generated annotations.
+    </td>
+    <td>
+
+```yaml
+structs:
+  pack:
+    # Matches with the generated name.
+    'NoPackStruct': none # No packing
+    '.*': 1 # Pack all structs with value 1
+```
+  </td>
+  </tr>
+  <tr>
     <td>array-workaround</td>
     <td>Should generate workaround for fixed arrays in Structs. See <a href="#array-workaround">Array Workaround</a><br>
       <b>Default: false</b>
diff --git a/lib/src/code_generator/library.dart b/lib/src/code_generator/library.dart
index e37a062..c29313f 100644
--- a/lib/src/code_generator/library.dart
+++ b/lib/src/code_generator/library.dart
@@ -5,9 +5,11 @@
 import 'dart:io';
 
 import 'package:cli_util/cli_util.dart';
+import 'package:ffigen/src/config_provider/config_types.dart';
 import 'package:logging/logging.dart';
 import 'package:path/path.dart' as p;
 import 'binding.dart';
+import 'struc.dart';
 import 'utils.dart';
 import 'writer.dart';
 
@@ -28,6 +30,7 @@
     String? header,
     bool dartBool = true,
     bool sort = false,
+    StructPackingOverride? packingOverride,
   }) {
     if (sort) _sort();
 
@@ -38,6 +41,16 @@
       _resolveIfNameConflicts(declConflictHandler, b);
     }
 
+    // Override pack values according to config. We do this after declaration
+    // conflicts have been handled so that users can target the generated names.
+    if (packingOverride != null) {
+      for (final b in bindings) {
+        if (b is Struc && packingOverride.isOverriden(b.name)) {
+          b.pack = packingOverride.getOverridenPackValue(b.name);
+        }
+      }
+    }
+
     // Seperate bindings which require lookup.
     final lookUpBindings = bindings.whereType<LookUpBinding>().toList();
     final noLookUpBindings = bindings.whereType<NoLookUpBinding>().toList();
diff --git a/lib/src/code_generator/struc.dart b/lib/src/code_generator/struc.dart
index 2fc96d6..37edfa0 100644
--- a/lib/src/code_generator/struc.dart
+++ b/lib/src/code_generator/struc.dart
@@ -44,6 +44,9 @@
 
   bool get isOpaque => members.isEmpty;
 
+  /// Value for `@Packed(X)` annotation. Can be null(no packing), 1, 2, 4, 8, 16.
+  int? pack;
+
   /// Marker for checking if the dependencies are parsed.
   bool parsedDependencies = false;
 
@@ -52,6 +55,7 @@
     String? originalName,
     required String name,
     this.isInComplete = false,
+    this.pack,
     String? dartDoc,
     List<Member>? members,
   })  : members = members ?? [],
@@ -105,6 +109,10 @@
     /// to have the same name as the class.
     final localUniqueNamer = UniqueNamer({enclosingClassName});
 
+    /// Write @Packed(X) annotation if struct is packed.
+    if (pack != null) {
+      s.write('@${w.ffiLibraryPrefix}.Packed($pack)\n');
+    }
     // Write class declaration.
     s.write(
         'class $enclosingClassName extends ${w.ffiLibraryPrefix}.${isOpaque ? 'Opaque' : 'Struct'}{\n');
diff --git a/lib/src/config_provider/config.dart b/lib/src/config_provider/config.dart
index 2830366..cacc7fa 100644
--- a/lib/src/config_provider/config.dart
+++ b/lib/src/config_provider/config.dart
@@ -80,6 +80,10 @@
   StructDependencies get structDependencies => _structDependencies;
   late StructDependencies _structDependencies;
 
+  /// Holds config for how struct packing should be overriden.
+  StructPackingOverride get structPackingOverride => _structPackingOverride;
+  late StructPackingOverride _structPackingOverride;
+
   /// If tool should generate array workarounds.
   ///
   /// If false(default), structs with inline array members will have all its
@@ -335,6 +339,15 @@
         extractedResult: (dynamic result) =>
             _structDependencies = result as StructDependencies,
       ),
+      [strings.structs, strings.structPack]:
+          Specification<StructPackingOverride>(
+        requirement: Requirement.no,
+        validator: structPackingOverrideValidator,
+        extractor: structPackingOverrideExtractor,
+        defaultValue: () => StructPackingOverride(),
+        extractedResult: (dynamic result) =>
+            _structPackingOverride = result as StructPackingOverride,
+      ),
       [strings.arrayWorkaround]: Specification<bool>(
         requirement: Requirement.no,
         validator: booleanValidator,
diff --git a/lib/src/config_provider/config_types.dart b/lib/src/config_provider/config_types.dart
index 0240c19..7fcc352 100644
--- a/lib/src/config_provider/config_types.dart
+++ b/lib/src/config_provider/config_types.dart
@@ -31,6 +31,35 @@
 
 enum StructDependencies { full, opaque }
 
+/// Holds config for how Structs Packing will be overriden.
+class StructPackingOverride {
+  final Map<RegExp, int?> _matcherMap;
+
+  StructPackingOverride({Map<RegExp, int?>? matcherMap})
+      : _matcherMap = matcherMap ?? {};
+
+  /// Returns true if the user has overriden the pack value.
+  bool isOverriden(String name) {
+    for (final key in _matcherMap.keys) {
+      if (quiver.matchesFull(key, name)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /// Returns pack value for [name]. Ensure that value [isOverriden] before
+  /// using the returned value.
+  int? getOverridenPackValue(String name) {
+    for (final opv in _matcherMap.entries) {
+      if (quiver.matchesFull(opv.key, name)) {
+        return opv.value;
+      }
+    }
+    return null;
+  }
+}
+
 /// Represents a single specification in configurations.
 ///
 /// [E] is the return type of the extractedResult.
diff --git a/lib/src/config_provider/spec_utils.dart b/lib/src/config_provider/spec_utils.dart
index 6875af1..ff4a44a 100644
--- a/lib/src/config_provider/spec_utils.dart
+++ b/lib/src/config_provider/spec_utils.dart
@@ -699,3 +699,30 @@
   }
   return result;
 }
+
+StructPackingOverride structPackingOverrideExtractor(dynamic value) {
+  final matcherMap = <RegExp, int?>{};
+  for (final key in value.keys) {
+    matcherMap[RegExp(key as String, dotAll: true)] =
+        strings.packingValuesMap[value[key]];
+  }
+  return StructPackingOverride(matcherMap: matcherMap);
+}
+
+bool structPackingOverrideValidator(List<String> name, dynamic value) {
+  var _result = true;
+
+  if (!checkType<YamlMap>([...name], value)) {
+    _result = false;
+  } else {
+    for (final key in value.keys) {
+      if (!(strings.packingValuesMap.keys.contains(value[key]))) {
+        _logger.severe(
+            "'$name -> $key' must be one of the following - ${strings.packingValuesMap.keys.toList()}");
+        _result = false;
+      }
+    }
+  }
+
+  return _result;
+}
diff --git a/lib/src/header_parser/clang_bindings/clang_bindings.dart b/lib/src/header_parser/clang_bindings/clang_bindings.dart
index 71c02c9..2c69fa7 100644
--- a/lib/src/header_parser/clang_bindings/clang_bindings.dart
+++ b/lib/src/header_parser/clang_bindings/clang_bindings.dart
@@ -376,6 +376,21 @@
   late final _dart_clang_getCursorKind _clang_getCursorKind =
       _clang_getCursorKind_ptr.asFunction<_dart_clang_getCursorKind>();
 
+  /// Determine whether the given cursor has any attributes.
+  int clang_Cursor_hasAttrs(
+    CXCursor C,
+  ) {
+    return _clang_Cursor_hasAttrs(
+      C,
+    );
+  }
+
+  late final _clang_Cursor_hasAttrs_ptr =
+      _lookup<ffi.NativeFunction<_c_clang_Cursor_hasAttrs>>(
+          'clang_Cursor_hasAttrs');
+  late final _dart_clang_Cursor_hasAttrs _clang_Cursor_hasAttrs =
+      _clang_Cursor_hasAttrs_ptr.asFunction<_dart_clang_Cursor_hasAttrs>();
+
   /// Retrieve the physical location of the source constructor referenced
   /// by the given cursor.
   ///
@@ -772,6 +787,30 @@
   late final _dart_clang_Type_getNamedType _clang_Type_getNamedType =
       _clang_Type_getNamedType_ptr.asFunction<_dart_clang_Type_getNamedType>();
 
+  /// Return the alignment of a type in bytes as per C++[expr.alignof]
+  /// standard.
+  ///
+  /// If the type declaration is invalid, CXTypeLayoutError_Invalid is returned.
+  /// If the type declaration is an incomplete type, CXTypeLayoutError_Incomplete
+  /// is returned.
+  /// If the type declaration is a dependent type, CXTypeLayoutError_Dependent is
+  /// returned.
+  /// If the type declaration is not a constant size type,
+  /// CXTypeLayoutError_NotConstantSize is returned.
+  int clang_Type_getAlignOf(
+    CXType T,
+  ) {
+    return _clang_Type_getAlignOf(
+      T,
+    );
+  }
+
+  late final _clang_Type_getAlignOf_ptr =
+      _lookup<ffi.NativeFunction<_c_clang_Type_getAlignOf>>(
+          'clang_Type_getAlignOf');
+  late final _dart_clang_Type_getAlignOf _clang_Type_getAlignOf =
+      _clang_Type_getAlignOf_ptr.asFunction<_dart_clang_Type_getAlignOf>();
+
   /// Determine whether the given cursor represents an anonymous
   /// tag or namespace
   int clang_Cursor_isAnonymous(
@@ -2690,6 +2729,14 @@
   CXCursor arg0,
 );
 
+typedef _c_clang_Cursor_hasAttrs = ffi.Uint32 Function(
+  CXCursor C,
+);
+
+typedef _dart_clang_Cursor_hasAttrs = int Function(
+  CXCursor C,
+);
+
 typedef _c_clang_getCursorLocation = CXSourceLocation Function(
   CXCursor arg0,
 );
@@ -2870,6 +2917,14 @@
   CXType T,
 );
 
+typedef _c_clang_Type_getAlignOf = ffi.Int64 Function(
+  CXType T,
+);
+
+typedef _dart_clang_Type_getAlignOf = int Function(
+  CXType T,
+);
+
 typedef _c_clang_Cursor_isAnonymous = ffi.Uint32 Function(
   CXCursor C,
 );
diff --git a/lib/src/header_parser/parser.dart b/lib/src/header_parser/parser.dart
index ff28679..ddcd9c6 100644
--- a/lib/src/header_parser/parser.dart
+++ b/lib/src/header_parser/parser.dart
@@ -30,6 +30,7 @@
     header: config.preamble,
     dartBool: config.dartBool,
     sort: config.sort,
+    packingOverride: config.structPackingOverride,
   );
 
   return library;
diff --git a/lib/src/header_parser/sub_parsers/structdecl_parser.dart b/lib/src/header_parser/sub_parsers/structdecl_parser.dart
index d9c4023..c27d841 100644
--- a/lib/src/header_parser/sub_parsers/structdecl_parser.dart
+++ b/lib/src/header_parser/sub_parsers/structdecl_parser.dart
@@ -8,6 +8,7 @@
 import 'package:ffigen/src/config_provider/config_types.dart';
 import 'package:logging/logging.dart';
 
+import '../../strings.dart' as strings;
 import '../clang_bindings/clang_bindings.dart' as clang_types;
 import '../data.dart';
 import '../includer.dart';
@@ -33,6 +34,39 @@
       (dartHandleMember && config.useDartHandle) ||
       incompleteStructMember;
 
+  // A struct without any attribute is definitely not packed. #pragma pack(...)
+  // also adds an attribute, but it's unexposed and cannot be travesed.
+  bool hasAttr = false;
+  // A struct which as a __packed__ attribute is definitely packed.
+  bool hasPackedAttr = false;
+  // Stores the maximum alignment from all the children.
+  int maxChildAlignment = 0;
+  // Alignment of this struct.
+  int allignment = 0;
+
+  bool get _isPacked {
+    if (!hasAttr || isInComplete) return false;
+    if (hasPackedAttr) return true;
+
+    return maxChildAlignment > allignment;
+  }
+
+  /// Returns pack value of a struct depending on config, returns null for no
+  /// packing.
+  int? get packValue {
+    if (_isPacked) {
+      if (strings.packingValuesMap.containsKey(allignment)) {
+        return allignment;
+      } else {
+        _logger.warning(
+            'Unsupported pack value "$allignment" for Struct "${struc!.name}".');
+        return null;
+      }
+    } else {
+      return null;
+    }
+  }
+
   _ParsedStruc();
 }
 
@@ -62,7 +96,6 @@
   if (isForwardDeclaration(cursor)) {
     cursor = clang.clang_getCursorDefinition(cursor);
   }
-
   final structUsr = cursor.usr();
   final structName = name ?? cursor.spelling();
 
@@ -119,8 +152,8 @@
 }
 
 void _setStructMembers(clang_types.CXCursor cursor) {
-  _stack.top.arrayMember = false;
-  _stack.top.unimplementedMemberType = false;
+  _stack.top.hasAttr = clang.clang_Cursor_hasAttrs(cursor) != 0;
+  _stack.top.allignment = cursor.type().alignment();
 
   final resultCode = clang.clang_visitChildren(
     cursor,
@@ -129,6 +162,10 @@
     nullptr,
   );
 
+  _logger.finest(
+      'Opaque: ${_stack.top.isInComplete}, HasAttr: ${_stack.top.hasAttr}, AlignValue: ${_stack.top.allignment}, MaxChildAlignValue: ${_stack.top.maxChildAlignment}, PackValue: ${_stack.top.packValue}.');
+  _stack.top.struc!.pack = _stack.top.packValue;
+
   visitChildrenResultChecker(resultCode);
 
   if (_stack.top.arrayMember && !config.arrayWorkaround) {
@@ -183,6 +220,12 @@
     if (cursor.kind == clang_types.CXCursorKind.CXCursor_FieldDecl) {
       _logger.finer('===== member: ${cursor.completeStringRepr()}');
 
+      // Set maxChildAlignValue.
+      final align = cursor.type().alignment();
+      if (align > _stack.top.maxChildAlignment) {
+        _stack.top.maxChildAlignment = align;
+      }
+
       final mt = cursor.type().toCodeGenType();
       if (mt.broadType == BroadType.ConstantArray) {
         _stack.top.arrayMember = true;
@@ -219,6 +262,8 @@
           type: mt,
         ),
       );
+    } else if (cursor.kind == clang_types.CXCursorKind.CXCursor_PackedAttr) {
+      _stack.top.hasPackedAttr = true;
     }
   } catch (e, s) {
     _logger.severe(e);
diff --git a/lib/src/header_parser/utils.dart b/lib/src/header_parser/utils.dart
index e1b205c..fda7f65 100644
--- a/lib/src/header_parser/utils.dart
+++ b/lib/src/header_parser/utils.dart
@@ -236,6 +236,10 @@
     return clang.clang_getTypeKindSpelling(kind()).toStringAndDispose();
   }
 
+  int alignment() {
+    return clang.clang_Type_getAlignOf(this);
+  }
+
   /// For debugging: returns [spelling] [kind] [kindSpelling].
   String completeStringRepr() {
     final s =
diff --git a/lib/src/strings.dart b/lib/src/strings.dart
index 2f592a2..76ed940 100644
--- a/lib/src/strings.dart
+++ b/lib/src/strings.dart
@@ -64,6 +64,16 @@
 const fullStructDependencies = 'full';
 const opaqueStructDependencies = 'opaque';
 
+const structPack = 'pack';
+const Map<Object, int?> packingValuesMap = {
+  'none': null,
+  1: 1,
+  2: 2,
+  4: 4,
+  8: 8,
+  16: 16,
+};
+
 const sizemap = 'size-map';
 const typedefmap = 'typedef-map';
 
diff --git a/pubspec.yaml b/pubspec.yaml
index d5ad721..e761291 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -3,12 +3,12 @@
 # BSD-style license that can be found in the LICENSE file.
 
 name: ffigen
-version: 2.4.2
+version: 2.5.0-beta.1
 homepage: https://github.com/dart-lang/ffigen
 description: Generator for FFI bindings, using LibClang to parse C header files.
 
 environment:
-  sdk: '>=2.12.0 <3.0.0'
+  sdk: '>=2.13.0-211.6.beta <3.0.0'
 
 dependencies:
   ffi: ^1.0.0
diff --git a/test/code_generator_tests/code_generator_test.dart b/test/code_generator_tests/code_generator_test.dart
index 806ae22..7b0fa29 100644
--- a/test/code_generator_tests/code_generator_test.dart
+++ b/test/code_generator_tests/code_generator_test.dart
@@ -378,6 +378,32 @@
     );
     _matchLib(library, 'sort_bindings');
   });
+  test('Pack Structs', () {
+    final library = Library(
+      name: 'Bindings',
+      bindings: [
+        Struc(name: 'NoPacking', pack: null, members: [
+          Member(name: 'a', type: Type.nativeType(SupportedNativeType.Char)),
+        ]),
+        Struc(name: 'Pack1', pack: 1, members: [
+          Member(name: 'a', type: Type.nativeType(SupportedNativeType.Char)),
+        ]),
+        Struc(name: 'Pack2', pack: 2, members: [
+          Member(name: 'a', type: Type.nativeType(SupportedNativeType.Char)),
+        ]),
+        Struc(name: 'Pack2', pack: 4, members: [
+          Member(name: 'a', type: Type.nativeType(SupportedNativeType.Char)),
+        ]),
+        Struc(name: 'Pack2', pack: 8, members: [
+          Member(name: 'a', type: Type.nativeType(SupportedNativeType.Char)),
+        ]),
+        Struc(name: 'Pack16', pack: 16, members: [
+          Member(name: 'a', type: Type.nativeType(SupportedNativeType.Char)),
+        ]),
+      ],
+    );
+    _matchLib(library, 'packed_structs');
+  });
 }
 
 /// Utility to match expected bindings to the generated bindings.
diff --git a/test/code_generator_tests/expected_bindings/_expected_packed_structs_bindings.dart b/test/code_generator_tests/expected_bindings/_expected_packed_structs_bindings.dart
new file mode 100644
index 0000000..9c647d7
--- /dev/null
+++ b/test/code_generator_tests/expected_bindings/_expected_packed_structs_bindings.dart
@@ -0,0 +1,39 @@
+// AUTO GENERATED FILE, DO NOT EDIT.
+//
+// Generated by `package:ffigen`.
+import 'dart:ffi' as ffi;
+
+class NoPacking extends ffi.Struct {
+  @ffi.Uint8()
+  external int a;
+}
+
+@ffi.Packed(1)
+class Pack1 extends ffi.Struct {
+  @ffi.Uint8()
+  external int a;
+}
+
+@ffi.Packed(2)
+class Pack2 extends ffi.Struct {
+  @ffi.Uint8()
+  external int a;
+}
+
+@ffi.Packed(4)
+class Pack2_1 extends ffi.Struct {
+  @ffi.Uint8()
+  external int a;
+}
+
+@ffi.Packed(8)
+class Pack2_2 extends ffi.Struct {
+  @ffi.Uint8()
+  external int a;
+}
+
+@ffi.Packed(16)
+class Pack16 extends ffi.Struct {
+  @ffi.Uint8()
+  external int a;
+}
diff --git a/test/config_tests/packed_struct_override_test.dart b/test/config_tests/packed_struct_override_test.dart
new file mode 100644
index 0000000..fd8be93
--- /dev/null
+++ b/test/config_tests/packed_struct_override_test.dart
@@ -0,0 +1,62 @@
+// Copyright (c) 2021, 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/ffigen.dart';
+import 'package:ffigen/src/code_generator.dart';
+import 'package:ffigen/src/strings.dart' as strings;
+import 'package:yaml/yaml.dart' as yaml;
+import 'package:test/test.dart';
+
+import '../test_utils.dart';
+
+late Library actual, expected;
+
+void main() {
+  group('packed_struct_override_test', () {
+    test('Invalid Packed Config values', () {
+      const baseYaml = '''${strings.name}: 'NativeLibrary'
+${strings.description}: 'Packed Struct Override Test'
+${strings.output}: 'unused'
+${strings.headers}:
+  ${strings.entryPoints}:
+    - 'test/header_parser_tests/packed_structs.h'
+${strings.structs}:
+  ${strings.structPack}:
+    ''';
+      expect(
+          () => Config.fromYaml(
+              yaml.loadYaml(baseYaml + "'.*': null") as yaml.YamlMap),
+          throwsA(TypeMatcher<FormatException>()));
+      expect(
+          () => Config.fromYaml(
+              yaml.loadYaml(baseYaml + "'.*': 3") as yaml.YamlMap),
+          throwsA(TypeMatcher<FormatException>()));
+      expect(
+          () => Config.fromYaml(
+              yaml.loadYaml(baseYaml + "'.*': 32") as yaml.YamlMap),
+          throwsA(TypeMatcher<FormatException>()));
+    });
+    test('Override values', () {
+      final config = Config.fromYaml(yaml.loadYaml('''
+${strings.name}: 'NativeLibrary'
+${strings.description}: 'Packed Struct Override Test'
+${strings.output}: 'unused'
+${strings.headers}:
+  ${strings.entryPoints}:
+    - 'test/header_parser_tests/packed_structs.h'
+${strings.structs}:
+  ${strings.structPack}:
+    'Normal.*': 1
+    'StructWithAttr': 2
+    'PackedAttr': none
+        ''') as yaml.YamlMap);
+
+      final library = parse(config);
+
+      expect((library.getBinding('NormalStruct1') as Struc).pack, 1);
+      expect((library.getBinding('StructWithAttr') as Struc).pack, 2);
+      expect((library.getBinding('PackedAttr') as Struc).pack, null);
+    });
+  });
+}
diff --git a/test/header_parser_tests/expected_bindings/_expected_packed_structs_bindings.dart b/test/header_parser_tests/expected_bindings/_expected_packed_structs_bindings.dart
new file mode 100644
index 0000000..d27adc4
--- /dev/null
+++ b/test/header_parser_tests/expected_bindings/_expected_packed_structs_bindings.dart
@@ -0,0 +1,49 @@
+// AUTO GENERATED FILE, DO NOT EDIT.
+//
+// Generated by `package:ffigen`.
+import 'dart:ffi' as ffi;
+
+class NormalStruct1 extends ffi.Struct {
+  @ffi.Int8()
+  external int a;
+}
+
+/// Should not be packed.
+class StructWithAttr extends ffi.Struct {
+  external ffi.Pointer<ffi.Int32> a;
+
+  external ffi.Pointer<ffi.Int32> b;
+}
+
+/// Should be packed with 1.
+@ffi.Packed(1)
+class PackedAttr extends ffi.Struct {
+  @ffi.Int32()
+  external int a;
+}
+
+/// Should be packed with 8.
+@ffi.Packed(8)
+class PackedAttrAlign8 extends ffi.Struct {
+  @ffi.Int32()
+  external int a;
+}
+
+/// Should be packed with 2.
+@ffi.Packed(2)
+class Pack2WithPragma extends ffi.Struct {
+  @ffi.Int32()
+  external int a;
+}
+
+/// Should be packed with 4.
+@ffi.Packed(4)
+class Pack4WithPragma extends ffi.Struct {
+  @ffi.Int64()
+  external int a;
+}
+
+class NormalStruct2 extends ffi.Struct {
+  @ffi.Int8()
+  external int a;
+}
diff --git a/test/header_parser_tests/packed_structs.h b/test/header_parser_tests/packed_structs.h
new file mode 100644
index 0000000..2edec80
--- /dev/null
+++ b/test/header_parser_tests/packed_structs.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2021, 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 NormalStruct1
+{
+    char a;
+};
+
+/// Should not be packed.
+struct StructWithAttr
+{
+    int *a;
+    int *b;
+} __attribute__((annotate("Attr is not __packed__")));
+
+/// Should be packed with 1.
+struct PackedAttr{
+    int a;
+} __attribute__((__packed__));
+
+/// Should be packed with 8.
+struct PackedAttrAlign8{
+    int a;
+} __attribute__((__packed__, aligned(8)));
+
+#pragma pack(push, 2)
+/// Should be packed with 2.
+struct Pack2WithPragma{
+    int a;
+};
+#pragma pack(4)
+/// Should be packed with 4.
+struct Pack4WithPragma{
+    long long a;
+};
+#pragma pack(pop)
+struct NormalStruct2
+{
+    char a;
+};
diff --git a/test/header_parser_tests/packed_structs_test.dart b/test/header_parser_tests/packed_structs_test.dart
new file mode 100644
index 0000000..e334fab
--- /dev/null
+++ b/test/header_parser_tests/packed_structs_test.dart
@@ -0,0 +1,45 @@
+// Copyright (c) 2021, 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/header_parser.dart' as parser;
+import 'package:ffigen/src/config_provider.dart';
+import 'package:logging/logging.dart';
+import 'package:test/test.dart';
+import 'package:yaml/yaml.dart' as yaml;
+import 'package:ffigen/src/strings.dart' as strings;
+
+import '../test_utils.dart';
+
+late Library actual;
+void main() {
+  group('packed_structs_test', () {
+    setUpAll(() {
+      logWarnings(Level.SEVERE);
+      actual = parser.parse(
+        Config.fromYaml(yaml.loadYaml('''
+${strings.name}: 'NativeLibrary'
+${strings.description}: 'Packed Structs Test'
+${strings.output}: 'unused'
+${strings.headers}:
+  ${strings.entryPoints}:
+    - 'test/header_parser_tests/packed_structs.h'
+        ''') as yaml.YamlMap),
+      );
+    });
+
+    test('Expected bindings', () {
+      matchLibraryWithExpected(actual, [
+        'test',
+        'debug_generated',
+        'packed_structs_test_output.dart'
+      ], [
+        'test',
+        'header_parser_tests',
+        'expected_bindings',
+        '_expected_packed_structs_bindings.dart'
+      ]);
+    });
+  });
+}
diff --git a/tool/libclang_config.yaml b/tool/libclang_config.yaml
index f61c097..87f6bb0 100644
--- a/tool/libclang_config.yaml
+++ b/tool/libclang_config.yaml
@@ -76,6 +76,7 @@
     - clang_getPointeeType
     - clang_getCanonicalType
     - clang_Type_getNamedType
+    - clang_Type_getAlignOf
     - clang_getTypeDeclaration
     - clang_getTypedefDeclUnderlyingType
     - clang_getCursorSpelling
@@ -106,3 +107,4 @@
     - clang_Cursor_isFunctionInlined
     - clang_getCursorDefinition
     - clang_Cursor_isNull
+    - clang_Cursor_hasAttrs