2017-11-08 23:16:52 +00:00
from collections import namedtuple
import os
import shlex
import subprocess
import tempfile
2017-11-09 12:35:59 +00:00
ConfigDmgtools = namedtuple ( ' Config ' , ' dmg hfsplus newfs_hfs verbose ' )
ConfigHdiutil = namedtuple ( ' Config ' , ' hdiutil verbose ' )
2017-11-08 23:16:52 +00:00
def chunks ( l , n ) :
"""
Yield successive n - sized chunks from l .
From https : / / stackoverflow . com / a / 312464.
"""
for i in range ( 0 , len ( l ) , n ) :
yield l [ i : i + n ]
class Dmg :
def __init__ ( self , config ) :
self . config = config
def _check_call ( self , process_args , * args , * * kwargs ) :
if self . config . verbose > = 1 :
print ( " EXECUTING {} " . format ( " " . join ( shlex . quote ( x ) for x in process_args ) ) )
if not ( self . config . verbose > = 2 and " stdout " not in kwargs ) :
2017-11-15 23:11:30 +00:00
kwargs [ " stdout " ] = open ( os . devnull , ' wb ' )
2017-11-08 23:16:52 +00:00
subprocess . check_call ( process_args , * args , * * kwargs )
2017-11-09 12:35:59 +00:00
class Dmgtools ( Dmg ) :
2017-11-08 23:16:52 +00:00
def _mkfs_hfs ( self , * args ) :
self . _check_call ( ( self . config . newfs_hfs , ) + args )
def _hfs ( self , * args ) :
self . _check_call ( ( self . config . hfsplus , ) + args )
def _dmg ( self , * args ) :
self . _check_call ( ( self . config . dmg , ) + args )
def _create_hfs ( self , hfs , volume_name , size ) :
if self . config . verbose > = 1 :
print ( " TRUNCATING {} to {} bytes " . format ( hfs , size ) )
with open ( hfs , ' wb ' ) as f :
f . truncate ( size )
self . _mkfs_hfs ( ' -v ' , volume_name , hfs )
def _symlink ( self , hfs , target , link_name ) :
self . _hfs ( hfs , ' symlink ' , link_name , target )
def _add ( self , hfs , directory ) :
self . _hfs ( hfs , ' addall ' , directory )
def _finish ( self , hfs , dmg ) :
self . _dmg ( ' build ' , hfs , dmg )
def create ( self , dmg , volume_name , directory , symlinks ) :
output_size_kb = int ( subprocess . check_output ( [ ' du ' , ' --apparent-size ' , ' -sk ' , directory ] ) . split ( ) [ 0 ] )
# TODO: Approximate a useful size (--apparent-size is GNU-specific)
output_size = max ( int ( round ( output_size_kb * 1024 * 2 ) ) , 1024 * * 2 )
hfs = tempfile . mktemp ( prefix = dmg + ' . ' , suffix = ' .hfs ' )
self . _create_hfs ( hfs , volume_name , output_size )
self . _add ( hfs , directory )
for target , link_name in symlinks :
self . _symlink ( hfs , target , link_name )
self . _finish ( hfs , dmg )
if self . config . verbose > = 1 :
print ( " REMOVING {} " . format ( hfs ) )
os . remove ( hfs )
2017-11-09 12:35:59 +00:00
class Hdiutil ( Dmg ) :
def _hdiutil ( self , * args ) :
self . _check_call ( ( self . config . hdiutil , ) + args )
def create ( self , dmg , volume_name , directory , symlinks ) :
if symlinks :
raise NotImplementedError ( " symlinks are not yet implemented " )
self . _hdiutil ( ' create ' , ' -volname ' , volume_name , ' -srcdir ' , directory , dmg )
2017-11-08 23:16:52 +00:00
def main ( ) :
import argparse
p = argparse . ArgumentParser ( description = " Manipulate dmg archives " )
subcommands = p . add_subparsers ( help = " Subcommand " , dest = ' command ' , metavar = " COMMAND " )
subcommands . required = True
create = subcommands . add_parser ( " create " , help = " Create a dmg archive from files or directories " )
create . add_argument ( ' -v ' , ' --verbose ' , action = ' count ' , help = " Verbose output " )
2017-11-09 12:35:59 +00:00
createx = create . add_mutually_exclusive_group ( required = True )
createx . add_argument ( ' --dmgtools ' , nargs = 3 , help = " Paths to the dmg and hfsplus executable (https://github.com/mozilla/libdmg-hfsplus) and the newfs_hfs executable (http://pkgs.fedoraproject.org/repo/pkgs/hfsplus-tools/diskdev_cmds-540.1.linux3.tar.gz/0435afc389b919027b69616ad1b05709/diskdev_cmds-540.1.linux3.tar.gz) " )
createx . add_argument ( ' --hdiutil ' , help = " Path to the hdiutil (only exists for macOS at time of writing) " )
2017-11-08 23:16:52 +00:00
create . add_argument ( ' output ' , metavar = " OUTPUT " , help = " Filename of the output dmg archive " )
create . add_argument ( ' volume_name ' , metavar = " VOLUME_NAME " , help = " Name of the dmg archive " )
create . add_argument ( ' directory ' , metavar = " DIR " , help = " Directory to create the archive from " )
create . add_argument ( ' --symlink ' , metavar = " SYMLINK " , nargs = 2 , action = " append " , help = " Symlink the first argument under the second name " )
args = p . parse_args ( )
verbose = args . verbose or 0
symlinks = args . symlink or [ ]
2017-11-09 12:35:59 +00:00
if args . dmgtools :
dmg , hfsplus , newfs_hfs = args . dmgtools
dmg = Dmgtools ( ConfigDmgtools ( dmg = dmg , hfsplus = hfsplus , newfs_hfs = newfs_hfs , verbose = verbose ) )
elif args . hdiutil :
dmg = Hdiutil ( ConfigHdiutil ( hdiutil = args . hdiutil , verbose = verbose ) )
else :
raise RuntimeError ( " unreachable " )
2017-11-08 23:16:52 +00:00
dmg . create ( volume_name = args . volume_name , directory = args . directory , dmg = args . output , symlinks = symlinks )
if __name__ == ' __main__ ' :
main ( )