2 import os, sys, re, subprocess
5 print("USAGE: /path/to/lightning.h /path/to/bindings/output /path/to/bindings/ /path/to/bindings/output.c debug lang")
8 if sys.argv[5] == "false":
10 elif sys.argv[5] == "true":
13 print("debug should be true or false and indicates whether to track allocations and ensure we don't leak")
17 if sys.argv[6] == "java" or sys.argv[6] == "android":
19 from java_strings import Consts
20 target = java_strings.Target.JAVA
21 if sys.argv[6] == "android":
22 target = java_strings.Target.ANDROID
23 elif sys.argv[6] == "typescript":
24 import typescript_strings
25 from typescript_strings import Consts
26 target = typescript_strings.Target.NODEJS
27 if len(sys.argv) == 8 and sys.argv[7] == 'browser':
28 target = typescript_strings.Target.BROWSER
30 print("Only java or typescript can be set for lang")
34 consts = Consts(DEBUG, target=target, outdir=sys.argv[4])
36 local_git_version = os.getenv("LDK_GARBAGECOLLECTED_GIT_OVERRIDE")
37 if local_git_version is None:
38 local_git_version = subprocess.check_output(["git", "describe", '--tag', '--dirty']).decode("utf-8").strip()
40 from bindingstypes import *
47 def camel_to_snake(s):
48 # Convert camel case to snake case, in a way that appears to match cbindgen
54 if lastchar.isupper():
55 if not char.isupper() and not lastund:
60 ret = ret + lastchar.lower()
63 if char.isupper() and not lastund:
71 return (ret + lastchar.lower()).strip("_")
73 def doc_to_field_nullable(doc):
76 for line in doc.splitlines():
77 if "Note that this (or a relevant inner pointer) may be NULL or all-0s to represent None" in line:
81 def doc_to_params_ret_nullable(doc):
86 for line in doc.splitlines():
87 if "may be NULL or all-0s to represent None" not in line:
89 if "Note that the return value" in line:
91 elif "Note that " in line:
92 param = line.split("Note that ")[1].split(" ")[0]
94 return (params, ret_null)
97 # Map from enum name to "contains trait object"
99 opaque_structs = set()
104 var_is_arr_regex = re.compile("\(\* ?([A-za-z0-9_]*)\)\[([a-z0-9]*)\]")
105 var_ty_regex = re.compile("([A-za-z_0-9]*)(.*)")
106 java_c_types_none_allowed = True # Unset when we do the real pass that populates the above sets
107 def java_c_types(fn_arg, ret_arr_len):
108 fn_arg = fn_arg.strip()
109 if fn_arg.startswith("MUST_USE_RES "):
112 if fn_arg.startswith("const "):
115 if fn_arg.startswith("struct "):
117 if fn_arg.startswith("enum "):
119 nonnull_ptr = "NONNULL_PTR" in fn_arg
120 fn_arg = fn_arg.replace("NONNULL_PTR", "")
127 if fn_arg.startswith("LDKThirtyTwoBytes"):
128 fn_arg = "uint8_t (*" + fn_arg[18:] + ")[32]"
129 assert var_is_arr_regex.match(fn_arg[8:])
130 rust_obj = "LDKThirtyTwoBytes"
132 elif fn_arg.startswith("LDKTxid"):
133 fn_arg = "uint8_t (*" + fn_arg[8:] + ")[32]"
134 assert var_is_arr_regex.match(fn_arg[8:])
135 rust_obj = "LDKThirtyTwoBytes"
137 elif fn_arg.startswith("LDKPublicKey"):
138 fn_arg = "uint8_t (*" + fn_arg[13:] + ")[33]"
139 assert var_is_arr_regex.match(fn_arg[8:])
140 rust_obj = "LDKPublicKey"
141 arr_access = "compressed_form"
142 elif fn_arg.startswith("LDKSecretKey"):
143 fn_arg = "uint8_t (*" + fn_arg[13:] + ")[32]"
144 assert var_is_arr_regex.match(fn_arg[8:])
145 rust_obj = "LDKSecretKey"
147 elif fn_arg.startswith("LDKSignature"):
148 fn_arg = "uint8_t (*" + fn_arg[13:] + ")[64]"
149 assert var_is_arr_regex.match(fn_arg[8:])
150 rust_obj = "LDKSignature"
151 arr_access = "compact_form"
152 elif fn_arg.startswith("LDKRecoverableSignature"):
153 fn_arg = "uint8_t (*" + fn_arg[24:] + ")[68]"
154 assert var_is_arr_regex.match(fn_arg[8:])
155 rust_obj = "LDKRecoverableSignature"
156 arr_access = "serialized_form"
157 elif fn_arg.startswith("LDKThreeBytes"):
158 fn_arg = "uint8_t (*" + fn_arg[14:] + ")[3]"
159 assert var_is_arr_regex.match(fn_arg[8:])
160 rust_obj = "LDKThreeBytes"
162 elif fn_arg.startswith("LDKFourBytes"):
163 fn_arg = "uint8_t (*" + fn_arg[13:] + ")[4]"
164 assert var_is_arr_regex.match(fn_arg[8:])
165 rust_obj = "LDKFourBytes"
167 elif fn_arg.startswith("LDKSixteenBytes"):
168 fn_arg = "uint8_t (*" + fn_arg[16:] + ")[16]"
169 assert var_is_arr_regex.match(fn_arg[8:])
170 rust_obj = "LDKSixteenBytes"
172 elif fn_arg.startswith("LDKTwentyBytes"):
173 fn_arg = "uint8_t (*" + fn_arg[15:] + ")[20]"
174 assert var_is_arr_regex.match(fn_arg[8:])
175 rust_obj = "LDKTwentyBytes"
177 elif fn_arg.startswith("LDKTwelveBytes"):
178 fn_arg = "uint8_t (*" + fn_arg[15:] + ")[12]"
179 assert var_is_arr_regex.match(fn_arg[8:])
180 rust_obj = "LDKTwelveBytes"
182 elif fn_arg.startswith("LDKu8slice"):
183 fn_arg = "uint8_t (*" + fn_arg[11:] + ")[datalen]"
184 assert var_is_arr_regex.match(fn_arg[8:])
185 rust_obj = "LDKu8slice"
187 elif fn_arg.startswith("LDKCVec_u8Z"):
188 fn_arg = "uint8_t (*" + fn_arg[12:] + ")[datalen]"
189 rust_obj = "LDKCVec_u8Z"
190 assert var_is_arr_regex.match(fn_arg[8:])
192 elif fn_arg.startswith("LDKTransaction ") or fn_arg == "LDKTransaction":
193 fn_arg = "uint8_t (*" + fn_arg[15:] + ")[datalen]"
194 rust_obj = "LDKTransaction"
195 assert var_is_arr_regex.match(fn_arg[8:])
197 elif fn_arg.startswith("LDKCVec_"):
200 fn_arg = fn_arg.replace("*", "")
203 tyn = fn_arg[8:].split(" ")
204 assert tyn[0].endswith("Z")
208 new_arg = "LDK" + tyn[0][:-1]
210 new_arg = new_arg + " " + a
211 res = java_c_types(new_arg, ret_arr_len)
213 assert java_c_types_none_allowed
216 res.pass_by_ref = True
217 java_ty = consts.java_arr_ty_str(res.java_ty)
218 if res.is_native_primitive or res.passed_as_ptr:
219 return TypeInfo(rust_obj=fn_arg.split(" ")[0], java_ty=java_ty, java_hu_ty=res.java_hu_ty + "[]",
220 java_fn_ty_arg="[" + res.java_fn_ty_arg, c_ty=res.c_ty + "Array", passed_as_ptr=False, is_ptr=is_ptr,
221 nonnull_ptr=nonnull_ptr, is_const=is_const,
222 var_name=res.var_name, arr_len="datalen", arr_access="data", subty=res, is_native_primitive=False)
224 return TypeInfo(rust_obj=fn_arg.split(" ")[0], java_ty=java_ty, java_hu_ty=res.java_hu_ty + "[]",
225 java_fn_ty_arg="[" + res.java_fn_ty_arg, c_ty=consts.ptr_arr, passed_as_ptr=False, is_ptr=is_ptr,
226 nonnull_ptr=nonnull_ptr, is_const=is_const,
227 var_name=res.var_name, arr_len="datalen", arr_access="data", subty=res, is_native_primitive=False)
230 contains_trait = False
233 java_type_plural = None
235 if fn_arg.startswith("void"):
239 fn_arg = fn_arg[4:].strip()
241 elif fn_arg.startswith("bool"):
246 fn_arg = fn_arg[4:].strip()
248 elif fn_arg.startswith("uint8_t"):
249 mapped_type = consts.c_type_map['uint8_t']
250 java_ty = mapped_type[0]
254 fn_arg = fn_arg[7:].strip()
256 elif fn_arg.startswith("LDKu5"):
257 java_ty = consts.c_type_map['uint8_t'][0]
263 fn_arg = fn_arg[6:].strip()
264 elif fn_arg.startswith("uint16_t"):
265 mapped_type = consts.c_type_map['uint16_t']
266 java_ty = mapped_type[0]
270 fn_arg = fn_arg[8:].strip()
272 elif fn_arg.startswith("uint32_t"):
273 mapped_type = consts.c_type_map['uint32_t']
274 java_ty = mapped_type[0]
278 fn_arg = fn_arg[8:].strip()
280 elif fn_arg.startswith("uint64_t") or fn_arg.startswith("uintptr_t"):
281 # TODO: uintptr_t is arch-dependent :(
282 mapped_type = consts.c_type_map['uint64_t']
283 java_ty = mapped_type[0]
285 if fn_arg.startswith("uint64_t"):
288 fn_arg = fn_arg[8:].strip()
290 java_ty = consts.ptr_native_ty
293 rust_obj = "uintptr_t"
294 fn_arg = fn_arg[9:].strip()
296 elif is_const and fn_arg.startswith("char *"):
297 java_ty = consts.java_type_map["String"]
298 java_hu_ty = consts.java_hu_type_map["String"]
301 fn_ty_arg = "Ljava/lang/String;"
302 fn_arg = fn_arg[6:].strip()
303 elif fn_arg.startswith("LDKStr"):
306 java_ty = consts.java_type_map["String"]
307 java_hu_ty = consts.java_hu_type_map["String"]
309 fn_ty_arg = "Ljava/lang/String;"
310 fn_arg = fn_arg[6:].strip()
314 ma = var_ty_regex.match(fn_arg)
315 arr_ty = ma.group(1).strip()
316 if ma.group(1).strip() in unitary_enums:
317 assert ma.group(1).strip().startswith("LDK")
318 java_ty = ma.group(1).strip()[3:]
320 c_ty = consts.result_c_ty
321 fn_ty_arg = "Lorg/ldk/enums/" + java_ty + ";"
322 fn_arg = ma.group(2).strip()
323 rust_obj = ma.group(1).strip()
325 c_ty = consts.ptr_c_ty
326 java_ty = consts.ptr_native_ty
327 java_hu_ty = ma.group(1).strip()
328 java_hu_ty = java_hu_ty.replace("LDKCOption", "Option")
329 java_hu_ty = java_hu_ty.replace("LDKCResult", "Result")
330 java_hu_ty = java_hu_ty.replace("LDKC2Tuple", "TwoTuple")
331 java_hu_ty = java_hu_ty.replace("LDKC3Tuple", "ThreeTuple")
332 java_hu_ty = java_hu_ty.replace("LDK", "")
334 fn_arg = ma.group(2).strip()
335 rust_obj = ma.group(1).strip()
336 if rust_obj in trait_structs:
337 contains_trait = True
338 elif rust_obj in complex_enums:
339 contains_trait = complex_enums[rust_obj]
342 if fn_arg.startswith(" *") or fn_arg.startswith("*"):
343 fn_arg = fn_arg.replace("*", "").strip()
345 c_ty = consts.ptr_c_ty
346 java_ty = consts.ptr_native_ty
349 var_is_arr = var_is_arr_regex.match(fn_arg)
351 if var_is_arr is not None or ret_arr_len is not None:
352 assert(not take_by_ptr)
354 # is there a special case for plurals?
355 if len(mapped_type) == 3:
356 java_ty = mapped_type[1]
357 java_hu_ty = mapped_type[2]
359 java_ty = java_ty + "[]"
361 c_ty = c_ty + "Array"
363 subty = java_c_types(arr_ty, None)
365 assert java_c_types_none_allowed
368 subty.pass_by_ref = True
370 if var_is_arr is not None:
371 if var_is_arr.group(1) == "":
372 return TypeInfo(rust_obj=rust_obj, java_ty=java_ty, java_hu_ty=java_hu_ty, java_fn_ty_arg="[" + fn_ty_arg, c_ty=c_ty, is_const=is_const,
373 passed_as_ptr=False, is_ptr=False, nonnull_ptr=nonnull_ptr, var_name="arg", subty=subty,
374 arr_len=var_is_arr.group(2), arr_access=arr_access, is_native_primitive=False, contains_trait=contains_trait)
375 return TypeInfo(rust_obj=rust_obj, java_ty=java_ty, java_hu_ty=java_hu_ty, java_fn_ty_arg="[" + fn_ty_arg, c_ty=c_ty, is_const=is_const,
376 passed_as_ptr=False, is_ptr=False, nonnull_ptr=nonnull_ptr, var_name=var_is_arr.group(1), subty=subty,
377 arr_len=var_is_arr.group(2), arr_access=arr_access, is_native_primitive=False, contains_trait=contains_trait)
379 if java_hu_ty is None:
381 return TypeInfo(rust_obj=rust_obj, java_ty=java_ty, java_hu_ty=java_hu_ty, java_fn_ty_arg=fn_ty_arg, c_ty=c_ty, passed_as_ptr=is_ptr or take_by_ptr,
382 is_const=is_const, is_ptr=is_ptr, nonnull_ptr=nonnull_ptr, var_name=fn_arg, arr_len=arr_len, arr_access=arr_access, is_native_primitive=is_primitive,
383 contains_trait=contains_trait, subty=subty)
385 fn_ptr_regex = re.compile("^extern const ([A-Za-z_0-9\* ]*) \(\*(.*)\)\((.*)\);$")
386 fn_ret_arr_regex = re.compile("(.*) \(\*(.*)\((.*)\)\)\[([0-9]*)\];$")
387 reg_fn_regex = re.compile("([A-Za-z_0-9\* ]* \*?)([a-zA-Z_0-9]*)\((.*)\);$")
391 from gen_type_mapping import TypeMappingGenerator
392 type_mapping_generator = TypeMappingGenerator(java_c_types, consts, opaque_structs, clone_fns, unitary_enums, trait_structs, complex_enums, result_types, tuple_types)
394 with open(sys.argv[1]) as in_h:
396 reg_fn = reg_fn_regex.match(line)
397 if reg_fn is not None:
398 if reg_fn.group(2).endswith("_clone"):
399 clone_fns.add(reg_fn.group(2))
401 rty = java_c_types(reg_fn.group(1), None)
402 if rty is not None and not rty.is_native_primitive and reg_fn.group(2) == rty.java_hu_ty + "_new":
403 constructor_fns[rty.rust_obj] = reg_fn.group(3)
405 arr_fn = fn_ret_arr_regex.match(line)
406 if arr_fn is not None:
407 if arr_fn.group(2).endswith("_clone"):
408 clone_fns.add(arr_fn.group(2))
409 # No object constructors return arrays, as then they wouldn't be an object constructor
412 # Define some manual clones...
413 clone_fns.add("ThirtyTwoBytes_clone")
414 write_c("static inline struct LDKThirtyTwoBytes ThirtyTwoBytes_clone(const struct LDKThirtyTwoBytes *orig) { struct LDKThirtyTwoBytes ret; memcpy(ret.data, orig->data, 32); return ret; }\n")
416 java_c_types_none_allowed = False # C structs created by cbindgen are declared in dependency order
418 with open(f"{sys.argv[3]}/structs/UtilMethods{consts.file_ext}", "a") as util:
419 util.write(consts.util_fn_pfx)
421 with open(sys.argv[1]) as in_h, open(f"{sys.argv[2]}/bindings{consts.file_ext}", "w") as out_java:
422 # Map a top-level function
423 def map_fn(line, re_match, ret_arr_len, c_call_string, doc_comment):
424 map_fn_with_ref_option(line, re_match, ret_arr_len, c_call_string, doc_comment, False)
425 def map_fn_with_ref_option(line, re_match, ret_arr_len, c_call_string, doc_comment, force_holds_ref):
426 method_return_type = re_match.group(1)
427 method_name = re_match.group(2)
428 method_comma_separated_arguments = re_match.group(3)
429 method_arguments = method_comma_separated_arguments.split(',')
431 if method_name.startswith("__"):
434 is_free = method_name.endswith("_free")
435 if method_name.startswith("COption") or method_name.startswith("CResult"):
436 struct_meth = method_name.rsplit("Z", 1)[0][1:] + "Z"
437 expected_struct = "LDKC" + struct_meth
438 struct_meth_name = method_name[len(struct_meth) + 1:].strip("_")
439 elif method_name.startswith("C2Tuple") or method_name.startswith("C3Tuple"):
440 tuple_name = method_name.rsplit("Z", 1)[0][2:] + "Z"
441 if method_name.startswith("C2Tuple"):
442 struct_meth = "Two" + tuple_name
443 expected_struct = "LDKC2" + tuple_name
445 struct_meth = "Three" + tuple_name
446 expected_struct = "LDKC3" + tuple_name
447 struct_meth_name = method_name[len(tuple_name) + 2:].strip("_")
449 struct_meth = method_name.split("_")[0]
450 expected_struct = "LDK" + struct_meth
451 struct_meth_name = method_name[len(struct_meth) + 1 if len(struct_meth) != 0 else 0:].strip("_")
453 (params_nullable, ret_nullable) = doc_to_params_ret_nullable(doc_comment)
455 return_type_info = type_mapping_generator.map_nullable_type(method_return_type.strip() + " ret", True, ret_arr_len, False, force_holds_ref)
457 return_type_info = type_mapping_generator.map_type(method_return_type.strip() + " ret", True, ret_arr_len, False, force_holds_ref)
459 if method_name.endswith("_clone") and expected_struct not in unitary_enums:
460 meth_line = "uintptr_t " + expected_struct.replace("LDK", "") + "_clone_ptr(" + expected_struct + " *NONNULL_PTR arg)"
461 write_c("static inline " + meth_line + " {\n")
462 write_c("\t" + return_type_info.ret_conv[0].replace("\n", "\n\t"))
463 write_c(method_name + "(arg)")
464 write_c(return_type_info.ret_conv[1])
465 write_c("\n\treturn " + return_type_info.ret_conv_name + ";\n}\n")
466 map_fn(meth_line + ";\n", re.compile("(uintptr_t) ([A-Za-z_0-9]*)\((.*)\)").match(meth_line), None, None, None)
469 default_constructor_args = {}
471 takes_self_ptr = False
474 for argument_index, argument in enumerate(method_arguments):
475 arg_ty = type_mapping_generator.java_c_types(argument, None)
476 argument_conversion_info = None
477 if argument_index == 0 and arg_ty.java_hu_ty == struct_meth:
478 argument_conversion_info = type_mapping_generator.map_type_with_info(arg_ty, False, None, is_free, True, False)
480 if argument_conversion_info.ty_info.is_ptr:
481 takes_self_ptr = True
482 elif arg_ty.var_name in params_nullable:
483 argument_conversion_info = type_mapping_generator.map_type_with_info(arg_ty, False, None, is_free, True, True)
484 if argument_conversion_info.arg_conv is not None and "Warning" in argument_conversion_info.arg_conv:
485 arg_ty_info = java_c_types(argument, None)
486 print("WARNING: Remapping argument " + arg_ty_info.var_name + " of function " + method_name + " to a reference")
487 print(" The argument appears to require a move, or not clonable, and is nullable.")
488 print(" Normally for arguments that require a move and are not clonable, we split")
489 print(" the argument into the type's constructor's arguments and just use those to")
490 print(" construct a new object on the fly.")
491 print(" However, because the object is nullable, doing so would mean we can no")
492 print(" longer allow the user to pass null, as we now have an argument list instead.")
493 print(" Thus, we blindly assume its really an Option<&Type> instead of an Option<Type>.")
494 print(" It may or may not actually be a reference, but its the simplest mapping option")
495 print(" and also the only use of this code today.")
496 arg_ty_info.requires_clone = False
497 argument_conversion_info = type_mapping_generator.map_type_with_info(arg_ty_info, False, None, is_free, True, True)
498 assert argument_conversion_info.nullable
499 assert argument_conversion_info.arg_conv is not None and "Warning" not in argument_conversion_info.arg_conv
501 argument_conversion_info = type_mapping_generator.map_type_with_info(arg_ty, False, None, is_free, True, False)
503 if argument_conversion_info.arg_conv is not None and "Warning" in argument_conversion_info.arg_conv:
504 if argument_conversion_info.rust_obj in constructor_fns:
506 for explode_arg in constructor_fns[argument_conversion_info.rust_obj].split(','):
507 explode_arg_conv = type_mapping_generator.map_type(explode_arg, False, None, False, True)
508 if explode_arg_conv.c_ty == "void":
509 # We actually want to handle this case, but for now its only used in NetGraphMsgHandler::new()
510 # which ends up resulting in a redundant constructor - both without arguments for the NetworkGraph.
513 if not argument_conversion_info.arg_name in default_constructor_args:
514 default_constructor_args[argument_conversion_info.arg_name] = []
515 default_constructor_args[argument_conversion_info.arg_name].append(explode_arg_conv)
516 argument_types.append(argument_conversion_info)
518 if not takes_self and return_type_info.java_hu_ty != struct_meth:
519 if not return_type_info.java_hu_ty.startswith("Result_" + struct_meth):
520 struct_meth_name = method_name
524 impl_on_struct = (expected_struct in opaque_structs or expected_struct in trait_structs or
525 expected_struct in complex_enums or expected_struct in complex_enums or
526 expected_struct in result_types or expected_struct in tuple_types) and not is_free
527 impl_on_utils = not impl_on_struct and (not is_free and not method_name.endswith("_clone") and
528 not method_name.startswith("TxOut") and
529 not method_name.startswith("_") and
530 method_name != "check_platform" and method_name != "Result_read" and
531 not expected_struct in unitary_enums and
532 ((not method_name.startswith("C2Tuple_") and not method_name.startswith("C3Tuple_"))
533 or method_name.endswith("_read")))
535 # If we're adding a static method, and it returns a primitive or an array of primitives,
536 # and a variable conversion adds a reference on the return type (via `this`), skip the
537 # variable's conversion reference-add (as we obviously cannot need a reference).
538 if impl_on_utils and (return_type_info.is_native_primitive or
539 (return_type_info.ty_info.subty is not None and return_type_info.ty_info.subty.is_native_primitive)):
540 for arg in argument_types:
541 if arg.from_hu_conv is not None and arg.from_hu_conv[1] != "":
542 if "this" in arg.from_hu_conv[1]:
543 arg.from_hu_conv = (arg.from_hu_conv[0], "")
545 out_java.write("\t// " + line)
546 (out_java_delta, out_c_delta, out_java_struct_delta) = \
547 consts.map_function(argument_types, c_call_string, method_name, struct_meth_name, return_type_info, struct_meth, default_constructor_args, takes_self, takes_self_ptr, args_known, type_mapping_generator, doc_comment)
548 out_java.write(out_java_delta)
551 assert len(argument_types) == 1
552 assert return_type_info.c_ty == "void"
553 write_c(consts.c_fn_ty_pfx + "void " + consts.c_fn_name_define_pfx(method_name, True) + argument_types[0].c_ty + " " + argument_types[0].ty_info.var_name + ") {\n")
554 if argument_types[0].ty_info.passed_as_ptr and not argument_types[0].ty_info.rust_obj in opaque_structs:
555 write_c("\tif ((" + argument_types[0].ty_info.var_name + " & 1) != 0) return;\n")
557 for info in argument_types:
558 if info.arg_conv is not None:
559 write_c("\t" + info.arg_conv.replace('\n', "\n\t") + "\n")
560 assert c_call_string is None
561 write_c("\t" + method_name + "(")
562 if argument_types[0].arg_conv_name is not None:
563 write_c(argument_types[0].arg_conv_name)
565 for info in argument_types:
566 if info.arg_conv_cleanup is not None:
567 write_c("\n\t" + info.arg_conv_cleanup.replace("\n", "\n\t"))
572 out_java_struct = None
574 out_java_struct = open(f"{sys.argv[3]}/structs/{struct_meth}{consts.file_ext}", "a")
575 out_java_struct.write(out_java_struct_delta)
577 out_java_struct = open(f"{sys.argv[3]}/structs/UtilMethods{consts.file_ext}", "a")
578 for line in out_java_struct_delta.splitlines():
579 out_java_struct.write(line + "\n")
581 def map_unitary_enum(struct_name, field_lines, enum_doc_comment):
582 assert struct_name.startswith("LDK")
583 with open(f"{sys.argv[3]}/enums/{struct_name[3:]}{consts.file_ext}", "w") as out_java_enum:
584 unitary_enums.add(struct_name)
585 for idx, (struct_line, _) in enumerate(field_lines):
587 assert(struct_line == "typedef enum %s {" % struct_name)
588 elif idx == len(field_lines) - 3:
589 assert(struct_line.endswith("_Sentinel,"))
590 elif idx == len(field_lines) - 2:
591 assert(struct_line == "} %s;" % struct_name)
592 elif idx == len(field_lines) - 1:
593 assert(struct_line == "")
594 assert struct_name.startswith("LDK")
595 (c_out, native_file_out, native_out) = consts.native_c_unitary_enum_map(struct_name[3:], [(x.strip().strip(","), y) for x, y in field_lines[1:-3]], enum_doc_comment)
597 out_java_enum.write(native_file_out)
598 out_java.write(native_out)
600 def map_complex_enum(struct_name, union_enum_items, inline_enum_variants, enum_doc_comment):
601 java_hu_type = struct_name.replace("LDK", "").replace("COption", "Option")
604 tag_field_lines = union_enum_items["field_lines"]
605 contains_trait = False
606 for idx, (struct_line, variant_docs) in enumerate(tag_field_lines):
608 assert(struct_line == "typedef enum %s_Tag {" % struct_name)
609 elif idx == len(tag_field_lines) - 3:
610 assert(struct_line.endswith("_Sentinel,"))
611 elif idx == len(tag_field_lines) - 2:
612 assert(struct_line == "} %s_Tag;" % struct_name)
613 elif idx == len(tag_field_lines) - 1:
614 assert(struct_line == "")
616 variant_name = struct_line.strip(' ,')[len(struct_name) + 1:]
618 if "LDK" + variant_name in union_enum_items:
619 enum_var_lines = union_enum_items["LDK" + variant_name]
620 for idx, (field, field_docs) in enumerate(enum_var_lines):
621 if idx != 0 and idx < len(enum_var_lines) - 2 and field.strip() != "":
622 field_ty = type_mapping_generator.java_c_types(field.strip(' ;'), None)
623 contains_trait |= field_ty.contains_trait
624 if field_docs is not None and doc_to_field_nullable(field_docs):
625 field_conv = type_mapping_generator.map_type_with_info(field_ty, False, None, False, True, True)
627 field_conv = type_mapping_generator.map_type_with_info(field_ty, False, None, False, True, False)
628 fields.append((field_conv, field_docs))
629 enum_variants.append(ComplexEnumVariantInfo(variant_name, variant_docs, fields, False))
630 elif camel_to_snake(variant_name) in inline_enum_variants:
631 # TODO: If we ever have a rust enum Variant(Option<Struct>) we need to pipe
632 # docs through to there, and then potentially mark the field nullable.
633 mapped = type_mapping_generator.map_type(inline_enum_variants[camel_to_snake(variant_name)] + " " + camel_to_snake(variant_name), False, None, False, True)
634 contains_trait |= mapped.ty_info.contains_trait
635 fields.append((mapped, None))
636 enum_variants.append(ComplexEnumVariantInfo(variant_name, variant_docs, fields, True))
638 enum_variants.append(ComplexEnumVariantInfo(variant_name, variant_docs, fields, True))
639 complex_enums[struct_name] = contains_trait
641 with open(f"{sys.argv[3]}/structs/{java_hu_type}{consts.file_ext}", "w") as out_java_enum:
642 (out_java_addendum, out_java_enum_addendum, out_c_addendum) = consts.map_complex_enum(struct_name, enum_variants, camel_to_snake, enum_doc_comment)
644 out_java_enum.write(out_java_enum_addendum)
645 out_java.write(out_java_addendum)
646 write_c(out_c_addendum)
648 def map_trait(struct_name, field_var_lines, trait_fn_lines, trait_doc_comment):
649 with open(f"{sys.argv[3]}/structs/{struct_name.replace('LDK', '')}{consts.file_ext}", "w") as out_java_trait:
651 flattened_field_var_convs = []
652 for var_line in field_var_lines:
653 if var_line.group(1) in trait_structs:
654 field_var_convs.append((var_line.group(1), var_line.group(2), trait_structs[var_line.group(1)]))
655 flattened_field_var_convs.append((var_line.group(1), var_line.group(2), ))
656 flattened_field_var_convs.extend(trait_structs[var_line.group(1)])
658 mapped = type_mapping_generator.map_type(var_line.group(1) + " " + var_line.group(2), False, None, False, False)
659 field_var_convs.append(mapped)
660 flattened_field_var_convs.append(mapped)
661 trait_structs[struct_name] = field_var_convs
664 for fn_docs, fn_line in trait_fn_lines:
665 if fn_line == "cloned":
666 ret_ty_info = type_mapping_generator.map_type("void", True, None, False, False)
667 field_fns.append(TraitMethInfo("cloned", False, ret_ty_info, [], fn_docs))
669 (nullable_params, ret_nullable) = doc_to_params_ret_nullable(fn_docs)
671 assert False # This isn't yet handled on the Java side
672 ret_ty_info.nullable = True
673 ret_ty_info = type_mapping_generator.map_nullable_type(fn_line.group(2).strip() + " ret", True, None, False, False)
675 ret_ty_info = type_mapping_generator.map_type(fn_line.group(2).strip() + " ret", True, None, False, False)
676 is_const = fn_line.group(4) is not None
679 for idx, arg in enumerate(fn_line.group(5).split(',')):
682 arg_ty_info = type_mapping_generator.java_c_types(arg, None)
683 if arg_ty_info.var_name in nullable_params:
684 # Types that are actually null instead of all-0s aren't yet handled on the Java side:
685 arg_conv_info = type_mapping_generator.map_type_with_info(arg_ty_info, True, None, False, False, True)
687 arg_conv_info = type_mapping_generator.map_type_with_info(arg_ty_info, True, None, False, False, False)
688 arg_tys.append(arg_conv_info)
689 field_fns.append(TraitMethInfo(fn_line.group(3), is_const, ret_ty_info, arg_tys, fn_docs))
691 (out_java_addendum, out_java_trait_addendum, out_c_addendum) = consts.native_c_map_trait(struct_name, field_var_convs, flattened_field_var_convs, field_fns, trait_doc_comment)
692 write_c(out_c_addendum)
693 out_java_trait.write(out_java_trait_addendum)
694 out_java.write(out_java_addendum)
696 for fn_docs, fn_line in trait_fn_lines:
697 if fn_line == "cloned":
699 # For now, just disable enabling the _call_log - we don't know how to inverse-map String
700 is_log = fn_line.group(3) == "log" and struct_name == "LDKLogger"
701 if fn_line.group(3) != "free" and fn_line.group(3) != "eq" and not is_log:
702 dummy_line = fn_line.group(2) + struct_name.replace("LDK", "") + "_" + fn_line.group(3) + " " + struct_name + " *NONNULL_PTR this_arg" + fn_line.group(5) + "\n"
703 map_fn(dummy_line, re.compile("([A-Za-z_0-9]*) *([A-Za-z_0-9]*) *(.*)").match(dummy_line), None, "(this_arg_conv->" + fn_line.group(3) + ")(this_arg_conv->this_arg", fn_docs)
704 for idx, var_line in enumerate(field_var_lines):
705 if var_line.group(1) not in trait_structs:
706 write_c(var_line.group(1) + " " + struct_name + "_set_get_" + var_line.group(2) + "(" + struct_name + "* this_arg) {\n")
707 write_c("\tif (this_arg->set_" + var_line.group(2) + " != NULL)\n")
708 write_c("\t\tthis_arg->set_" + var_line.group(2) + "(this_arg);\n")
709 write_c("\treturn this_arg->" + var_line.group(2) + ";\n")
711 dummy_line = var_line.group(1) + " " + struct_name.replace("LDK", "") + "_get_" + var_line.group(2) + " " + struct_name + " *NONNULL_PTR this_arg" + fn_line.group(5) + "\n"
712 map_fn(dummy_line, re.compile("([A-Za-z_0-9]*) *([A-Za-z_0-9]*) *(.*)").match(dummy_line), None, struct_name + "_set_get_" + var_line.group(2) + "(this_arg_conv", fn_docs)
714 def map_result(struct_name, res_ty, err_ty):
715 result_types.add(struct_name)
716 human_ty = struct_name.replace("LDKCResult", "Result")
717 res_map = type_mapping_generator.map_type(res_ty + " res", True, None, False, True)
718 err_map = type_mapping_generator.map_type(err_ty + " err", True, None, False, True)
719 java_hu_struct = consts.map_result(struct_name, res_map, err_map)
720 create_getter(struct_name, res_ty, "ok", ("*", "->contents.result"), ("", "->result_ok"))
721 create_getter(struct_name, err_ty, "err", ("*", "->contents.err"), ("!", "->result_ok"))
722 with open(f"{sys.argv[3]}/structs/{human_ty}{consts.file_ext}", "w") as out_java_struct:
723 out_java_struct.write(java_hu_struct)
725 def create_getter(struct_name, field_decl, field_name, accessor, check_sfx):
726 field_ty = java_c_types(field_decl + " " + field_name, None)
727 ptr_fn_defn = field_decl + " *" + struct_name.replace("LDK", "") + "_get_" + field_name + "(" + struct_name + " *NONNULL_PTR owner)"
728 owned_fn_defn = field_decl + " " + struct_name.replace("LDK", "") + "_get_" + field_name + "(" + struct_name + " *NONNULL_PTR owner)"
731 if field_ty.rust_obj is not None and field_ty.rust_obj.replace("LDK", "") + "_clone" in clone_fns:
732 fn_defn = owned_fn_defn
733 write_c("static inline " + fn_defn + "{\n")
734 if check_sfx is not None:
735 write_c("CHECK(" + check_sfx[0] + "owner" + check_sfx[1] + ");\n")
736 write_c("\treturn " + field_ty.rust_obj.replace("LDK", "") + "_clone(&" + accessor[0] + "owner" + accessor[1] + ");\n")
737 elif field_ty.arr_len is not None or field_ty.is_native_primitive or field_ty.rust_obj in unitary_enums:
738 fn_defn = owned_fn_defn
739 write_c("static inline " + fn_defn + "{\n")
740 if check_sfx is not None:
741 write_c("CHECK(" + check_sfx[0] + "owner" + check_sfx[1] + ");\n")
742 write_c("\treturn " + accessor[0] + "owner" + accessor[1] + ";\n")
745 fn_defn = ptr_fn_defn
746 write_c("static inline " + fn_defn + "{\n")
747 if check_sfx is not None:
748 write_c("CHECK(" + check_sfx[0] + "owner" + check_sfx[1] + ");\n")
749 write_c("\treturn &" + accessor[0] + "owner" + accessor[1] + ";\n")
752 dummy_line = fn_defn + ";\n"
753 map_fn_with_ref_option(dummy_line, reg_fn_regex.match(dummy_line), None, None, "", holds_ref)
755 def map_tuple(struct_name, field_lines):
756 human_ty = struct_name.replace("LDKC2Tuple", "TwoTuple").replace("LDKC3Tuple", "ThreeTuple")
757 with open(f"{sys.argv[3]}/structs/{human_ty}{consts.file_ext}", "w") as out_java_struct:
758 out_java_struct.write(consts.map_tuple(struct_name))
760 for idx, (line, _) in enumerate(field_lines):
761 if idx != 0 and idx < len(field_lines) - 2:
762 ty_list.append(java_c_types(line.strip(';'), None))
763 tuple_types[struct_name] = (ty_list, struct_name)
765 # Map virtual getter functions
766 for idx, (line, _) in enumerate(field_lines):
767 if idx != 0 and idx < len(field_lines) - 2:
768 field_name = chr(ord('a') + idx - 1)
769 assert line.endswith(" " + field_name + ";")
770 create_getter(struct_name, line[:-3].strip(), field_name, ("", "->" + field_name), None)
772 out_java.write(consts.bindings_header)
773 with open(f"{sys.argv[2]}/version{consts.file_ext}", "w") as out_java_version:
774 out_java_version.write(consts.bindings_version_file.replace('<git_version_ldk_garbagecollected>', local_git_version))
776 with open(f"{sys.argv[3]}/structs/CommonBase{consts.file_ext}", "w") as out_java_struct:
777 out_java_struct.write(consts.common_base)
780 last_block_comment = None
783 const_val_regex = re.compile("^extern const ([A-Za-z_0-9]*) ([A-Za-z_0-9]*);$")
785 line_indicates_result_regex = re.compile("^ union (LDKCResult_[A-Za-z_0-9]*Ptr) contents;$")
786 line_indicates_vec_regex = re.compile("^ (struct |enum |union )?([A-Za-z_0-9]*) \*data;$")
787 line_indicates_opaque_regex = re.compile("^ bool is_owned;$")
788 line_indicates_trait_regex = re.compile("^ (struct |enum |union )?([A-Za-z_0-9]* \*?)\(\*([A-Za-z_0-9]*)\)\((const )?void \*this_arg(.*)\);$")
789 assert(line_indicates_trait_regex.match(" uintptr_t (*send_data)(void *this_arg, LDKu8slice data, bool resume_read);"))
790 assert(line_indicates_trait_regex.match(" struct LDKCVec_MessageSendEventZ (*get_and_clear_pending_msg_events)(const void *this_arg);"))
791 assert(line_indicates_trait_regex.match(" struct LDKCVec_u8Z (*write)(const void *this_arg);"))
792 line_indicates_trait_clone_regex = re.compile("^ void \(\*cloned\)\(struct ([A-Za-z0-9])* \*NONNULL_PTR new_[A-Za-z0-9]*\);$")
793 assert(line_indicates_trait_clone_regex.match(" void (*cloned)(struct LDKSign *NONNULL_PTR new_Sign);"))
794 line_field_var_regex = re.compile("^ struct ([A-Za-z_0-9]*) ([A-Za-z_0-9]*);$")
795 assert(line_field_var_regex.match(" struct LDKMessageSendEventsProvider MessageSendEventsProvider;"))
796 assert(line_field_var_regex.match(" struct LDKChannelPublicKeys pubkeys;"))
797 struct_name_regex = re.compile("^typedef (struct|enum|union) (MUST_USE_STRUCT )?(LDK[A-Za-z_0-9]*) {$")
798 assert(struct_name_regex.match("typedef struct LDKCVec_u8Z {"))
799 assert(struct_name_regex.match("typedef enum LDKNetwork {"))
801 union_enum_items = {}
802 result_ptr_struct_items = {}
804 if block_comment is not None:
805 if line.endswith("*/\n"):
806 last_block_comment = block_comment.strip("\n")
809 block_comment = block_comment + line.strip(" /*")
810 elif cur_block_obj is not None:
811 cur_block_obj = cur_block_obj + line
812 if line.startswith("} "):
816 obj_lines = cur_block_obj.split("\n")
818 result_contents = None
819 is_unitary_enum = False
820 is_union_enum = False
825 last_struct_block_comment = None
827 for idx, struct_line in enumerate(obj_lines):
828 if struct_line.strip().startswith("/*"):
829 block_comment = struct_line.strip(" /*")
830 if block_comment is not None:
831 if struct_line.endswith("*/"):
832 last_struct_block_comment = block_comment.strip("\n")
835 block_comment = block_comment + "\n" + struct_line.strip(" /*").replace("…", "...")
837 struct_name_match = struct_name_regex.match(struct_line)
838 if struct_name_match is not None:
839 struct_name = struct_name_match.group(3)
840 if struct_name_match.group(1) == "enum":
841 if not struct_name.endswith("_Tag"):
842 is_unitary_enum = True
845 elif struct_name_match.group(1) == "union":
847 if line_indicates_opaque_regex.match(struct_line):
849 result_match = line_indicates_result_regex.match(struct_line)
850 if result_match is not None:
851 result_contents = result_match.group(1)
852 vec_ty_match = line_indicates_vec_regex.match(struct_line)
853 if vec_ty_match is not None and struct_name.startswith("LDKCVec_"):
854 vec_ty = vec_ty_match.group(2)
855 elif struct_name.startswith("LDKC2Tuple_") or struct_name.startswith("LDKC3Tuple_"):
857 trait_fn_match = line_indicates_trait_regex.match(struct_line)
858 if trait_fn_match is not None:
859 trait_fn_lines.append((last_struct_block_comment, trait_fn_match))
860 trait_clone_fn_match = line_indicates_trait_clone_regex.match(struct_line)
861 if trait_clone_fn_match is not None:
862 trait_fn_lines.append((last_struct_block_comment, "cloned"))
863 field_var_match = line_field_var_regex.match(struct_line)
864 if field_var_match is not None:
865 field_var_lines.append(field_var_match)
866 field_lines.append((struct_line, last_struct_block_comment))
867 last_struct_block_comment = None
869 assert(struct_name is not None)
870 assert(len(trait_fn_lines) == 0 or not (is_opaque or is_unitary_enum or is_union_enum or is_union or result_contents is not None or vec_ty is not None))
871 assert(not is_opaque or not (len(trait_fn_lines) != 0 or is_unitary_enum or is_union_enum or is_union or result_contents is not None or vec_ty is not None))
872 assert(not is_unitary_enum or not (len(trait_fn_lines) != 0 or is_opaque or is_union_enum or is_union or result_contents is not None or vec_ty is not None))
873 assert(not is_union_enum or not (len(trait_fn_lines) != 0 or is_unitary_enum or is_opaque or is_union or result_contents is not None or vec_ty is not None))
874 assert(not is_union or not (len(trait_fn_lines) != 0 or is_unitary_enum or is_union_enum or is_opaque or result_contents is not None or vec_ty is not None))
875 assert(result_contents is None or not (len(trait_fn_lines) != 0 or is_unitary_enum or is_union_enum or is_opaque or is_union or vec_ty is not None))
876 assert(vec_ty is None or not (len(trait_fn_lines) != 0 or is_unitary_enum or is_union_enum or is_opaque or is_union or result_contents is not None))
879 opaque_structs.add(struct_name)
880 with open(f"{sys.argv[3]}/structs/{struct_name.replace('LDK', '')}{consts.file_ext}", "w") as out_java_struct:
881 out_opaque_struct_human = consts.map_opaque_struct(struct_name, last_block_comment)
882 last_block_comment = None
883 out_java_struct.write(out_opaque_struct_human)
884 elif result_contents is not None:
885 assert result_contents in result_ptr_struct_items
886 res_ty, err_ty = result_ptr_struct_items[result_contents]
887 map_result(struct_name, res_ty, err_ty)
888 elif struct_name.startswith("LDKCResult_") and struct_name.endswith("ZPtr"):
889 for line, _ in field_lines:
890 if line.endswith("*result;"):
891 res_ty = line[:-8].strip()
892 elif line.endswith("*err;"):
893 err_ty = line[:-5].strip()
894 result_ptr_struct_items[struct_name] = (res_ty, err_ty)
895 result_types.add(struct_name[:-3])
897 map_tuple(struct_name, field_lines)
898 elif vec_ty is not None:
899 ty_info = type_mapping_generator.map_type(vec_ty + " arr_elem", False, None, False, False)
900 if ty_info.is_native_primitive:
901 clone_fns.add(struct_name.replace("LDK", "") + "_clone")
902 write_c("static inline " + struct_name + " " + struct_name.replace("LDK", "") + "_clone(const " + struct_name + " *orig) {\n")
903 write_c("\t" + struct_name + " ret = { .data = MALLOC(sizeof(" + ty_info.c_ty + ") * orig->datalen, \"" + struct_name + " clone bytes\"), .datalen = orig->datalen };\n")
904 write_c("\tmemcpy(ret.data, orig->data, sizeof(" + ty_info.c_ty + ") * ret.datalen);\n")
905 write_c("\treturn ret;\n}\n")
906 elif (ty_info.rust_obj.replace("LDK", "") + "_clone") in clone_fns:
907 ty_name = "CVec_" + ty_info.rust_obj.replace("LDK", "") + "Z";
908 clone_fns.add(ty_name + "_clone")
909 write_c("static inline " + struct_name + " " + ty_name + "_clone(const " + struct_name + " *orig) {\n")
910 write_c("\t" + struct_name + " ret = { .data = MALLOC(sizeof(" + ty_info.rust_obj + ") * orig->datalen, \"" + struct_name + " clone bytes\"), .datalen = orig->datalen };\n")
911 write_c("\tfor (size_t i = 0; i < ret.datalen; i++) {\n")
912 write_c("\t\tret.data[i] = " + ty_info.rust_obj.replace("LDK", "") + "_clone(&orig->data[i]);\n")
913 write_c("\t}\n\treturn ret;\n}\n")
915 assert(struct_name.endswith("_Tag"))
916 struct_name = struct_name[:-4]
917 union_enum_items[struct_name] = {"field_lines": field_lines}
918 elif struct_name.endswith("_Body") and struct_name.split("_")[0] in union_enum_items:
919 enum_var_name = struct_name.split("_")
920 union_enum_items[enum_var_name[0]][enum_var_name[1]] = field_lines
921 elif struct_name in union_enum_items:
924 for line, _ in field_lines:
925 if line == " struct {":
929 elif elem_items > -1:
931 if line.startswith("struct "):
933 elif line.startswith("enum "):
935 split = line.split(" ")
936 assert len(split) == 2
937 tuple_variants[split[1].strip(";")] = split[0]
940 # We don't currently support tuple variant with more than one element
942 map_complex_enum(struct_name, union_enum_items[struct_name], tuple_variants, last_block_comment)
943 last_block_comment = None
944 elif is_unitary_enum:
945 map_unitary_enum(struct_name, field_lines, last_block_comment)
946 last_block_comment = None
947 elif len(trait_fn_lines) > 0:
948 map_trait(struct_name, field_var_lines, trait_fn_lines, last_block_comment)
949 elif struct_name == "LDKTxOut":
950 with open(f"{sys.argv[3]}/structs/TxOut{consts.file_ext}", "w") as out_java_struct:
951 out_java_struct.write(consts.hu_struct_file_prefix)
952 out_java_struct.write(consts.txout_defn)
953 fn_line = "struct LDKCVec_u8Z TxOut_get_script_pubkey (struct LDKTxOut* thing)"
954 write_c(fn_line + " {")
955 write_c("\treturn CVec_u8Z_clone(&thing->script_pubkey);")
957 map_fn(fn_line + "\n", re.compile("(.*) (TxOut_get_script_pubkey) \((.*)\)").match(fn_line), None, None, None)
958 fn_line = "uint64_t TxOut_get_value (struct LDKTxOut* thing)"
959 write_c(fn_line + " {")
960 write_c("\treturn thing->value;")
962 map_fn(fn_line + "\n", re.compile("(.*) (TxOut_get_value) \((.*)\)").match(fn_line), None, None, None)
964 pass # Everything remaining is a byte[] or some form
967 fn_ptr = fn_ptr_regex.match(line)
968 fn_ret_arr = fn_ret_arr_regex.match(line)
969 reg_fn = reg_fn_regex.match(line)
970 const_val = const_val_regex.match(line)
972 if line.startswith("#include <"):
974 elif line.startswith("/*"):
975 if not line.endswith("*/\n"):
976 block_comment = line.strip(" /*")
977 elif line.startswith("typedef enum "):
979 elif line.startswith("typedef struct "):
981 elif line.startswith("typedef union "):
983 elif fn_ptr is not None:
984 map_fn(line, fn_ptr, None, None, last_block_comment)
985 last_block_comment = None
986 elif fn_ret_arr is not None:
987 map_fn(line, fn_ret_arr, fn_ret_arr.group(4), None, last_block_comment)
988 last_block_comment = None
989 elif reg_fn is not None:
990 map_fn(line, reg_fn, None, None, last_block_comment)
991 last_block_comment = None
992 elif const_val_regex is not None:
993 # TODO Map const variables
998 out_java.write(consts.bindings_footer)
999 for struct_name in opaque_structs:
1000 with open(f"{sys.argv[3]}/structs/{struct_name.replace('LDK', '')}{consts.file_ext}", "a") as out_java_struct:
1001 out_java_struct.write("}\n")
1002 for struct_name in trait_structs:
1003 with open(f"{sys.argv[3]}/structs/{struct_name.replace('LDK', '')}{consts.file_ext}", "a") as out_java_struct:
1004 out_java_struct.write("}\n")
1005 for struct_name in complex_enums:
1006 with open(f"{sys.argv[3]}/structs/{struct_name.replace('LDK', '').replace('COption', 'Option')}{consts.file_ext}", "a") as out_java_struct:
1007 out_java_struct.write("}\n")
1008 for struct_name in result_types:
1009 with open(f"{sys.argv[3]}/structs/{struct_name.replace('LDKCResult', 'Result')}{consts.file_ext}", "a") as out_java_struct:
1010 out_java_struct.write("}\n")
1011 for struct_name in tuple_types:
1012 struct_hu_name = struct_name.replace("LDKC2Tuple", "TwoTuple").replace("LDKC3Tuple", "ThreeTuple")
1013 with open(f"{sys.argv[3]}/structs/{struct_hu_name}{consts.file_ext}", "a") as out_java_struct:
1014 out_java_struct.write("}\n")
1016 with open(f"{sys.argv[4]}/bindings.c.body", "w") as out_c:
1017 out_c.write(consts.c_file_pfx)
1018 out_c.write(consts.init_str())
1020 with open(f"{sys.argv[4]}/version.c", "w") as out_c:
1021 out_c.write(consts.c_version_file.replace('<git_version_ldk_garbagecollected>', local_git_version))
1022 with open(f"{sys.argv[3]}/structs/UtilMethods{consts.file_ext}", "a") as util:
1023 util.write(consts.util_fn_sfx)