def loadTemplateAndReturnError_(self, path): if path in self.loadedTemplates: return "%s included recursively" % path else: self.loadedTemplates.add(path) plist = NSDictionary.dictionaryWithContentsOfFile_(path) if not plist: error = "Couldn't read dictionary from plist at %s" % (path) LogWarning("%@", error) return error templateFormat = plist.get("TemplateFormat", "1.0") if templateFormat not in ["1.0", "1.1"]: LogWarning("Unknown format version %@", templateFormat) for key in plist.iterkeys(): if key == "IncludeTemplates": for includePath in plist["IncludeTemplates"]: LogInfo("Including template %@", includePath) error = self.loadTemplateAndReturnError_(includePath) if error: return error elif key == "SourcePath": self.setSourcePath_(plist["SourcePath"]) elif key == "ApplyUpdates": self.setApplyUpdates_(plist["ApplyUpdates"]) elif key == "AdditionalPackages": if not self.setAdditionalPackages_( plist["AdditionalPackages"]): msg = "Additional packages failed verification" if self.additionalPackageError: msg += ":\n" + self.additionalPackageError return msg elif key == "OutputPath": self.setOutputPath_(plist["OutputPath"]) elif key == "VolumeName": self.setVolumeName_(plist["VolumeName"]) elif key == "VolumeSize": self.setVolumeSize_(plist["VolumeSize"]) elif key == "FinalizeAsrImagescan": self.setFinalizeAsrImagescan_(plist["FinalizeAsrImagescan"]) elif key == "Filesystem": if plist["Filesystem"] not in ["apfs", "hfs"]: error = "Unknown Filesystem value '%s'" % plist[ "Filesystem"] return error self.setFilesystem_(plist["Filesystem"]) elif key == "TemplateFormat": pass else: LogWarning("Unknown key '%@' in template", key) return None
def setFilesystem_(self, filesystem): LogDebug("Setting filesystem for workflow to '%@'", filesystem) if filesystem == "hfs": if IEDUtil.hostMajorVersion() > 13: LogWarning("Ignoring filesystem setting, only apfs is supported") elif filesystem == "apfs": if IEDUtil.hostMajorVersion() < 13: LogWarning("Ignoring filesystem setting, only hfs is supported") else: LogWarning("Unrecognized filesystem setting '%@'", filesystem) self._filesystem = filesystem
def getInstalledPkgSize_(cls, pkgPath): # For apps just return the size on disk. name, ext = os.path.splitext(pkgPath) if ext == u".app": return cls.getPackageSize_(pkgPath) # For packages try to get the size requirements with installer. pkgFileName = os.path.os.path.basename(pkgPath) tempdir = tempfile.mkdtemp() try: symlinkPath = os.path.join(tempdir, pkgFileName) os.symlink(pkgPath, symlinkPath) p = subprocess.Popen([u"/usr/sbin/installer", u"-pkginfo", u"-verbose", u"-plist", u"-pkg", symlinkPath], stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() finally: try: shutil.rmtree(tempdir) except BaseException as e: LogWarning(u"Unable to remove tempdir: %@", unicode(e)) # Try to handle some common scenarios when installer fails. if p.returncode == -11: LogWarning(u"Estimating package size since installer -pkginfo " u"'%@' crashed", pkgPath) return cls.getPackageSize_(pkgPath) * 2 elif p.returncode != 0: mountPoints = IEDMountInfo.getMountPoints() fsInfo = mountPoints[cls.findMountPoint_(pkgPath)] if not fsInfo[u"islocal"]: LogWarning(u"Estimating package size since installer -pkginfo " u"failed and '%@' is on a remote (%@) filesystem", pkgPath, fsInfo[u"fstypename"]) return cls.getPackageSize_(pkgPath) * 2 else: LogError(u"installer -pkginfo -pkg '%@' failed with exit code %d", pkgPath, p.returncode) return None outData = NSData.dataWithBytes_length_(out, len(out)) plist, format, error = NSPropertyListSerialization.propertyListWithData_options_format_error_(outData, NSPropertyListImmutable, None, None) if not plist: LogError(u"Error decoding plist: %@", error) return None LogDebug(u"%@ requires %@", pkgPath, cls.formatBytes_(int(plist[u"Size"]) * 1024)) return int(plist[u"Size"]) * 1024
def pruneAndCreateSymlinks(self, symlinks): LogInfo(u"Pruning cache") self.symlinks = symlinks # Create a reverse dictionary and a set of filenames. names = dict() filenames = set() for sha1, name in symlinks.iteritems(): names[name] = sha1 filenames.add(name) filenames.add(sha1) for item in os.listdir(self.updateDir): try: itempath = os.path.join(self.updateDir, item) if item not in filenames: LogInfo(u"Removing %s" % item) os.unlink(itempath) except OSError as e: LogWarning(u"Cache pruning of %s failed: %s" % (item, unicode(e))) for sha1 in symlinks.iterkeys(): sha1Path = self.cachePath_(sha1) linkPath = self.updatePath_(sha1) name = os.path.basename(linkPath) if os.path.exists(sha1Path): if os.path.lexists(linkPath): if os.readlink(linkPath) == sha1: LogInfo(u"Found %s -> %s" % (name, sha1)) continue LogInfo(u"Removing stale link %s -> %s" % (name, os.readlink(linkPath))) try: os.unlink(linkPath) except OSError as e: LogWarning(u"Cache pruning of %s failed: %s" % (name, unicode(c))) continue LogInfo(u"Creating %s -> %s" % (name, sha1)) os.symlink(sha1, linkPath) else: if os.path.lexists(linkPath): LogInfo(u"Removing stale link %s -> %s" % (name, os.readlink(linkPath))) try: os.unlink(linkPath) except OSError as e: LogWarning(u"Cache pruning of %s failed: %s" % (name, unicode(c)))
def loadProfilesFromPlist_(self, plist): """Load UpdateProfiles from a plist dictionary.""" LogInfo("Loading update profiles with PublicationDate %@", plist["PublicationDate"]) try: # FIXME: Add profile verification. self.profiles = dict() for name, updates in plist["Profiles"].iteritems(): profile = list() for update in updates: profile.append(plist["Updates"][update]) self.profiles[name] = profile self.publicationDate = plist["PublicationDate"] self.updatePaths = dict() for name, update in plist["Updates"].iteritems(): filename, ext = os.path.splitext( os.path.basename(update["url"])) self.updatePaths[update["sha1"]] = "%s(%s)%s" % ( filename, update["sha1"][:7], ext) self.deprecatedInstallerBuilds = dict() try: for replacement, builds in plist[ "DeprecatedInstallers"].iteritems(): for build in builds: self.deprecatedInstallerBuilds[build] = replacement except KeyError: LogWarning("No deprecated installers in profile") self.deprecatedOS = False try: for osVerStr in plist["DeprecatedOSVersions"]: deprecatedVerMajor = IEDUtil.splitVersion(osVerStr)[1] if IEDUtil.hostMajorVersion() <= deprecatedVerMajor: self.deprecatedOS = True LogWarning("%@ is no longer being updated by Apple", osVerStr) break except KeyError: LogWarning("No deprecated OS versions in profile") if self.delegate: self.delegate.profilesUpdated() except BaseException as e: LogError("Failed to load profile: %@", unicode(e))
def loadTemplateAndReturnError_(self, path): if path in self.loadedTemplates: return u"%s included recursively" % path else: self.loadedTemplates.add(path) plist = NSDictionary.dictionaryWithContentsOfFile_(path) if not plist: error = u"Couldn't read dictionary from plist at %s" % (path) LogWarning(u"%@", error) return error templateFormat = plist.get(u"TemplateFormat", u"1.0") if templateFormat != u"1.0": LogWarning(u"Unknown format version %@", templateFormat) for key in plist.keys(): if key == u"IncludeTemplates": for includePath in plist[u"IncludeTemplates"]: LogInfo(u"Including template %@", includePath) error = self.loadTemplateAndReturnError_(includePath) if error: return error elif key == u"SourcePath": self.setSourcePath_(plist[u"SourcePath"]) elif key == u"ApplyUpdates": self.setApplyUpdates_(plist[u"ApplyUpdates"]) elif key == u"AdditionalPackages": if not self.setAdditionalPackages_( plist[u"AdditionalPackages"]): msg = u"Additional packages failed verification" if self.additionalPackageError: msg += u":\n" + self.additionalPackageError return msg elif key == u"OutputPath": self.setOutputPath_(plist[u"OutputPath"]) elif key == u"VolumeName": self.setVolumeName_(plist[u"VolumeName"]) elif key == u"VolumeSize": self.setVolumeSize_(plist[u"VolumeSize"]) elif key == u"FinalizeAsrImagescan": self.setFinalizeAsrImagescan_(plist[u"FinalizeAsrImagescan"]) elif key == u"TemplateFormat": pass else: LogWarning(u"Unknown key '%@' in template", key) return None
def getFlatPkgInfo_(cls, pkgPath): tempdir = tempfile.mkdtemp() try: # Extract to tempdir, excluding all except Distribution and # PackageInfo. subprocess.check_output([ "/usr/bin/xar", "-x", "--exclude", "^[^DP]", "--exclude", "Payload", "-C", tempdir, "-f", pkgPath ]) distPath = os.path.join(tempdir, "Distribution") pkgInfoPath = os.path.join(tempdir, "PackageInfo") if os.path.exists(distPath): return cls.getSizeFromDistribution_(distPath) elif os.path.exists(pkgInfoPath): return cls.getSizeFromPackageInfo_(pkgInfoPath) else: LogError("No Distribution or PackageInfo found in '%@'", pkgPath) return None except subprocess.CalledProcessError as e: LogError("xar failed with return code %d", e.returncode) return None finally: try: shutil.rmtree(tempdir) except Exception as e: LogWarning("Unable to remove tempdir: %@", str(e))
def loadProfilesFromPlist_(self, plist): """Load UpdateProfiles from a plist dictionary.""" LogInfo(u"Loading update profiles with PublicationDate %@", plist[u"PublicationDate"]) self.profiles = dict() for name, updates in plist[u"Profiles"].iteritems(): profile = list() for update in updates: profile.append(plist[u"Updates"][update]) self.profiles[name] = profile self.publicationDate = plist[u"PublicationDate"] self.updatePaths = dict() for name, update in plist[u"Updates"].iteritems(): filename, ext = os.path.splitext(os.path.basename(update[u"url"])) self.updatePaths[update[u"sha1"]] = u"%s(%s)%s" % ( filename, update[u"sha1"][:7], ext) self.deprecatedInstallerBuilds = dict() try: for replacement, builds in plist[ u"DeprecatedInstallers"].iteritems(): for build in builds: self.deprecatedInstallerBuilds[build] = replacement except KeyError: LogWarning(u"No deprecated installers") if self.delegate: self.delegate.profilesUpdated()
def stopListening(self): LogDebug("stopListening") self.watchThread.cancel() try: os.unlink(self.socketPath) except BaseException as e: LogWarning("Couldn't remove listener socket %@: %@", self.socketPath, str(e))
def getvar(m): try: return variables[m.group("key")] except KeyError as err: LogWarning("Template references undefined variable: %%%@%%", m.group("key")) return u"%%%s%%" % m.group("key")
def gui_main(): IEDLog.IEDLogToController = True IEDLog.IEDLogToSyslog = True IEDLog.IEDLogToStdOut = True IEDLog.IEDLogStdOutLogLevel = IEDLog.IEDLogLevelDebug try: logFile = os.path.join(get_log_dir(), u"AutoDMG-%s.log" % get_date_string()) IEDLog.IEDLogFileHandle = open(logFile, u"a", buffering=1) IEDLog.IEDLogToFile = True except OSError as e: IEDLog.IEDLogToFile = False LogWarning(u"Couldn't open %s for writing" % (logFile)).encode(u"utf-8") import AppKit from PyObjCTools import AppHelper # import modules containing classes required to start application and load MainMenu.nib import IEDAppDelegate import IEDController import IEDSourceSelector import IEDAddPkgController import IEDAppVersionController # pass control to AppKit AppHelper.runEventLoop(unexpectedErrorAlert=gui_unexpected_error_alert) return os.EX_OK
def getInstalledPkgSizeFromInstaller_(cls, pkgPath): pkgFileName = os.path.os.path.basename(pkgPath) tempdir = tempfile.mkdtemp() try: symlinkPath = os.path.join(tempdir, pkgFileName) os.symlink(pkgPath, symlinkPath) p = subprocess.Popen([ "/usr/sbin/installer", "-pkginfo", "-verbose", "-plist", "-pkg", symlinkPath ], stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() finally: try: shutil.rmtree(tempdir) except BaseException as e: LogWarning("Unable to remove tempdir: %@", str(e)) if p.returncode != 0: LogDebug( "/usr/sbin/installer failed to determine size requirements") return None outData = NSData.dataWithBytes_length_(out, len(out)) plist, format, error = NSPropertyListSerialization.propertyListWithData_options_format_error_( outData, NSPropertyListImmutable, None, None) if not plist: LogError("Error decoding plist: %@", error) return None LogDebug("Installer says %@ requires %@", pkgPath, cls.formatByteSize_(int(plist["Size"]) * 1024)) return int(plist["Size"]) * 1024
def get_log_dir(): logDir = os.path.expanduser(u"~/Library/Logs/AutoDMG") if not os.path.exists(logDir): try: os.makedirs(logDir) except OSError as e: LogWarning(u"Couldn't create %s" % (logDir)) return logDir
def deleteTempDir(self): if self.tempDir: try: shutil.rmtree(self.tempDir) except OSError as e: LogWarning("Can't remove temporary directory '%@': %@", self.tempDir, str(e)) finally: self.tempDir = None
def calculateInstalledPkgSize_(cls, pkgPath): if os.path.isdir(pkgPath): size = cls.getBundlePkgInfo_(pkgPath) else: size = cls.getFlatPkgInfo_(pkgPath) if size is None: # If all else fails, estimate package size requirements. LogWarning("Estimating package size for '%@'", pkgPath) size = cls.getPackageSize_(pkgPath) * 2 LogDebug("%@ needs %@", pkgPath, cls.formatByteSize_(size)) return size
def loadImageTemplate_(self, mountPoint): LogDebug("checkTemplate:%@", mountPoint) try: path = glob.glob(os.path.join(mountPoint, "private/var/log/*.adtmpl"))[0] except IndexError: return None template = IEDTemplate.alloc().init() error = template.loadTemplateAndReturnError_(path) if error: LogWarning("Error reading %@ from image: %@", os.path.basename(path), error) return None return template
def attachedDMGs(self): dmgMounts = dict() LogDebug(u"Finding already attached dmgs") p = subprocess.Popen([u"/usr/bin/hdiutil", u"info", u"-plist"], bufsize=1, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() if p.returncode != 0: errstr = u"hdiutil info failed with return code %d" % p.returncode if err: errstr += u": %s" % err.decode(u"utf-8") LogWarning("%@", errstr) return dmgMounts # Strip EULA text. outXML = out[out.find("<?xml"):] outData = NSData.dataWithBytes_length_(outXML, len(outXML)) plist, format, error = \ NSPropertyListSerialization.propertyListWithData_options_format_error_(outData, NSPropertyListImmutable, None, None) for dmgInfo in plist[u"images"]: for entity in dmgInfo.get(u"system-entities", []): try: image_path = dmgInfo[u"image-path"] alias_path = u"" bookmark = CFURLCreateBookmarkDataFromAliasRecord( kCFAllocatorDefault, dmgInfo[u"image-alias"]) if bookmark: url, stale, error = CFURLCreateByResolvingBookmarkData( None, bookmark, kCFBookmarkResolutionWithoutUIMask, None, None, None, None) if url: alias_path = url.path() else: LogDebug(u"Couldn't resolve bookmark: %@", error.localizedDescription()) for path in set(x for x in (image_path, alias_path) if x): dmgMounts[path] = entity[u"mount-point"] LogDebug(u"'%@' is already mounted at '%@'", path, entity[u"mount-point"]) break except IndexError: pass except KeyError: pass return dmgMounts
def launchScript_(self, args): LogDebug("launchScript:") if (self.authPassword() is None) and (os.getuid() != 0): # Use GUI dialog to elevate privileges. task = STPrivilegedTask.alloc().init() task.setLaunchPath_(args[0]) task.setArguments_(args[1:]) status = task.launch() LogNotice("Install task launched with return code: %d", status) if status: self.performSelectorOnMainThread_withObject_waitUntilDone_( self.handleLaunchScriptError_, status, False) else: task = NSTask.alloc().init() if os.getuid() == 0: # No privilege elevation necessary. task.setLaunchPath_(args[0]) task.setArguments_(args[1:]) else: # Use sudo to elevate privileges. task.setLaunchPath_("/usr/bin/sudo") task.setArguments_(["-kSE"] + args) # Send password to sudo on stdin. passwordpipe = NSPipe.alloc().init() task.setStandardInput_(passwordpipe.fileHandleForReading()) task.setStandardOutput_( NSFileHandle.fileHandleWithNullDevice()) task.setStandardError_(NSFileHandle.fileHandleWithNullDevice()) writer = passwordpipe.fileHandleForWriting() pwd = NSString.stringWithString_(self.authPassword() + "\n") writer.writeData_(pwd.dataUsingEncoding_(NSUTF8StringEncoding)) writer.closeFile() try: task.launch() LogNotice("Install task launched%@", " with sudo" if os.getuid() != 0 else "") except BaseException as e: LogWarning("Install task launch failed with exception") self.performSelectorOnMainThread_withObject_waitUntilDone_( self.handleLaunchScriptError_, str(e), False) return task.waitUntilExit() LogNotice("Install task finished with exit status %d", task.terminationStatus()) if task.terminationStatus() != 0: self.performSelectorOnMainThread_withObject_waitUntilDone_( self.handleLaunchScriptError_, "Install task failed with status %d" % task.terminationStatus(), False)
def checkForAppUpdateSilently_(self, silently): self.checkSilently = silently # Create a buffer for data. self.plistData = NSMutableData.alloc().init() # Start download. osVer, osBuild = IEDUtil.readSystemVersion_("/")[1:3] appVer, appBuild = IEDUtil.getAppVersion() urlString = "%s?osVer=%s&osBuild=%s&appVer=%s&appBuild=%s" % ( self.defaults.stringForKey_("AppVersionURL"), osVer, osBuild, appVer, appBuild) url = NSURL.URLWithString_(urlString) request = NSURLRequest.requestWithURL_(url) self.connection = NSURLConnection.connectionWithRequest_delegate_( request, self) LogDebug("connection = %@", self.connection) if not self.connection: LogWarning("Connection to %@ failed", url)
def saveTemplateAndReturnError_(self, path): plist = NSMutableDictionary.alloc().init() plist[u"TemplateFormat"] = self.templateFormat = u"1.0" plist[u"AdditionalPackages"] = self.additionalPackages plist[u"ApplyUpdates"] = self.applyUpdates plist[u"VolumeName"] = self.volumeName if self.sourcePath: plist[u"SourcePath"] = self.sourcePath if self.outputPath: plist[u"OutputPath"] = self.outputPath if self.volumeSize: plist[u"VolumeSize"] = self.volumeSize if plist.writeToFile_atomically_(path, False): return None else: error = u"Couldn't write dictionary to plist at %s" % (path) LogWarning(u"%@", error) return error
def saveTemplateAndReturnError_(self, path): plist = NSMutableDictionary.alloc().init() plist["TemplateFormat"] = self.templateFormat = "1.1" plist["AdditionalPackages"] = self.additionalPackages plist["ApplyUpdates"] = self.applyUpdates plist["VolumeName"] = self.volumeName if self.sourcePath: plist["SourcePath"] = self.sourcePath if self.outputPath: plist["OutputPath"] = self.outputPath if self.volumeSize: plist["VolumeSize"] = self.volumeSize if not self.finalizeAsrImagescan: plist["FinalizeAsrImagescan"] = self.finalizeAsrImagescan if self.filesystem: plist["Filesystem"] = self.filesystem if plist.writeToFile_atomically_(path, False): return None else: error = "Couldn't write dictionary to plist at %s" % (path) LogWarning("%@", error) return error
def updateFromURL_(self, url): """Download the latest UpdateProfiles.plist.""" LogDebug("updateFromURL:%@", url) if self.profileUpdateWindow: # Show the progress window. self.progressBar.setIndeterminate_(True) self.progressBar.startAnimation_(self) self.profileUpdateWindow.makeKeyAndOrderFront_(self) # Create a buffer for data. self.profileUpdateData = NSMutableData.alloc().init() # Start download. request = NSURLRequest.requestWithURL_(url) self.connection = NSURLConnection.connectionWithRequest_delegate_( request, self) LogDebug("connection = %@", self.connection) if not self.connection: LogWarning("Connection to %@ failed", url) if self.profileUpdateWindow: self.profileUpdateWindow.orderOut_(self) self.delegate.profileUpdateFailed_(error)
def getInstalledPkgSize_(cls, pkgPath): # For apps just return the size on disk. ext = os.path.splitext(pkgPath)[1].lower() if ext == ".app": return cls.getPackageSize_(pkgPath) elif ext in (".pkg", ".mpkg"): # For packages first try to get the size requirements with # installer. size = cls.getInstalledPkgSizeFromInstaller_(pkgPath) if size is None: # If this fails, manually extract the size requirements from # the package. return cls.calculateInstalledPkgSize_(pkgPath) else: return size elif ext == ".plist": # FIXME: Implement size calculation LogWarning("Using hardcoded size for InstallInfo.plist") return 12497424 else: LogError("Don't know how to calculate installed size for '%@'", pkgPath) return None
def cli_main(argv): IEDLog.IEDLogToController = False IEDLog.IEDLogToSyslog = True IEDLog.IEDLogToStdOut = True IEDLog.IEDLogToFile = False from IEDCLIController import IEDCLIController clicontroller = IEDCLIController.alloc().init() try: # Initialize user defaults before application starts. defaults = NSUserDefaults.standardUserDefaults() defaultsPath = NSBundle.mainBundle().pathForResource_ofType_( u"Defaults", u"plist") defaultsDict = NSDictionary.dictionaryWithContentsOfFile_(defaultsPath) defaults.registerDefaults_(defaultsDict) p = argparse.ArgumentParser() p.add_argument(u"-v", u"--verbose", action=u"store_true", help=u"Verbose output") p.add_argument(u"-L", u"--log-level", type=int, choices=range(0, 8), default=6, metavar=u"LEVEL", help=u"Log level (0-7), default 6") p.add_argument(u"-l", u"--logfile", help=u"Log to file") p.add_argument(u"-r", u"--root", action=u"store_true", help=u"Allow running as root") sp = p.add_subparsers(title=u"subcommands", dest=u"subcommand") # Populate subparser for each verb. for verb in clicontroller.listVerbs(): verb_method = getattr(clicontroller, u"cmd%s_" % verb.capitalize()) addargs_method = getattr(clicontroller, u"addargs%s_" % verb.capitalize()) parser = sp.add_parser(verb, help=verb_method.__doc__) addargs_method(parser) parser.set_defaults(func=verb_method) args = p.parse_args(argv) if args.verbose: IEDLog.IEDLogStdOutLogLevel = IEDLog.IEDLogLevelInfo else: IEDLog.IEDLogStdOutLogLevel = IEDLog.IEDLogLevelNotice IEDLog.IEDLogFileLogLevel = args.log_level if args.logfile == u"-": # Redirect log to stdout instead. IEDLog.IEDLogFileHandle = sys.stdout IEDLog.IEDLogToFile = True IEDLog.IEDLogToStdOut = False else: try: if args.logfile: logFile = args.logfile else: logFile = os.path.join( get_log_dir(), u"AutoDMG-%s.log" % get_date_string()) IEDLog.IEDLogFileHandle = open(logFile, u"a", buffering=1) except OSError as e: print >> sys.stderr, (u"Couldn't open %s for writing" % logFile).encode(u"utf-8") return os.EX_CANTCREAT IEDLog.IEDLogToFile = True # Check if we're running with root. if os.getuid() == 0: if args.root: fm = NSFileManager.defaultManager() url, error = fm.URLForDirectory_inDomain_appropriateForURL_create_error_( NSApplicationSupportDirectory, NSUserDomainMask, None, False, None) LogWarning(u"Running as root, using %@", os.path.join(url.path(), u"AutoDMG")) else: LogError( u"Running as root isn't recommended (use -r to override)") return os.EX_USAGE # Log version info on startup. version, build = IEDUtil.getAppVersion() LogInfo(u"AutoDMG v%@ build %@", version, build) name, version, build = IEDUtil.readSystemVersion_(u"/") LogInfo(u"%@ %@ %@", name, version, build) LogInfo(u"%@ %@ (%@)", platform.python_implementation(), platform.python_version(), platform.python_compiler()) LogInfo(u"PyObjC %@", objc.__version__) return args.func(args) finally: clicontroller.cleanup()
def openTemplateAtURL_(self, url): LogDebug("openTemplateAtURL:%@", url) self.templateURL = None template = IEDTemplate.alloc().init() error = template.loadTemplateAndReturnError_(url.path()) if error: self.displayAlert_text_("Couldn't open template", error) return False self.templateURL = url NSDocumentController.sharedDocumentController( ).noteNewRecentDocumentURL_(url) # AdditionalPackages. LogDebug("Setting additional packages to %@", template.additionalPackages) self.addPkgController.replacePackagesWithPaths_( template.additionalPackages) # ApplyUpdates. if template.applyUpdates: LogDebug("Enable updates") self.updateController.applyUpdatesCheckbox.setState_(NSOnState) else: LogDebug("Disable updates") self.updateController.applyUpdatesCheckbox.setState_(NSOffState) # VolumeName. self.volumeName.setStringValue_("") if template.volumeName: LogDebug("Setting volume name to %@", template.volumeName) self.volumeName.setStringValue_(template.volumeName) # VolumeSize. self.volumeSize.setStringValue_("") if template.volumeSize: LogDebug("Setting volume size to %@", template.volumeSize) self.volumeSize.setIntValue_(template.volumeSize) # Finalize task: ASR imagescan. self.finalizeAsrImagescan.setState_(NSOnState) if template.finalizeAsrImagescan == False: LogDebug("Setting 'Finalize: Scan for restore' to %@", template.finalizeAsrImagescan) self.finalizeAsrImagescan.setState_(NSOffState) # Filesystem. if template.filesystem: if IEDUtil.hostMajorVersion() < 13: if template.filesystem != "hfs": LogWarning( "Ignoring template filesystem (%@), using hfs on 10.%d", template.filesystem, IEDUtil.hostMajorVersion()) else: LogDebug("Setting filesystem to %@", template.filesystem) for item in self.filesystem.itemArray(): if item.representedObject() == template.filesystem: self.filesystem.selectItem_(item) break else: LogWarning("Unknown filesystem '%@'", template.filesystem) # SourcePath. if template.sourcePath: LogDebug("Setting source to %@", template.sourcePath) self.setBusy_(True) self.workflow.setSource_(template.sourcePath) return True
def continuePrepare(self): LogDebug("continuePrepare") # Generate a list of packages to install. self.packagesToInstall = list() if self.sourceType == IEDWorkflow.INSTALL_ESD: self.packagesToInstall.append( os.path.join(self.installerMountPoint, "Packages", "OSInstall.mpkg")) elif self.sourceType == IEDWorkflow.INSTALL_INFO: self.packagesToInstall.append( os.path.join(self.newSourcePath, "Contents", "SharedSupport", "InstallInfo.plist")) for package in self.additionalPackages: if package.path().endswith(".dmg"): mountPoint = self.attachedPackageDMGs[package.path()] LogDebug("Looking for packages and applications in %@: %@", mountPoint, glob.glob(os.path.join(mountPoint, "*"))) packagePaths = glob.glob(os.path.join(mountPoint, "*.mpkg")) packagePaths += glob.glob(os.path.join(mountPoint, "*.pkg")) packagePaths += glob.glob(os.path.join(mountPoint, "*.app")) if len(packagePaths) == 0: self.fail_details_( "Nothing found to install", "No package or application found in %s" % package.name()) return elif len(packagePaths) > 1: LogWarning("Multiple items found in %@, using %@", package.path(), packagePaths[0]) self.packagesToInstall.append(packagePaths[0]) else: self.packagesToInstall.append(package.path()) if len(self.packagesToInstall) == 0: self.delegate.buildFailed_details_( "Nothing to do", "There are no packages to install") self.stop() return # Calculate disk image size requirements. sizeRequirement = 0 LogInfo("%d packages to install:", len(self.packagesToInstall)) for path in self.packagesToInstall: try: installedSize = IEDUtil.getInstalledPkgSize_(path) except BaseException as e: LogError("Size calculation of %@ failed: %@", path, unicode(e)) installedSize = None if installedSize is None: self.delegate.buildFailed_details_( "Failed to determine installed size", "Unable to determine installation size requirements for %s" % path) self.stop() return LogInfo(" %@ requires %@", path, IEDUtil.formatByteSize_(installedSize)) sizeRequirement += installedSize sizeReqStr = IEDUtil.formatByteSize_(sizeRequirement) LogInfo("Workflow requires a %@ disk image", sizeReqStr) if self.volumeSize() is None: # Calculate DMG size. Multiply package requirements by 1.1, round # to the nearest GB, and add 23. self.setVolumeSize_( int((float(sizeRequirement) * 1.1) / (1000.0 * 1000.0 * 1000.0) + 23.5)) else: # Make sure user specified image size is large enough. if sizeRequirement > self.volumeSize() * 1000 * 1000 * 1000: details = "Workflow requires %s and disk image is %d GB" % ( sizeReqStr, self.volumeSize()) self.delegate.buildFailed_details_( "Disk image too small for workflow", details) self.stop() return LogInfo("Using a %d GB disk image", self.volumeSize()) # Task done. self.nextTask()