commit 767a5ffa8778853bcadbc7617ea546ecc11264df
parent 52269afb1f5498cc58ac584a7dfd5819ad38f3b3
Author: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Date: Thu, 30 Oct 2025 22:13:45 -0300
Squashed 'odin-c-bindgen/' changes from 58199219..e3a75121
e3a75121 Reversed the order of bit_setify so that the key is the bit_set name and the value is the new name for a newly created enum type
bacf9b14 Update README.md
c343d2d2 remove_type_suffix config
5731ec8b Update README.md
66e7cffd Check for libclang version 16 or higher by asking the lib for a version string. I have checked that all the examples etc work on 16 or higher.
git-subtree-dir: odin-c-bindgen
git-subtree-split: e3a751218341e3e1acda40103539de092332546f
Diffstat:
9 files changed, 131 insertions(+), 82 deletions(-)
diff --git a/README.md b/README.md
@@ -14,11 +14,11 @@ Features:
## Requirements
- Odin
-- libclang
+- libclang version 16 or higher
- On Windows: Download libclang 20.1.8 from here: https://github.com/llvm/llvm-project/releases/download/llvmorg-20.1.8/clang+llvm-20.1.8-x86_64-pc-windows-msvc.tar.xz -- Copy the following from that archive:
- `lib/libclang.lib` into the generator's 'libclang' folder
- `bin/libclang.dll` into the root of the generator (next to where the bindgen executable will end up).
- - On Linux/mac, please install libclang. For example using `apt install libclang-dev` on Ubuntu/Debian/Mint. It doesn't have to be version 20, I've tried it with as low as version 18.
+ - On Linux/mac, please install libclang. For example using `apt install libclang-dev` on Ubuntu/Debian/Mint. Anything from clang version 16 and new should work.
> [!NOTE]
> libclang is used for analysing the C headers and deciding what Odin code to output.
@@ -58,6 +58,9 @@ remove_macro_prefix = ""
// Remove this prefix from function names (and add it as link_prefix) to the foreign group
remove_function_prefix = ""
+// Remove this suffix from type names (such as '_t' etc)
+remove_type_suffix = ""
+
// Set to true translate type names to Ada_Case
force_ada_case_types = false
@@ -77,10 +80,12 @@ rename = {
}
// Turns an enum into a bit_set. Converts the values of the enum into appropriate values for a
-// bit_set. Creates a bit_set type that uses the enum. Properly removes enum values with value 0.
-// Translates the enum values using a log2 procedure.
+// bit_set (translates the enum values using a log2 procedure).
+//
+// Note that the enum will be turned into a bit_set type. There will be a new type created that
+// contains the actual enum, which the bit_set then references.
bit_setify = {
- // "Pre_Existing_Enum_Type" = "New_Bit_Set_Type"
+ // "Enum_To_Turn_Into_Bitset" = "New_Enum_Type_Name"
}
// Completely override the definition of a type.
diff --git a/examples/raylib/bindgen.sjson b/examples/raylib/bindgen.sjson
@@ -9,15 +9,15 @@ imports_file = "imports.odin"
package_name = "raylib"
// "Old_Name" = "New_Name"
-// We rename ConfigFlags to ConfigFlag so we can introduce a bit_set with the name ConfigFlags.
rename = {
- "ConfigFlags" = "ConfigFlag"
+ "Gesture" = "Gestures"
+ "Gesture_Enum" = "Gesture"
}
-// "Pre_Existing_Enum_Type" = "New_Bit_Set_Type"
+// "Enum_To_Turn_Into_Bitset" = "New_Enum_Type_Name"
bit_setify = {
- "Gesture" = "Gestures"
- "ConfigFlags" = "ConfigFlags"
+ "Gesture" = "Gesture_Enum"
+ "ConfigFlags" = "ConfigFlag"
}
type_overrides = {
diff --git a/examples/raylib/raylib/raylib.odin b/examples/raylib/raylib/raylib.odin
@@ -422,12 +422,6 @@ AutomationEventList :: struct {
events: [^]AutomationEvent, // Events entries
}
-//----------------------------------------------------------------------------------
-// Enumerators Definition
-//----------------------------------------------------------------------------------
-// System/Window config flags
-// NOTE: Every bit registers one state (use it with bit masks)
-// By default all flags are set to 0
ConfigFlag :: enum i32 {
VSYNC_HINT = 6, // Set to try enabling V-Sync on GPU
FULLSCREEN_MODE = 1, // Set to run program in fullscreen
@@ -447,6 +441,12 @@ ConfigFlag :: enum i32 {
INTERLACED_HINT = 16, // Set to try enabling interlaced video format (for V3D)
}
+//----------------------------------------------------------------------------------
+// Enumerators Definition
+//----------------------------------------------------------------------------------
+// System/Window config flags
+// NOTE: Every bit registers one state (use it with bit masks)
+// By default all flags are set to 0
ConfigFlags :: bit_set[ConfigFlag; i32]
// Trace log level
@@ -795,8 +795,6 @@ BlendMode :: enum i32 {
CUSTOM_SEPARATE = 7, // Blend textures using custom rgb/alpha separate src/dst factors (use rlSetBlendFactorsSeparate())
}
-// Gesture
-// NOTE: Provided as bit-wise flags to enable only desired gestures
Gesture :: enum i32 {
TAP = 0, // Tap gesture
DOUBLETAP = 1, // Double tap gesture
@@ -810,6 +808,8 @@ Gesture :: enum i32 {
PINCH_OUT = 9, // Pinch out gesture
}
+// Gesture
+// NOTE: Provided as bit-wise flags to enable only desired gestures
Gestures :: bit_set[Gesture; i32]
// Camera system modes
diff --git a/examples/ufbx/bindgen.sjson b/examples/ufbx/bindgen.sjson
@@ -43,18 +43,12 @@ procedure_type_overrides = {
"ufbx_generate_indices.streams" = "[^]"
}
-rename = {
+bit_setify = {
"ufbx_prop_flags" = "Prop_Flag"
"ufbx_transform_flags" = "Transform_Flag"
"ufbx_baked_key_flags" = "Baked_Key_Flag"
}
-bit_setify = {
- "ufbx_prop_flags" = "Prop_Flags"
- "ufbx_transform_flags" = "Transform_Flags"
- "ufbx_baked_key_flags" = "Baked_Key_Flags"
-}
-
remove_enum_members = [
"*FORCE_32BIT"
]
diff --git a/examples/ufbx/ufbx/ufbx.odin b/examples/ufbx/ufbx/ufbx.odin
@@ -205,7 +205,6 @@ Prop_Type :: enum i32 {
PROP_TYPE_COUNT :: 16
-// Property flags: Advanced information about properties, not usually needed.
Prop_Flag :: enum i32 {
// Supports animation.
// NOTE: ufbx ignores this and allows animations on non-animatable properties.
@@ -265,6 +264,7 @@ Prop_Flag :: enum i32 {
VALUE_BLOB = 26,
}
+// Property flags: Advanced information about properties, not usually needed.
Prop_Flags :: bit_set[Prop_Flag; i32]
// Single property with name/type/value.
@@ -272,7 +272,7 @@ Prop :: struct {
name: String,
_internal_key: u32,
type: Prop_Type,
- flags: Prop_Flag,
+ flags: Prop_Flags,
value_str: String,
value_blob: Blob,
value_int: i64,
@@ -4390,9 +4390,9 @@ Baked_Key_Flag :: enum i32 {
Baked_Key_Flags :: bit_set[Baked_Key_Flag; i32]
Baked_Vec3 :: struct {
- time: f64, // < Time of the keyframe, in seconds
- value: Vec3, // < Value at `time`, can be linearly interpolated
- flags: Baked_Key_Flag, // < Additional information about the keyframe
+ time: f64, // < Time of the keyframe, in seconds
+ value: Vec3, // < Value at `time`, can be linearly interpolated
+ flags: Baked_Key_Flags, // < Additional information about the keyframe
}
Baked_Vec3_List :: struct {
@@ -4401,9 +4401,9 @@ Baked_Vec3_List :: struct {
}
Baked_Quat :: struct {
- time: f64, // < Time of the keyframe, in seconds
- value: Quat, // < Value at `time`, can be (spherically) linearly interpolated
- flags: Baked_Key_Flag, // < Additional information about the keyframe
+ time: f64, // < Time of the keyframe, in seconds
+ value: Quat, // < Value at `time`, can be (spherically) linearly interpolated
+ flags: Baked_Key_Flags, // < Additional information about the keyframe
}
Baked_Quat_List :: struct {
@@ -5121,7 +5121,6 @@ Panic :: struct {
message: [128]i8,
}
-// Flags to control `ufbx_evaluate_transform_flags()`.
Transform_Flag :: enum i32 {
// Ignore parent scale helper.
IGNORE_SCALE_HELPER = 0,
@@ -5148,6 +5147,7 @@ Transform_Flag :: enum i32 {
NO_EXTRAPOLATION = 7,
}
+// Flags to control `ufbx_evaluate_transform_flags()`.
Transform_Flags :: bit_set[Transform_Flag; i32]
// bindgen-enable
diff --git a/src/config.odin b/src/config.odin
@@ -19,6 +19,9 @@ Config :: struct {
// Remove this prefix from function names (and add it as link_prefix) to the foreign group
remove_function_prefix: string,
+
+ // Remove this suffix from type names (structs, enum, etc)
+ remove_type_suffix: string,
// Set to true translate type names to Ada_Case
force_ada_case_types: bool,
@@ -38,8 +41,10 @@ Config :: struct {
rename: map[string]string,
// Turns an enum into a bit_set. Converts the values of the enum into appropriate values for a
- // bit_set. Creates a bit_set type that uses the enum. Properly removes enum values with value 0.
- // Translates the enum values using a log2 procedure.
+ // bit_set (translates the enum values using a log2 procedure).
+ //
+ // Note that the enum will be turned into a bit_set type. There will be a new type created that
+ // contains the actual enum, which the bit_set then references.
bit_setify: map[string]string,
// Completely override the definition of a type.
@@ -80,6 +85,4 @@ Config :: struct {
// this path in search for included headers.
clang_include_paths: []string,
clang_defines: map[string]string,
-
-
}
diff --git a/src/examples/tester/tester.h b/src/examples/tester/tester.h
@@ -44,6 +44,12 @@ struct Test2 {
int y;
};
+typedef enum {
+ Thing,
+ Something,
+ Else,
+} A_Typedeffed_Enum;
+
enum Wa {
One,
Two,
diff --git a/src/translate_collect.odin b/src/translate_collect.odin
@@ -6,6 +6,7 @@ import clang "../libclang"
import "core:slice"
import "core:log"
import "core:strings"
+import "core:strconv"
import "core:unicode"
import "core:unicode/utf8"
import "core:fmt"
@@ -22,6 +23,21 @@ Translate_Collect_Result :: struct {
// that is deferred to `translate_process`.
@(private="package", require_results)
translate_collect :: proc(filename: string, config: Config, types: Type_List, decls: Decl_List) -> (Translate_Collect_Result, bool) {
+ clang_version := string_from_clang_string(clang.getClangVersion())
+ clang_version = strings.trim_prefix(clang_version, "clang version ")
+ clang_version_major_end := strings.index_rune(clang_version, '.')
+
+ if clang_version_major_end == -1 {
+ log.panic("Failed checking libclang version")
+ }
+
+ clang_major_version_str := clang_version[:clang_version_major_end]
+
+ if clang_major_version, clang_major_version_ok := strconv.parse_int(clang_major_version_str);
+ clang_major_version_ok && clang_major_version < 16 {
+ log.panic("libclang version 16 or newer is required")
+ }
+
clang_args: [dynamic]cstring
append(&clang_args, "-fparse-all-comments")
diff --git a/src/translate_process.odin b/src/translate_process.odin
@@ -164,18 +164,14 @@ translate_process :: proc(tcr: Translate_Collect_Result, config: Config, types:
v.members = new_members[:]
}
- bit_set_name, bit_setify := config.bit_setify[d.name]
+ bit_set_enum_name, bit_setify := config.bit_setify[d.name]
if bit_setify {
clear(&bit_set_make_constant)
- if bit_set_name == d.name && (d.name not_in config.rename) {
- log.warnf("bit_set '%v' has same as enum '%v'. Suggestion: Add '\"%v\" = \"Some_New_Name\"' to 'rename' in bindgen.sjson", bit_set_name, d.name, d.name)
- }
-
bs_idx := add_type(types, Type_Bit_Set {
enum_type = d.def.(Type_Index),
- enum_decl_name = Type_Name(d.name),
+ enum_decl_name = Type_Name(bit_set_enum_name),
})
new_members: [dynamic]Type_Enum_Member
@@ -191,7 +187,7 @@ translate_process :: proc(tcr: Translate_Collect_Result, config: Config, types:
// it into a constant.
bs_constant_idx := add_type(types, Type_Bit_Set_Constant {
bit_set_type = bs_idx,
- bit_set_type_name = Type_Name(bit_set_name),
+ bit_set_type_name = Type_Name(d.name),
value = m.value,
})
@@ -217,12 +213,15 @@ translate_process :: proc(tcr: Translate_Collect_Result, config: Config, types:
v.members = new_members[:]
- add_decl(decls, {
- original_line = d.original_line + 1,
- name = bit_set_name,
- def = bs_idx,
- explicitly_created = true,
- })
+ enum_decl := d
+ enum_decl.comment_before = ""
+ enum_decl.side_comment = ""
+ d.def = bs_idx
+ d.original_line += 1
+
+ enum_decl.name = bit_set_enum_name
+
+ add_decl(decls, enum_decl)
}
case Type_Struct:
@@ -253,6 +252,10 @@ translate_process :: proc(tcr: Translate_Collect_Result, config: Config, types:
if default, has_default := config.procedure_parameter_defaults[key]; has_default {
param.default = default
}
+
+ if override, has_override := config.procedure_type_overrides[key]; has_override {
+ override_procedure_parameter(¶m, types, override)
+ }
}
}
@@ -264,23 +267,7 @@ translate_process :: proc(tcr: Translate_Collect_Result, config: Config, types:
for &p in v.parameters {
param_key := fmt.tprintf("%s.%s", d.name, p.name)
if override, has_override := config.procedure_type_overrides[param_key]; has_override {
- if override == "[^]" {
- if ptr_type, is_ptr_type := resolve_type_definition(types, p.type, Type_Pointer); is_ptr_type {
- p.type = add_type(types, Type_Multipointer {
- pointed_to_type = ptr_type.pointed_to_type,
- })
- }
- } else if override == "#by_ptr" {
- if ptr_type, is_ptr_type := resolve_type_definition(types, p.type, Type_Pointer); is_ptr_type {
- p.type = add_type(types, Type_Pointer_By_Ptr {
- pointed_to_type = ptr_type.pointed_to_type,
- })
- }
- } else if override == "#any_int" {
- p.any_int = true
- } else {
- p.type = Fixed_Value(override)
- }
+ override_procedure_parameter(&p, types, override)
}
if default, has_default := config.procedure_parameter_defaults[param_key]; has_default {
@@ -502,20 +489,8 @@ resolve_final_names :: proc(types: Type_List, decls: Decl_List, config: Config)
}
for &d in decls {
- if d.explicitly_created {
- continue
- }
-
- _, is_proc := resolve_type_definition(types, d.def, Type_Procedure)
-
- if is_proc {
- d.name = strings.trim_prefix(d.name, config.remove_function_prefix)
- } else if d.from_macro {
- d.name = strings.trim_prefix(d.name, config.remove_macro_prefix)
- } else {
- d.name = string(final_type_name(Type_Name(d.name), config))
- }
-
+ d.name = final_decl_name(d, types, config)
+
switch &def in d.def {
case Type_Name: d.def = final_type_name(def, config)
case Macro_Name: d.def = final_macro_name(def, config)
@@ -526,6 +501,26 @@ resolve_final_names :: proc(types: Type_List, decls: Decl_List, config: Config)
}
}
+override_procedure_parameter :: proc(p: ^Type_Procedure_Parameter, types: Type_List, override: string) {
+ if override == "[^]" {
+ if ptr_type, is_ptr_type := resolve_type_definition(types, p.type, Type_Pointer); is_ptr_type {
+ p.type = add_type(types, Type_Multipointer {
+ pointed_to_type = ptr_type.pointed_to_type,
+ })
+ }
+ } else if override == "#by_ptr" {
+ if ptr_type, is_ptr_type := resolve_type_definition(types, p.type, Type_Pointer); is_ptr_type {
+ p.type = add_type(types, Type_Pointer_By_Ptr {
+ pointed_to_type = ptr_type.pointed_to_type,
+ })
+ }
+ } else if override == "#any_int" {
+ p.any_int = true
+ } else {
+ p.type = Fixed_Value(override)
+ }
+}
+
is_number :: proc(b: byte) -> bool {
return b >= '0' && b <= '9'
}
@@ -644,12 +639,42 @@ ensure_name_valid :: proc(s: string) -> string {
return s
}
+final_decl_name :: proc(d: Decl, types: Type_List, config: Config) -> string {
+ if d.explicitly_created {
+ return d.name
+ }
+
+ if new_name, rename := config.rename[string(d.name)]; rename {
+ return new_name
+ }
+
+ _, is_proc := resolve_type_definition(types, d.def, Type_Procedure)
+
+ if is_proc {
+ return strings.trim_prefix(d.name, config.remove_function_prefix)
+ } else if d.from_macro {
+ return strings.trim_prefix(d.name, config.remove_macro_prefix)
+ } else {
+ res := strings.trim_prefix(d.name, config.remove_type_prefix)
+ res = strings.trim_suffix(res, config.remove_type_suffix)
+
+ if config.force_ada_case_types {
+ res = strings.to_ada_case(res)
+ }
+
+ return res
+ }
+
+ return d.name
+}
+
final_type_name :: proc(name: Type_Name, config: Config) -> Type_Name {
if new_name, rename := config.rename[string(name)]; rename {
return Type_Name(new_name)
}
res := strings.trim_prefix(string(name), config.remove_type_prefix)
+ res = strings.trim_suffix(res, config.remove_type_suffix)
if config.force_ada_case_types {
res = strings.to_ada_case(res)