@@ -691,7 +691,10 @@ def read_mpy(filename):
691
691
raise Exception ('native architecture mismatch' )
692
692
config .mp_small_int_bits = header [3 ]
693
693
qstr_win = QStrWindow (qw_size )
694
- return read_raw_code (f , qstr_win )
694
+ rc = read_raw_code (f , qstr_win )
695
+ rc .mpy_source_file = filename
696
+ rc .qstr_win_size = qw_size
697
+ return rc
695
698
696
699
def dump_mpy (raw_codes ):
697
700
for rc in raw_codes :
@@ -789,19 +792,72 @@ def freeze_mpy(base_qstrs, raw_codes):
789
792
print (' &raw_code_%s,' % rc .escaped_name )
790
793
print ('};' )
791
794
795
+ def merge_mpy (raw_codes , output_file ):
796
+ assert len (raw_codes ) <= 31 # so var-uints all fit in 1 byte
797
+ merged_mpy = bytearray ()
798
+
799
+ if len (raw_codes ) == 1 :
800
+ with open (raw_codes [0 ].mpy_source_file , 'rb' ) as f :
801
+ merged_mpy .extend (f .read ())
802
+ else :
803
+ header = bytearray (5 )
804
+ header [0 ] = ord ('M' )
805
+ header [1 ] = config .MPY_VERSION
806
+ header [2 ] = (config .native_arch << 2
807
+ | config .MICROPY_PY_BUILTINS_STR_UNICODE << 1
808
+ | config .MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE )
809
+ header [3 ] = config .mp_small_int_bits
810
+ header [4 ] = 32 # qstr_win_size
811
+ merged_mpy .extend (header )
812
+
813
+ bytecode = bytearray ()
814
+ bytecode_len = 6 + len (raw_codes ) * 4 + 2
815
+ bytecode .append (bytecode_len << 2 ) # kind and length
816
+ bytecode .append (0b00000000 ) # signature prelude
817
+ bytecode .append (0b00001000 ) # size prelude
818
+ bytecode .extend (b'\x00 \x01 ' ) # MP_QSTR_
819
+ bytecode .extend (b'\x00 \x01 ' ) # MP_QSTR_
820
+ for idx in range (len (raw_codes )):
821
+ bytecode .append (0x32 ) # MP_BC_MAKE_FUNCTION
822
+ bytecode .append (idx ) # index raw code
823
+ bytecode .extend (b'\x34 \x00 ' ) # MP_BC_CALL_FUNCTION, 0 args
824
+ bytecode .extend (b'\x51 \x63 ' ) # MP_BC_LOAD_NONE, MP_BC_RETURN_VALUE
825
+
826
+ bytecode .append (0 ) # n_obj
827
+ bytecode .append (len (raw_codes )) # n_raw_code
828
+
829
+ merged_mpy .extend (bytecode )
830
+
831
+ for rc in raw_codes :
832
+ with open (rc .mpy_source_file , 'rb' ) as f :
833
+ f .read (4 ) # skip header
834
+ read_uint (f ) # skip qstr_win_size
835
+ data = f .read () # read rest of mpy file
836
+ merged_mpy .extend (data )
837
+
838
+ if output_file is None :
839
+ sys .stdout .buffer .write (merged_mpy )
840
+ else :
841
+ with open (output_file , 'wb' ) as f :
842
+ f .write (merged_mpy )
843
+
792
844
def main ():
793
845
import argparse
794
846
cmd_parser = argparse .ArgumentParser (description = 'A tool to work with MicroPython .mpy files.' )
795
847
cmd_parser .add_argument ('-d' , '--dump' , action = 'store_true' ,
796
848
help = 'dump contents of files' )
797
849
cmd_parser .add_argument ('-f' , '--freeze' , action = 'store_true' ,
798
850
help = 'freeze files' )
851
+ cmd_parser .add_argument ('--merge' , action = 'store_true' ,
852
+ help = 'merge multiple .mpy files into one' )
799
853
cmd_parser .add_argument ('-q' , '--qstr-header' ,
800
854
help = 'qstr header file to freeze against' )
801
855
cmd_parser .add_argument ('-mlongint-impl' , choices = ['none' , 'longlong' , 'mpz' ], default = 'mpz' ,
802
856
help = 'long-int implementation used by target (default mpz)' )
803
857
cmd_parser .add_argument ('-mmpz-dig-size' , metavar = 'N' , type = int , default = 16 ,
804
858
help = 'mpz digit size used by target (default 16)' )
859
+ cmd_parser .add_argument ('-o' , '--output' , default = None ,
860
+ help = 'output file' )
805
861
cmd_parser .add_argument ('files' , nargs = '+' ,
806
862
help = 'input .mpy files' )
807
863
args = cmd_parser .parse_args ()
@@ -835,6 +891,8 @@ def main():
835
891
except FreezeError as er :
836
892
print (er , file = sys .stderr )
837
893
sys .exit (1 )
894
+ elif args .merge :
895
+ merged_mpy = merge_mpy (raw_codes , args .output )
838
896
839
897
if __name__ == '__main__' :
840
898
main ()
0 commit comments