def sourceSucceeded_(self, info): self.installerName = info["name"] self.installerVersion = info["version"] self.installerBuild = info["build"] self.sourceLabel.setStringValue_( "%s %s %s" % (info["name"], info["version"], info["build"])) self.sourceLabel.setTextColor_(NSColor.controlTextColor()) self.updateController.loadProfileForVersion_build_( info["version"], info["build"]) template = info["template"] if template: LogInfo("Template found in image: %@", repr(template)) # Don't default to applying updates to an image that was built # with updates applied, and vice versa. if template.applyUpdates: self.updateController.applyUpdatesCheckbox.setState_( NSOffState) else: self.updateController.applyUpdatesCheckbox.setState_(NSOnState) else: if info["sourceType"] == IEDWorkflow.SYSTEM_IMAGE: LogInfo("No template found in image") # If the image doesn't have a template inside, assume that updates # were applied. self.updateController.applyUpdatesCheckbox.setState_( NSOffState) self.setBusy_(False)
def taskFinalize(self): LogNotice(u"Finalize task running") self.delegate.buildSetProgressMessage_( u"Scanning disk image for restore") # The script is wrapped with progresswatcher.py which parses script # output and sends it back as notifications to IEDSocketListener. args = [ NSBundle.mainBundle().pathForResource_ofType_( u"progresswatcher", u"py"), u"--socket", self.listenerPath, u"imagescan", self.outputPath(), ] LogInfo(u"Launching finalize with arguments:") for arg in args: LogInfo(u" '%@'", arg) try: p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) out = p.communicate()[0].decode(u"utf-8") LogDebug(u"Finalize exited with status %d and output '%@'", p.returncode, out) if p.returncode != 0: errMsg = u"Finalize task failed with status %d" % p.returncode LogError(u"%@: %@", errMsg, out) self.fail_details_(errMsg, out) except BaseException as e: LogError(u"Failed to launch finalize task: %@", unicode(e)) self.fail_details_(u"Failed to launch finalize task", unicode(e))
def taskInstall(self): LogNotice("Install task running") # The script is wrapped with progresswatcher.py which parses script # output and sends it back as notifications to IEDSocketListener. args = [ NSBundle.mainBundle().pathForResource_ofType_( "progresswatcher", "py"), "--cd", NSBundle.mainBundle().resourcePath(), "--socket", self.listenerPath, "installesdtodmg", "--user", str(os.getuid()), "--group", str(os.getgid()), "--fstype", self.filesystem(), "--output", self.outputPath(), "--volume-name", self.volumeName(), "--size", str(self.volumeSize()), "--template", self.templatePath, ] if self.sourceType == IEDWorkflow.SYSTEM_IMAGE: args.extend(["--baseimage", self.source()]) args.extend(self.packagesToInstall) LogInfo("Launching install with arguments:") for arg in args: LogInfo(" '%@'", arg) self.performSelectorInBackground_withObject_(self.launchScript_, args)
def setAdditionalPackages_(self, packagePaths): for packagePath in packagePaths: path = IEDUtil.resolvePath_(os.path.expanduser(packagePath)) if not path: LogError(u"Package '%@' not found", packagePath) return False if path not in self.additionalPackages: LogInfo(u"Adding '%@' to additional packages", path) self.additionalPackages.append(IEDUtil.resolvePath_(path)) else: LogInfo(u"Skipping duplicate package '%@'", path) return True
def initialize(self): # Log version info on startup. version, build = IEDUtil.getAppVersion() LogInfo("AutoDMG v%@ build %@", version, build) name, version, build = IEDUtil.readSystemVersion_("/") LogInfo("%@ %@ %@", name, version, build) LogInfo("%@ %@ (%@)", platform.python_implementation(), platform.python_version(), platform.python_compiler()) LogInfo("PyObjC %@", pyObjCVersion) # Initialize user defaults before application starts. defaultsPath = NSBundle.mainBundle().pathForResource_ofType_( "Defaults", "plist") defaultsDict = NSDictionary.dictionaryWithContentsOfFile_(defaultsPath) defaults.registerDefaults_(defaultsDict)
def checkForProfileUpdates_(self, sender): LogInfo(u"Checking for updates") self.disableControls() defaults = NSUserDefaults.standardUserDefaults() url = NSURL.URLWithString_( defaults.stringForKey_(u"UpdateProfilesURL")) self.profileController.updateFromURL_(url)
def taskFinalize(self): LogNotice("Finalize task running") self.delegate.buildSetProgressMessage_("Scanning disk image for restore") # The script is wrapped with progresswatcher.py which parses script # output and sends it back as notifications to IEDSocketListener. args = [ NSBundle.mainBundle().pathForResource_ofType_("progresswatcher", "py"), "--socket", self.listenerPath, "imagescan", self.outputPath(), ] LogInfo("Launching finalize with arguments:") for arg in args: LogInfo(" '%@'", arg) self.performSelectorInBackground_withObject_(self.launchFinalize_, args)
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 doCheckForProfileUpdates(self): LogInfo("Checking for updates") self.dateBeforeUpdating = self.profileController.publicationDate self.disableControls() defaults = NSUserDefaults.standardUserDefaults() url = NSURL.URLWithString_(defaults.stringForKey_("UpdateProfilesURL")) self.profileController.updateFromURL_(url)
def connectionDidFinishLoading_(self, connection): LogInfo(u"%@ finished downloading to %@", self.package.name(), self.cacheTmpPath_(self.package.sha1())) self.fileHandle.closeFile() self.delegate.downloadStopped_(self.package) if self.checksum.hexdigest() == self.package.sha1(): try: os.rename(self.cacheTmpPath_(self.package.sha1()), self.cachePath_(self.package.sha1())) except OSError as e: error = u"Failed when moving download to %s: %s" % ( self.cachePath_(self.package.sha1()), unicode(e)) LogError(error) self.delegate.downloadFailed_withError_(self.package, error) return linkPath = self.updatePath_(self.package.sha1()) try: os.symlink(self.package.sha1(), linkPath) except OSError as e: error = u"Failed when creating link from %s to %s: %s" % ( self.package.sha1(), linkPath, unicode(e)) LogError(error) self.delegate.downloadFailed_withError_(self.package, error) return LogNotice(u"%@ added to cache with sha1 %@", self.package.name(), self.package.sha1()) self.delegate.downloadSucceeded_(self.package) self.downloadNextUpdate() else: error = u"Expected sha1 checksum %s but got %s" % ( sha1.lower(), m.hexdigest().lower()) LogError(error) self.delegate.downloadFailed_withError_(self.package, error)
def taskFinalize(self): LogNotice(u"Finalize task running") self.delegate.buildSetProgressMessage_(u"Scanning disk image for restore") # The script is wrapped with progresswatcher.py which parses script # output and sends it back as notifications to IEDSocketListener. args = [ NSBundle.mainBundle().pathForResource_ofType_(u"progresswatcher", u"py"), u"--socket", self.listenerPath, u"imagescan", self.outputPath(), ] LogInfo(u"Launching finalize with arguments:") for arg in args: LogInfo(u" '%@'", arg) subprocess.Popen(args)
def saveUsersProfiles_(self, plist): """Save UpdateProfiles.plist to application support.""" LogInfo("Saving update profiles with PublicationDate %@", plist["PublicationDate"]) if not plist.writeToFile_atomically_(self.userUpdateProfilesPath, False): LogError("Failed to write %@", self.userUpdateProfilesPath)
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 downloadGotData_bytesRead_(self, package, bytes): percent = 100.0 * float(bytes) / float(package.size()) # Log progress if we've downloaded more than 10%, more than one second # has passed, or if we're at 100%. if (abs(percent - self.lastProgressPercent) >= 10.0) or \ (abs(self.lastProgressTimestamp.timeIntervalSinceNow()) >= 1.0) or \ (bytes == package.size()): LogInfo("progress: %.1f%%", percent) self.lastProgressPercent = percent self.lastProgressTimestamp = NSDate.alloc().init()
def setAdditionalPackages_(self, packagePaths): self.additionalPackageError = None for packagePath in packagePaths: path = IEDUtil.resolvePath_( os.path.abspath(os.path.expanduser(packagePath))) if not os.path.exists(path): self.additionalPackageError = u"Package '%s' not found" % packagePath LogError(u"'%@'", self.additionalPackageError) return False name, ext = os.path.splitext(path) if ext.lower() not in IEDUtil.PACKAGE_EXTENSIONS: self.additionalPackageError = u"'%s' is not valid software package" % packagePath LogError(u"'%@'", self.additionalPackageError) return False if path not in self.additionalPackages: LogInfo(u"Adding '%@' to additional packages", path) self.additionalPackages.append(IEDUtil.resolvePath_(path)) else: LogInfo(u"Skipping duplicate package '%@'", path) return True
def profileForVersion_Build_(self, version, build): """Return the update profile for a certain OS X version and build.""" try: profile = self.profiles["%s-%s" % (version, build)] LogInfo("Update profile for %@ %@: %@", version, build, ", ".join(u["name"] for u in profile)) except KeyError: profile = None LogNotice("No update profile for %@ %@", version, build) return profile
def taskPrepare(self): LogDebug("taskPrepare") # Attach any disk images containing update packages. self.attachedPackageDMGs = dict() self.numberOfDMGsToAttach = 0 for package in self.additionalPackages: if package.path().endswith(".dmg"): self.numberOfDMGsToAttach += 1 LogInfo("Attaching %@", package.path()) self.dmgHelper.attach_selector_(package.path(), self.attachPackageDMG_) if self.numberOfDMGsToAttach == 0: self.continuePrepare()
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 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 socketReceivedMessage_(self, msg): # The message is a dictionary with "action" as the only required key. action = msg["action"] if action == "update_progress": percent = msg["percent"] currentProgress = self.progress + self.currentPhase[ "weight"] * percent / 100.0 self.delegate.buildSetProgress_(currentProgress) elif action == "update_message": if self.lastUpdateMessage != msg["message"]: # Only log update messages when they change. LogInfo("%@", msg["message"]) self.lastUpdateMessage = msg["message"] self.delegate.buildSetProgressMessage_(msg["message"]) elif action == "select_phase": LogNotice("Script phase: %@", msg["phase"]) self.nextPhase() elif action == "log_message": LogMessage(msg["log_level"], msg["message"]) elif action == "notify_failure": self.fail_details_("Build failed", msg["message"]) elif action == "notify_success": LogNotice("Build success: %@", msg["message"]) elif action == "task_done": status = msg["termination_status"] if status == 0: self.nextTask() else: details = NSString.stringWithFormat_( "Task exited with status %@", msg["termination_status"]) LogError("%@", details) # Status codes 100-199 are from installesdtodmg.sh, and have # been preceeded by a "notify_failure" message. if (status < 100) or (status > 199): self.fail_details_("Build failed", details) else: self.fail_details_("Unknown progress notification", "Message: %@", msg)
def nextPhase(self): LogDebug("nextPhase, currentPhase == %@", self.currentPhase) if self.currentPhase: self.progress += self.currentPhase["weight"] LogInfo("Phase %@ with weight %ld finished after %.3f seconds", self.currentPhase["title"], self.currentPhase["weight"], time.time() - self.phaseStartTime) self.phaseStartTime = time.time() try: self.currentPhase = self.currentTask["phases"].pop(0) except IndexError: self.fail_details_("No phase left in task", traceback.format_stack()) return LogNotice("Starting phase: %@", self.currentPhase["title"]) self.delegate.buildSetPhase_(self.currentPhase["title"]) self.delegate.buildSetProgress_(self.progress)
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 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 start(self): LogNotice("Starting build") LogNotice("Using installer: %@ %@ %@", self.installerName, self.installerVersion, self.installerBuild) LogNotice("Using output path: %@", self.outputPath()) LogNotice("TMPDIR is set to: %@", os.getenv("TMPDIR")) self.delegate.buildStartingWithOutput_(self.outputPath()) self.createTempDir() LogDebug("Created temporary directory at %@", self.tempDir) if not self.template(): self.fail_details_( "Template missing", "A template for inclusion in the image is required.") return datestamp = datetime.datetime.today().strftime("%Y%m%d") self.templatePath = os.path.join(self.tempDir, "AutoDMG-%s.adtmpl" % datestamp) LogDebug("Saving template to %@", self.templatePath) error = self.template().saveTemplateAndReturnError_(self.templatePath) if error: self.fail_details_("Couldn't save template to tempdir", error) return # The workflow is split into tasks, and each task has one or more # phases. Each phase of the installation is given a weight for the # progress bar, calculated from the size of the installer package. # Phases that don't install packages get an estimated weight. self.tasks = list() # Prepare for install. self.tasks.append({ "title": "Prepare", "method": self.taskPrepare, "phases": [ { "title": "Preparing", "weight": 34 * 1024 * 1024 }, ], }) # Perform installation. installerPhases = [ { "title": "Starting install", "weight": 21 * 1024 * 1024 }, { "title": "Creating disk image", "weight": 21 * 1024 * 1024 }, ] if self.sourceType != IEDWorkflow.SYSTEM_IMAGE: installerPhases.append({ "title": "Installing OS", "weight": 4 * 1024 * 1024 * 1024, }) for package in self.additionalPackages: installerPhases.append({ "title": "Installing %s" % package.name(), # Add 100 MB to the weight to account for overhead. "weight": package.size() + 100 * 1024 * 1024, }) installerPhases.extend([ # hdiutil convert. { "title": "Converting disk image", "weight": 313 * 1024 * 1024 }, ]) self.tasks.append({ "title": "Install", "method": self.taskInstall, "phases": installerPhases, }) # Finalize image. (Skip adding this task if Finalize: Scan for restore is unchecked.) if self._finalizeAsrImagescan: self.tasks.append({ "title": "Finalize", "method": self.taskFinalize, "phases": [ { "title": "Scanning disk image", "weight": 2 * 1024 * 1024 }, { "title": "Scanning disk image", "weight": 1 * 1024 * 1024 }, { "title": "Scanning disk image", "weight": 150 * 1024 * 1024 }, { "title": "Scanning disk image", "weight": 17 * 1024 * 1024, "optional": True }, ], }) # Finish build. self.tasks.append({ "title": "Finish", "method": self.taskFinish, "phases": [ { "title": "Finishing", "weight": 1 * 1024 * 1024 }, ], }) # Calculate total weight of all phases. self.totalWeight = 0 for task in self.tasks: LogInfo("Task %@ with %d phases:", task["method"].__name__, len(task["phases"])) for phase in task["phases"]: LogInfo(" Phase '%@' with weight %.1f", phase["title"], phase["weight"] / 1048576.0) self.totalWeight += phase["weight"] self.delegate.buildSetTotalWeight_(self.totalWeight) # Start the first task. self.progress = 0 self.currentTask = None self.currentPhase = None self.nextTask()
def cancelUpdateDownload(self): LogInfo("User canceled profile update") self.connection.cancel() self.profileUpdateWindow.orderOut_(self)
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()
def buildSetProgressMessage_(self, message): if message != self.lastMessage: LogInfo("message: %@", message) self.lastMessage = message
def buildSetProgress_(self, progress): percent = 100.0 * progress / self.progressMax if abs(percent - self.lastProgressPercent) >= 0.1: LogInfo("progress: %.1f%%", percent) self.lastProgressPercent = percent
def examiningSource_(self, path): LogInfo("%@", "Examining source…")
def ejectingSource(self): LogInfo("%@", "Ejecting source…")