def makedmg(self, d, volname, internet_enable=True): ''' Copy a directory d into a dmg named volname ''' print('\nSigning...') sys.stdout.flush() destdir = OUTPUT_DIR try: shutil.rmtree(destdir) except EnvironmentError as err: if err.errno != errno.ENOENT: raise os.mkdir(destdir) dmg = join(destdir, volname + '.dmg') if os.path.exists(dmg): os.unlink(dmg) tdir = tempfile.mkdtemp() appdir = join(tdir, os.path.basename(d)) shutil.copytree(d, appdir, symlinks=True) if self.sign_installers or self.notarize: with timeit() as times: sign_app(appdir, self.notarize) print('Signing completed in %d minutes %d seconds' % tuple(times)) os.symlink('/Applications', join(tdir, 'Applications')) size_in_mb = int( subprocess.check_output(['du', '-s', '-k', tdir ]).decode('utf-8').split()[0]) / 1024. # UDBZ gives the best compression, better than ULFO cmd = [ '/usr/bin/hdiutil', 'create', '-srcfolder', tdir, '-volname', volname, '-format', 'UDBZ' ] if 190 < size_in_mb < 250: # We need -size 255m because of a bug in hdiutil. When the size of # srcfolder is close to 200MB hdiutil fails with # diskimages-helper: resize request is above maximum size allowed. cmd += ['-size', '255m'] print('\nCreating dmg...') with timeit() as times: subprocess.check_call(cmd + [dmg]) if internet_enable: subprocess.check_call( ['/usr/bin/hdiutil', 'internet-enable', '-yes', dmg]) print('dmg created in %d minutes and %d seconds' % tuple(times)) shutil.rmtree(tdir) size = os.stat(dmg).st_size / (1024 * 1024.) print('\nInstaller size: %.2fMB\n' % size) return dmg
def makedmg(self, d, volname, format='ULFO'): ''' Copy a directory d into a dmg named volname ''' print('\nMaking dmg...') sys.stdout.flush() destdir = os.path.join(SW, 'dist') try: shutil.rmtree(destdir) except FileNotFoundError: pass os.mkdir(destdir) dmg = os.path.join(destdir, volname + '.dmg') if os.path.exists(dmg): os.unlink(dmg) tdir = tempfile.mkdtemp() appdir = os.path.join(tdir, os.path.basename(d)) shutil.copytree(d, appdir, symlinks=True) if self.sign_installers: with timeit() as times: sign_app(appdir, self.notarize) print('Signing completed in %d minutes %d seconds' % tuple(times)) os.symlink('/Applications', os.path.join(tdir, 'Applications')) size_in_mb = int( subprocess.check_output(['du', '-s', '-k', tdir ]).decode('utf-8').split()[0]) / 1024. cmd = [ '/usr/bin/hdiutil', 'create', '-srcfolder', tdir, '-volname', volname, '-format', format ] if 190 < size_in_mb < 250: # We need -size 255m because of a bug in hdiutil. When the size of # srcfolder is close to 200MB hdiutil fails with # diskimages-helper: resize request is above maximum size allowed. cmd += ['-size', '255m'] print('\nCreating dmg...') with timeit() as times: subprocess.check_call(cmd + [dmg]) print('dmg created in %d minutes and %d seconds' % tuple(times)) shutil.rmtree(tdir) size = os.stat(dmg).st_size / (1024 * 1024.) print('\nInstaller size: %.2fMB\n' % size) return dmg
def notarize_app(app_path): # See # https://developer.apple.com/documentation/xcode/notarizing_your_app_before_distribution/customizing_the_notarization_workflow?language=objc # and # https://developer.apple.com/documentation/xcode/notarizing_your_app_before_distribution/resolving_common_notarization_issues?language=objc with open(APPLE_ID) as f: un, pw = f.read().strip().split(':') with open(os.path.join(app_path, 'Contents', 'Info.plist'), 'rb') as f: primary_bundle_id = plistlib.load(f)['CFBundleIdentifier'] zip_path = os.path.join(os.path.dirname(app_path), 'calibre.zip') print('Creating zip file for notarization') with timeit() as times: run('ditto', '-c', '-k', '--zlibCompressionLevel', '9', '--keepParent', app_path, zip_path) print('ZIP file of {} MB created in {} minutes and {} seconds'.format( os.path.getsize(zip_path) // 1024**2, *times)) def altool(*args): args = ['xcrun', 'altool' ] + list(args) + ['--username', un, '--password', pw] p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() stdout = stdout.decode('utf-8') stderr = stderr.decode('utf-8') output = stdout + '\n' + stderr print(output) if p.wait() != 0: print('The command {} failed with error code: {}'.format( args, p.returncode)) try: run_shell() finally: raise SystemExit(1) return output print('Submitting for notarization') with timeit() as times: try: stdout = altool('--notarize-app', '-f', zip_path, '--primary-bundle-id', primary_bundle_id) finally: os.remove(zip_path) request_id = re.search(r'RequestUUID = (\S+)', stdout).group(1) status = 'in progress' print('Submission done in {} minutes and {} seconds'.format(*times)) print('Waiting for notarization') with timeit() as times: start_time = time.monotonic() while status == 'in progress': time.sleep(30) print( 'Checking if notarization is complete, time elapsed: {:.1f} seconds', time.monotonic() - start_time) stdout = altool('--notarization-info', request_id) status = re.search(r'Status\s*:\s+(.+)', stdout).group(1).strip() print('Notarization done in {} minutes and {} seconds'.format(*times)) if status.lower() != 'success': log_url = re.search(r'LogFileURL\s*:\s+(.+)', stdout).group(1).strip() if log_url != '(null)': log = json.loads(urlopen(log_url).read()) pprint(log) raise SystemExit('Notarization failed, see JSON log above') with timeit() as times: print('Stapling notarization ticket') run('xcrun', 'stapler', 'staple', '-v', app_path) run('xcrun', 'stapler', 'validate', '-v', app_path) run('spctl', '--verbose=4', '--assess', '--type', 'execute', app_path) print('Stapling took {} minutes and {} seconds'.format(*times))