44This module automates compilation of Kaitai Struct definitions into Python code.
55
66This script is called from PolyFile's setup.py to compile the entire Kaitai Struct format library at build time.
7- Therefore, this script should always be self-contained and not require any dependencies outside of the Python standard
7+ Therefore, this script should always be self-contained and not require any dependencies other than the Python standard
88library.
99
1010"""
11+ from io import BytesIO
1112import json
13+ import os
1214from pathlib import Path
1315import shutil
16+ import stat
1417import subprocess
15- from typing import Iterable , List , Union
18+ import sys
19+ from typing import Iterable , List , Optional , Union
20+ from urllib .request import urlopen
21+ from zipfile import ZipFile
1622
1723
18- KAITAI_COMPILER_NAME : str = "kaitai-struct-compiler"
24+ if os .name == "nt" :
25+ KAITAI_COMPILER_NAME : str = "kaitai-struct-compiler.bat"
26+ else :
27+ KAITAI_COMPILER_NAME = "kaitai-struct-compiler"
28+
29+
30+ COMPILER_DIR = Path (__file__ ).absolute ().parent / "kaitai-struct-compiler-0.9"
31+ COMPILER_BIN_DIR = COMPILER_DIR / "bin"
32+ COMPILER_BIN = COMPILER_BIN_DIR / KAITAI_COMPILER_NAME
1933
2034
2135class KaitaiError (RuntimeError ):
@@ -31,10 +45,6 @@ def __str__(self):
3145 return f"{ self .ksy_file } : { super ().__str__ ()} "
3246
3347
34- def has_kaitai_compiler () -> bool :
35- return shutil .which (KAITAI_COMPILER_NAME ) is not None
36-
37-
3848class CompiledKSY :
3949 def __init__ (self , class_name : str , python_path : Union [str , Path ], dependencies : Iterable ["CompiledKSY" ] = ()):
4050 self .class_name : str = class_name
@@ -48,19 +58,46 @@ def __repr__(self):
4858 f"dependencies={ self .dependencies !r} )"
4959
5060
51- def compile (ksy_path : Union [str , Path ], output_directory : Union [str , Path ]) -> CompiledKSY :
61+ def install_compiler ():
62+ resp = urlopen ("https://github.com/kaitai-io/kaitai_struct_compiler/releases/download/0.9/"
63+ "kaitai-struct-compiler-0.9.zip" )
64+ zipfile = ZipFile (BytesIO (resp .read ()))
65+ COMPILER_DIR .mkdir (exist_ok = True )
66+ zipfile .extractall (COMPILER_DIR .parent )
67+ if COMPILER_BIN .exists ():
68+ COMPILER_BIN .chmod (COMPILER_BIN .stat ().st_mode | stat .S_IXUSR | stat .S_IXGRP | stat .S_IXOTH )
69+ sys .stderr .write (f"Installed the Kaitai Struct Compiler to { COMPILER_BIN } \n " )
70+
71+
72+ def compiler_path (auto_install : bool = True ) -> Optional [Path ]:
73+ if COMPILER_BIN .exists ():
74+ return COMPILER_BIN
75+ global_path = shutil .which (KAITAI_COMPILER_NAME )
76+ if global_path is not None :
77+ return Path (global_path )
78+ if not auto_install :
79+ return None
80+ install_compiler ()
81+ return compiler_path (auto_install = False )
82+
83+
84+ def compile (ksy_path : Union [str , Path ], output_directory : Union [str , Path ], auto_install : bool = True ) -> CompiledKSY :
5285 """Returns the list of compiled KSYs; the original spec being first, followed by its dependencies"""
53- if not has_kaitai_compiler ():
54- raise KaitaiError (f"{ KAITAI_COMPILER_NAME } not found! Please make sure it is in your PATH" )
86+ compiler = compiler_path (auto_install = auto_install )
87+ if compiler is None :
88+ raise KaitaiError (f"{ KAITAI_COMPILER_NAME } not found! Please make sure it is in your PATH. "
89+ f"See https://kaitai.io/#download" )
90+
91+ # sys.stderr.write(f"Using Kaitai Struct Compiler: {compiler!s}\n")
5592
5693 if not isinstance (output_directory , Path ):
5794 output_directory = Path (output_directory )
5895
5996 output_directory .mkdir (parents = True , exist_ok = True )
6097
6198 cmd = [
62- KAITAI_COMPILER_NAME , "--target" , "python" , "--outdir" , str (output_directory ), str (ksy_path ),
63- "--debug" , "--ksc-json-output" , "-I" , Path .cwd (), "--python-package" , "polyfile.kaitai.parsers"
99+ str ( compiler ) , "--target" , "python" , "--outdir" , str (output_directory ), str (ksy_path ),
100+ "--debug" , "--ksc-json-output" , "-I" , str ( Path .cwd () ), "--python-package" , "polyfile.kaitai.parsers"
64101 ]
65102
66103 proc = subprocess .Popen (cmd , stdout = subprocess .PIPE , stderr = subprocess .PIPE )
@@ -96,7 +133,12 @@ def compile(ksy_path: Union[str, Path], output_directory: Union[str, Path]) -> C
96133
97134if __name__ == "__main__" :
98135 import argparse
99- import sys
136+
137+ if len (sys .argv ) == 2 and sys .argv [1 ] == "--install" :
138+ if compiler_path () is None :
139+ sys .exit (1 )
140+ else :
141+ sys .exit (0 )
100142
101143 parser = argparse .ArgumentParser (description = "A Kaitai Struct to Python compiler" )
102144 parser .add_argument ("KSY_PATH" , type = str , help = "path to the Kaitai Struct definition file" )
0 commit comments