2020import zipfile
2121
2222from pkg .private import build_info
23- from pkg .private import helpers
2423from pkg .private import manifest
2524
2625ZIP_EPOCH = 315532800
@@ -98,7 +97,7 @@ def close(self):
9897 self .zip_file .close ()
9998 self .zip_file = None
10099
101- def make_zipinfo (self , path : str , mode : int ):
100+ def make_zipinfo (self , path : str , mode : str ):
102101 """Create a Zipinfo.
103102
104103 Args:
@@ -121,19 +120,17 @@ def make_zipinfo(self, path: str, mode: int):
121120 entry_info .external_attr = f_mode << 16
122121 return entry_info
123122
124- def add_manifest_entry (self , options , entry ):
123+ def add_manifest_entry (self , entry ):
125124 """Add an entry to the zip file.
126125
127126 Args:
128- options: parsed options
129127 zip_file: ZipFile to write to
130128 entry: manifest entry
131129 """
132130 entry_type , dest , src , mode , user , group = entry
133131
134132 # Use the pkg_tar mode/owner remaping as a fallback
135- non_abs_path = dest .strip ('/' )
136- dst_path = _combine_paths (options .directory , non_abs_path )
133+ dst_path = dest .strip ('/' )
137134 if entry_type == manifest .ENTRY_IS_DIR and not dst_path .endswith ('/' ):
138135 dst_path += '/'
139136 entry_info = self .make_zipinfo (path = dst_path , mode = mode )
@@ -189,24 +186,47 @@ def add_tree(self, tree_top: str, destpath: str, mode: int):
189186 dest_dir = dest + rel_path_from_top + '/'
190187 else :
191188 dest_dir = dest
189+ to_write [dest_dir ] = None
192190 for file in files :
193191 to_write [dest_dir + file ] = root + '/' + file
194192
195193 for path in sorted (to_write .keys ()):
196194 content_path = to_write [path ]
197- # If mode is unspecified, derive the mode from the file's mode.
198- if mode is None :
199- f_mode = 0o755 if os .access (content_path , os .X_OK ) else 0o644
200- else :
201- f_mode = mode
202- entry_info = self .make_zipinfo (path = path , mode = f_mode )
203- entry_info .compress_type = zipfile .ZIP_DEFLATED
204- if not content_path :
205- self .zip_file .writestr (entry_info , '' )
206- else :
195+ if content_path :
196+ # If mode is unspecified, derive the mode from the file's mode.
197+ if mode is None :
198+ f_mode = "0o755" if os .access (content_path , os .X_OK ) else self .default_mode
199+ else :
200+ f_mode = mode
201+ entry_info = self .make_zipinfo (path = path , mode = f_mode )
202+ entry_info .compress_type = zipfile .ZIP_DEFLATED
207203 with open (content_path , 'rb' ) as src :
208204 self .zip_file .writestr (entry_info , src .read ())
205+ else :
206+ # Implicitly created directory
207+ dir_path = path
208+ if not dir_path .endswith ('/' ):
209+ dir_path += '/'
210+ entry_info = self .make_zipinfo (path = dir_path , mode = "0o755" )
211+ entry_info .compress_type = zipfile .ZIP_STORED
212+ # Set directory bits
213+ entry_info .external_attr |= (UNIX_DIR_BIT << 16 ) | MSDOS_DIR_BIT
214+ self .zip_file .writestr (entry_info , '' )
209215
216+ def _load_manifest (prefix , manifest_fp ):
217+ manifest_map = {}
218+ for entry in json .load (manifest_fp ):
219+ entry [1 ] = _combine_paths (prefix , entry [1 ])
220+ manifest_map [entry [1 ]] = entry
221+ manifest_keys = list (manifest_map .keys ())
222+ # Add all parent directories of entries that have not been added explicitly.
223+ for dest in manifest_keys :
224+ parent = dest
225+ for _ in range (dest .count ("/" )):
226+ parent , _ , _ = parent .rpartition ("/" )
227+ if parent and parent not in manifest_map :
228+ manifest_map [parent ] = [manifest .ENTRY_IS_DIR , parent , None , "0o755" , None , None ]
229+ return sorted (manifest_map .values (), key = lambda x : x [1 ])
210230
211231def main (args ):
212232 unix_ts = max (ZIP_EPOCH , args .timestamp )
@@ -218,11 +238,11 @@ def main(args):
218238 default_mode = int (args .mode , 8 )
219239
220240 with open (args .manifest , 'r' ) as manifest_fp :
221- manifest = json . load ( manifest_fp )
241+ manifest = _load_manifest ( args . directory , manifest_fp )
222242 with ZipWriter (
223243 args .output , time_stamp = ts , default_mode = default_mode ) as zip_out :
224244 for entry in manifest :
225- zip_out .add_manifest_entry (args , entry )
245+ zip_out .add_manifest_entry (entry )
226246
227247
228248if __name__ == '__main__' :
0 commit comments