dmg.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. # This Source Code Form is subject to the terms of the Mozilla Public
  2. # License, v. 2.0. If a copy of the MPL was not distributed with this
  3. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  4. import errno
  5. import mozfile
  6. import os
  7. import platform
  8. import shutil
  9. import subprocess
  10. is_linux = platform.system() == 'Linux'
  11. def mkdir(dir):
  12. if not os.path.isdir(dir):
  13. try:
  14. os.makedirs(dir)
  15. except OSError as e:
  16. if e.errno != errno.EEXIST:
  17. raise
  18. def chmod(dir):
  19. 'Set permissions of DMG contents correctly'
  20. subprocess.check_call(['chmod', '-R', 'a+rX,a-st,u+w,go-w', dir])
  21. def rsync(source, dest):
  22. 'rsync the contents of directory source into directory dest'
  23. # Ensure a trailing slash so rsync copies the *contents* of source.
  24. if not source.endswith('/'):
  25. source += '/'
  26. subprocess.check_call(['rsync', '-a', '--copy-unsafe-links',
  27. source, dest])
  28. def set_folder_icon(dir):
  29. 'Set HFS attributes of dir to use a custom icon'
  30. if not is_linux:
  31. #TODO: bug 1197325 - figure out how to support this on Linux
  32. subprocess.check_call(['SetFile', '-a', 'C', dir])
  33. def create_dmg_from_staged(stagedir, output_dmg, tmpdir, volume_name):
  34. 'Given a prepared directory stagedir, produce a DMG at output_dmg.'
  35. if not is_linux:
  36. # Running on OS X
  37. hybrid = os.path.join(tmpdir, 'hybrid.dmg')
  38. subprocess.check_call(['hdiutil', 'makehybrid', '-hfs',
  39. '-hfs-volume-name', volume_name,
  40. '-hfs-openfolder', stagedir,
  41. '-ov', stagedir,
  42. '-o', hybrid])
  43. subprocess.check_call(['hdiutil', 'convert', '-format', 'UDBZ',
  44. '-imagekey', 'bzip2-level=9',
  45. '-ov', hybrid, '-o', output_dmg])
  46. else:
  47. import buildconfig
  48. uncompressed = os.path.join(tmpdir, 'uncompressed.dmg')
  49. subprocess.check_call([
  50. buildconfig.substs['GENISOIMAGE'],
  51. '-V', volume_name,
  52. '-D', '-R', '-apple', '-no-pad',
  53. '-o', uncompressed,
  54. stagedir
  55. ])
  56. subprocess.check_call([
  57. buildconfig.substs['DMG_TOOL'],
  58. 'dmg',
  59. uncompressed,
  60. output_dmg
  61. ],
  62. # dmg is seriously chatty
  63. stdout=open(os.devnull, 'wb'))
  64. def check_tools(*tools):
  65. '''
  66. Check that each tool named in tools exists in SUBSTS and is executable.
  67. '''
  68. import buildconfig
  69. for tool in tools:
  70. path = buildconfig.substs[tool]
  71. if not path:
  72. raise Exception('Required tool "%s" not found' % tool)
  73. if not os.path.isfile(path):
  74. raise Exception('Required tool "%s" not found at path "%s"' % (tool, path))
  75. if not os.access(path, os.X_OK):
  76. raise Exception('Required tool "%s" at path "%s" is not executable' % (tool, path))
  77. def create_dmg(source_directory, output_dmg, volume_name, extra_files):
  78. '''
  79. Create a DMG disk image at the path output_dmg from source_directory.
  80. Use volume_name as the disk image volume name, and
  81. use extra_files as a list of tuples of (filename, relative path) to copy
  82. into the disk image.
  83. '''
  84. if platform.system() not in ('Darwin', 'Linux'):
  85. raise Exception("Don't know how to build a DMG on '%s'" % platform.system())
  86. if is_linux:
  87. check_tools('DMG_TOOL', 'GENISOIMAGE')
  88. with mozfile.TemporaryDirectory() as tmpdir:
  89. stagedir = os.path.join(tmpdir, 'stage')
  90. os.mkdir(stagedir)
  91. # Copy the app bundle over using rsync
  92. rsync(source_directory, stagedir)
  93. # Copy extra files
  94. for source, target in extra_files:
  95. full_target = os.path.join(stagedir, target)
  96. mkdir(os.path.dirname(full_target))
  97. shutil.copyfile(source, full_target)
  98. # Make a symlink to /Applications. The symlink name is a space
  99. # so we don't have to localize it. The Applications folder icon
  100. # will be shown in Finder, which should be clear enough for users.
  101. os.symlink('/Applications', os.path.join(stagedir, ' '))
  102. # Set the folder attributes to use a custom icon
  103. set_folder_icon(stagedir)
  104. chmod(stagedir)
  105. create_dmg_from_staged(stagedir, output_dmg, tmpdir, volume_name)