| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
| file Copyright.txt or https://cmake.org/licensing for details. */ |
| #include "cmCPackPackageMakerGenerator.h" |
| |
| #include "cmsys/FStream.hxx" |
| #include "cmsys/RegularExpression.hxx" |
| #include <assert.h> |
| #include <map> |
| #include <sstream> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string> |
| |
| #include "cmCPackComponentGroup.h" |
| #include "cmCPackLog.h" |
| #include "cmDuration.h" |
| #include "cmGeneratedFileStream.h" |
| #include "cmSystemTools.h" |
| #include "cmXMLWriter.h" |
| |
| static inline unsigned int getVersion(unsigned int major, unsigned int minor) |
| { |
| assert(major < 256 && minor < 256); |
| return ((major & 0xFF) << 16 | minor); |
| } |
| |
| cmCPackPackageMakerGenerator::cmCPackPackageMakerGenerator() |
| { |
| this->PackageMakerVersion = 0.0; |
| this->PackageCompatibilityVersion = getVersion(10, 4); |
| } |
| |
| cmCPackPackageMakerGenerator::~cmCPackPackageMakerGenerator() |
| { |
| } |
| |
| bool cmCPackPackageMakerGenerator::SupportsComponentInstallation() const |
| { |
| return this->PackageCompatibilityVersion >= getVersion(10, 4); |
| } |
| |
| int cmCPackPackageMakerGenerator::PackageFiles() |
| { |
| // TODO: Use toplevel |
| // It is used! Is this an obsolete comment? |
| |
| std::string resDir; // Where this package's resources will go. |
| std::string packageDirFileName = |
| this->GetOption("CPACK_TEMPORARY_DIRECTORY"); |
| if (this->Components.empty()) { |
| packageDirFileName += ".pkg"; |
| resDir = this->GetOption("CPACK_TOPLEVEL_DIRECTORY"); |
| resDir += "/Resources"; |
| } else { |
| packageDirFileName += ".mpkg"; |
| if (!cmsys::SystemTools::MakeDirectory(packageDirFileName.c_str())) { |
| cmCPackLogger(cmCPackLog::LOG_ERROR, |
| "unable to create package directory " << packageDirFileName |
| << std::endl); |
| return 0; |
| } |
| |
| resDir = packageDirFileName; |
| resDir += "/Contents"; |
| if (!cmsys::SystemTools::MakeDirectory(resDir.c_str())) { |
| cmCPackLogger(cmCPackLog::LOG_ERROR, |
| "unable to create package subdirectory " << resDir |
| << std::endl); |
| return 0; |
| } |
| |
| resDir += "/Resources"; |
| if (!cmsys::SystemTools::MakeDirectory(resDir.c_str())) { |
| cmCPackLogger(cmCPackLog::LOG_ERROR, |
| "unable to create package subdirectory " << resDir |
| << std::endl); |
| return 0; |
| } |
| |
| resDir += "/en.lproj"; |
| } |
| |
| const char* preflight = this->GetOption("CPACK_PREFLIGHT_SCRIPT"); |
| const char* postflight = this->GetOption("CPACK_POSTFLIGHT_SCRIPT"); |
| const char* postupgrade = this->GetOption("CPACK_POSTUPGRADE_SCRIPT"); |
| |
| if (this->Components.empty()) { |
| // Create directory structure |
| std::string preflightDirName = resDir + "/PreFlight"; |
| std::string postflightDirName = resDir + "/PostFlight"; |
| // if preflight or postflight scripts not there create directories |
| // of the same name, I think this makes it work |
| if (!preflight) { |
| if (!cmsys::SystemTools::MakeDirectory(preflightDirName.c_str())) { |
| cmCPackLogger(cmCPackLog::LOG_ERROR, |
| "Problem creating installer directory: " |
| << preflightDirName << std::endl); |
| return 0; |
| } |
| } |
| if (!postflight) { |
| if (!cmsys::SystemTools::MakeDirectory(postflightDirName.c_str())) { |
| cmCPackLogger(cmCPackLog::LOG_ERROR, |
| "Problem creating installer directory: " |
| << postflightDirName << std::endl); |
| return 0; |
| } |
| } |
| // if preflight, postflight, or postupgrade are set |
| // then copy them into the resource directory and make |
| // them executable |
| if (preflight) { |
| this->CopyInstallScript(resDir, preflight, "preflight"); |
| } |
| if (postflight) { |
| this->CopyInstallScript(resDir, postflight, "postflight"); |
| } |
| if (postupgrade) { |
| this->CopyInstallScript(resDir, postupgrade, "postupgrade"); |
| } |
| } else if (postflight) { |
| // create a postflight component to house the script |
| this->PostFlightComponent.Name = "PostFlight"; |
| this->PostFlightComponent.DisplayName = "PostFlight"; |
| this->PostFlightComponent.Description = "PostFlight"; |
| this->PostFlightComponent.IsHidden = true; |
| |
| // empty directory for pkg contents |
| std::string packageDir = toplevel + "/" + PostFlightComponent.Name; |
| if (!cmsys::SystemTools::MakeDirectory(packageDir.c_str())) { |
| cmCPackLogger(cmCPackLog::LOG_ERROR, |
| "Problem creating component packages directory: " |
| << packageDir << std::endl); |
| return 0; |
| } |
| |
| // create package |
| std::string packageFileDir = packageDirFileName + "/Contents/Packages/"; |
| if (!cmsys::SystemTools::MakeDirectory(packageFileDir.c_str())) { |
| cmCPackLogger( |
| cmCPackLog::LOG_ERROR, |
| "Problem creating component PostFlight Packages directory: " |
| << packageFileDir << std::endl); |
| return 0; |
| } |
| std::string packageFile = |
| packageFileDir + this->GetPackageName(PostFlightComponent); |
| if (!this->GenerateComponentPackage( |
| packageFile.c_str(), packageDir.c_str(), PostFlightComponent)) { |
| return 0; |
| } |
| |
| // copy postflight script into resource directory of .pkg |
| std::string resourceDir = packageFile + "/Contents/Resources"; |
| this->CopyInstallScript(resourceDir, postflight, "postflight"); |
| } |
| |
| if (!this->Components.empty()) { |
| // Create the directory where component packages will be built. |
| std::string basePackageDir = packageDirFileName; |
| basePackageDir += "/Contents/Packages"; |
| if (!cmsys::SystemTools::MakeDirectory(basePackageDir.c_str())) { |
| cmCPackLogger(cmCPackLog::LOG_ERROR, |
| "Problem creating component packages directory: " |
| << basePackageDir << std::endl); |
| return 0; |
| } |
| |
| // Create the directory where downloaded component packages will |
| // be placed. |
| const char* userUploadDirectory = |
| this->GetOption("CPACK_UPLOAD_DIRECTORY"); |
| std::string uploadDirectory; |
| if (userUploadDirectory && *userUploadDirectory) { |
| uploadDirectory = userUploadDirectory; |
| } else { |
| uploadDirectory = this->GetOption("CPACK_PACKAGE_DIRECTORY"); |
| uploadDirectory += "/CPackUploads"; |
| } |
| |
| // Create packages for each component |
| bool warnedAboutDownloadCompatibility = false; |
| |
| std::map<std::string, cmCPackComponent>::iterator compIt; |
| for (compIt = this->Components.begin(); compIt != this->Components.end(); |
| ++compIt) { |
| std::string packageFile; |
| if (compIt->second.IsDownloaded) { |
| if (this->PackageCompatibilityVersion >= getVersion(10, 5) && |
| this->PackageMakerVersion >= 3.0) { |
| // Build this package within the upload directory. |
| packageFile = uploadDirectory; |
| |
| if (!cmSystemTools::FileExists(uploadDirectory.c_str())) { |
| if (!cmSystemTools::MakeDirectory(uploadDirectory.c_str())) { |
| cmCPackLogger(cmCPackLog::LOG_ERROR, |
| "Unable to create package upload directory " |
| << uploadDirectory << std::endl); |
| return 0; |
| } |
| } |
| } else if (!warnedAboutDownloadCompatibility) { |
| if (this->PackageCompatibilityVersion < getVersion(10, 5)) { |
| cmCPackLogger( |
| cmCPackLog::LOG_WARNING, |
| "CPack warning: please set CPACK_OSX_PACKAGE_VERSION to 10.5 " |
| "or greater enable downloaded packages. CPack will build a " |
| "non-downloaded package." |
| << std::endl); |
| } |
| |
| if (this->PackageMakerVersion < 3) { |
| cmCPackLogger(cmCPackLog::LOG_WARNING, |
| "CPack warning: unable to build downloaded " |
| "packages with PackageMaker versions prior " |
| "to 3.0. CPack will build a non-downloaded package." |
| << std::endl); |
| } |
| |
| warnedAboutDownloadCompatibility = true; |
| } |
| } |
| |
| if (packageFile.empty()) { |
| // Build this package within the overall distribution |
| // metapackage. |
| packageFile = basePackageDir; |
| |
| // We're not downloading this component, even if the user |
| // requested it. |
| compIt->second.IsDownloaded = false; |
| } |
| |
| packageFile += '/'; |
| packageFile += GetPackageName(compIt->second); |
| |
| std::string packageDir = toplevel; |
| packageDir += '/'; |
| packageDir += compIt->first; |
| if (!this->GenerateComponentPackage( |
| packageFile.c_str(), packageDir.c_str(), compIt->second)) { |
| return 0; |
| } |
| } |
| } |
| this->SetOption("CPACK_MODULE_VERSION_SUFFIX", ""); |
| |
| // Copy or create all of the resource files we need. |
| if (!this->CopyCreateResourceFile("License", resDir) || |
| !this->CopyCreateResourceFile("ReadMe", resDir) || |
| !this->CopyCreateResourceFile("Welcome", resDir) || |
| !this->CopyResourcePlistFile("Info.plist") || |
| !this->CopyResourcePlistFile("Description.plist")) { |
| cmCPackLogger(cmCPackLog::LOG_ERROR, |
| "Problem copying the resource files" << std::endl); |
| return 0; |
| } |
| |
| if (this->Components.empty()) { |
| // Use PackageMaker to build the package. |
| std::ostringstream pkgCmd; |
| pkgCmd << "\"" << this->GetOption("CPACK_INSTALLER_PROGRAM") |
| << "\" -build -p \"" << packageDirFileName << "\""; |
| if (this->Components.empty()) { |
| pkgCmd << " -f \"" << this->GetOption("CPACK_TEMPORARY_DIRECTORY"); |
| } else { |
| pkgCmd << " -mi \"" << this->GetOption("CPACK_TEMPORARY_DIRECTORY") |
| << "/packages/"; |
| } |
| pkgCmd << "\" -r \"" << this->GetOption("CPACK_TOPLEVEL_DIRECTORY") |
| << "/Resources\" -i \"" |
| << this->GetOption("CPACK_TOPLEVEL_DIRECTORY") |
| << "/Info.plist\" -d \"" |
| << this->GetOption("CPACK_TOPLEVEL_DIRECTORY") |
| << "/Description.plist\""; |
| if (this->PackageMakerVersion > 2.0) { |
| pkgCmd << " -v"; |
| } |
| if (!RunPackageMaker(pkgCmd.str().c_str(), packageDirFileName.c_str())) { |
| return 0; |
| } |
| } else { |
| // We have built the package in place. Generate the |
| // distribution.dist file to describe it for the installer. |
| WriteDistributionFile(packageDirFileName.c_str()); |
| } |
| |
| std::string tmpFile = this->GetOption("CPACK_TOPLEVEL_DIRECTORY"); |
| tmpFile += "/hdiutilOutput.log"; |
| std::ostringstream dmgCmd; |
| dmgCmd << "\"" << this->GetOption("CPACK_INSTALLER_PROGRAM_DISK_IMAGE") |
| << "\" create -ov -fs HFS+ -format UDZO -srcfolder \"" |
| << packageDirFileName << "\" \"" << packageFileNames[0] << "\""; |
| std::string output; |
| int retVal = 1; |
| int numTries = 10; |
| bool res = false; |
| while (numTries > 0) { |
| res = cmSystemTools::RunSingleCommand( |
| dmgCmd.str().c_str(), &output, &output, &retVal, nullptr, |
| this->GeneratorVerbose, cmDuration::zero()); |
| if (res && !retVal) { |
| numTries = -1; |
| break; |
| } |
| cmSystemTools::Delay(500); |
| numTries--; |
| } |
| if (!res || retVal) { |
| cmGeneratedFileStream ofs(tmpFile.c_str()); |
| ofs << "# Run command: " << dmgCmd.str() << std::endl |
| << "# Output:" << std::endl |
| << output << std::endl; |
| cmCPackLogger(cmCPackLog::LOG_ERROR, |
| "Problem running hdiutil command: " |
| << dmgCmd.str() << std::endl |
| << "Please check " << tmpFile << " for errors" |
| << std::endl); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| int cmCPackPackageMakerGenerator::InitializeInternal() |
| { |
| this->SetOptionIfNotSet("CPACK_PACKAGING_INSTALL_PREFIX", "/usr"); |
| |
| // Starting with Xcode 4.3, PackageMaker is a separate app, and you |
| // can put it anywhere you want. So... use a variable for its location. |
| // People who put it in unexpected places can use the variable to tell |
| // us where it is. |
| // |
| // Use the following locations, in "most recent installation" order, |
| // to search for the PackageMaker app. Assume people who copy it into |
| // the new Xcode 4.3 app in "/Applications" will copy it into the nested |
| // Applications folder inside the Xcode bundle itself. Or directly in |
| // the "/Applications" directory. |
| // |
| // If found, save result in the CPACK_INSTALLER_PROGRAM variable. |
| |
| std::vector<std::string> paths; |
| paths.push_back("/Applications/Xcode.app/Contents/Applications" |
| "/PackageMaker.app/Contents/MacOS"); |
| paths.push_back("/Applications/Utilities" |
| "/PackageMaker.app/Contents/MacOS"); |
| paths.push_back("/Applications" |
| "/PackageMaker.app/Contents/MacOS"); |
| paths.push_back("/Developer/Applications/Utilities" |
| "/PackageMaker.app/Contents/MacOS"); |
| paths.push_back("/Developer/Applications" |
| "/PackageMaker.app/Contents/MacOS"); |
| |
| std::string pkgPath; |
| const char* inst_program = this->GetOption("CPACK_INSTALLER_PROGRAM"); |
| if (inst_program && *inst_program) { |
| pkgPath = inst_program; |
| } else { |
| pkgPath = cmSystemTools::FindProgram("PackageMaker", paths, false); |
| if (pkgPath.empty()) { |
| cmCPackLogger(cmCPackLog::LOG_ERROR, |
| "Cannot find PackageMaker compiler" << std::endl); |
| return 0; |
| } |
| this->SetOptionIfNotSet("CPACK_INSTALLER_PROGRAM", pkgPath.c_str()); |
| } |
| |
| // Get path to the real PackageMaker, not a symlink: |
| pkgPath = cmSystemTools::GetRealPath(pkgPath); |
| // Up from there to find the version.plist file in the "Contents" dir: |
| std::string contents_dir; |
| contents_dir = cmSystemTools::GetFilenamePath(pkgPath); |
| contents_dir = cmSystemTools::GetFilenamePath(contents_dir); |
| |
| std::string versionFile = contents_dir + "/version.plist"; |
| |
| if (!cmSystemTools::FileExists(versionFile.c_str())) { |
| cmCPackLogger(cmCPackLog::LOG_ERROR, |
| "Cannot find PackageMaker compiler version file: " |
| << versionFile << std::endl); |
| return 0; |
| } |
| |
| cmsys::ifstream ifs(versionFile.c_str()); |
| if (!ifs) { |
| cmCPackLogger(cmCPackLog::LOG_ERROR, |
| "Cannot open PackageMaker compiler version file" |
| << std::endl); |
| return 0; |
| } |
| |
| // Check the PackageMaker version |
| cmsys::RegularExpression rexKey("<key>CFBundleShortVersionString</key>"); |
| cmsys::RegularExpression rexVersion("<string>([0-9]+.[0-9.]+)</string>"); |
| std::string line; |
| bool foundKey = false; |
| while (cmSystemTools::GetLineFromStream(ifs, line)) { |
| if (rexKey.find(line)) { |
| foundKey = true; |
| break; |
| } |
| } |
| if (!foundKey) { |
| cmCPackLogger( |
| cmCPackLog::LOG_ERROR, |
| "Cannot find CFBundleShortVersionString in the PackageMaker compiler " |
| "version file" |
| << std::endl); |
| return 0; |
| } |
| if (!cmSystemTools::GetLineFromStream(ifs, line) || !rexVersion.find(line)) { |
| cmCPackLogger(cmCPackLog::LOG_ERROR, |
| "Problem reading the PackageMaker compiler version file: " |
| << versionFile << std::endl); |
| return 0; |
| } |
| this->PackageMakerVersion = atof(rexVersion.match(1).c_str()); |
| if (this->PackageMakerVersion < 1.0) { |
| cmCPackLogger(cmCPackLog::LOG_ERROR, |
| "Require PackageMaker 1.0 or higher" << std::endl); |
| return 0; |
| } |
| cmCPackLogger(cmCPackLog::LOG_DEBUG, |
| "PackageMaker version is: " << this->PackageMakerVersion |
| << std::endl); |
| |
| // Determine the package compatibility version. If it wasn't |
| // specified by the user, we define it based on which features the |
| // user requested. |
| const char* packageCompat = this->GetOption("CPACK_OSX_PACKAGE_VERSION"); |
| if (packageCompat && *packageCompat) { |
| unsigned int majorVersion = 10; |
| unsigned int minorVersion = 5; |
| int res = sscanf(packageCompat, "%u.%u", &majorVersion, &minorVersion); |
| if (res == 2) { |
| this->PackageCompatibilityVersion = |
| getVersion(majorVersion, minorVersion); |
| } |
| } else if (this->GetOption("CPACK_DOWNLOAD_SITE")) { |
| this->SetOption("CPACK_OSX_PACKAGE_VERSION", "10.5"); |
| this->PackageCompatibilityVersion = getVersion(10, 5); |
| } else if (this->GetOption("CPACK_COMPONENTS_ALL")) { |
| this->SetOption("CPACK_OSX_PACKAGE_VERSION", "10.4"); |
| this->PackageCompatibilityVersion = getVersion(10, 4); |
| } else { |
| this->SetOption("CPACK_OSX_PACKAGE_VERSION", "10.3"); |
| this->PackageCompatibilityVersion = getVersion(10, 3); |
| } |
| |
| std::vector<std::string> no_paths; |
| pkgPath = cmSystemTools::FindProgram("hdiutil", no_paths, false); |
| if (pkgPath.empty()) { |
| cmCPackLogger(cmCPackLog::LOG_ERROR, |
| "Cannot find hdiutil compiler" << std::endl); |
| return 0; |
| } |
| this->SetOptionIfNotSet("CPACK_INSTALLER_PROGRAM_DISK_IMAGE", |
| pkgPath.c_str()); |
| |
| return this->Superclass::InitializeInternal(); |
| } |
| |
| bool cmCPackPackageMakerGenerator::RunPackageMaker(const char* command, |
| const char* packageFile) |
| { |
| std::string tmpFile = this->GetOption("CPACK_TOPLEVEL_DIRECTORY"); |
| tmpFile += "/PackageMakerOutput.log"; |
| |
| cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Execute: " << command << std::endl); |
| std::string output; |
| int retVal = 1; |
| bool res = cmSystemTools::RunSingleCommand( |
| command, &output, &output, &retVal, nullptr, this->GeneratorVerbose, |
| cmDuration::zero()); |
| cmCPackLogger(cmCPackLog::LOG_VERBOSE, |
| "Done running package maker" << std::endl); |
| if (!res || retVal) { |
| cmGeneratedFileStream ofs(tmpFile.c_str()); |
| ofs << "# Run command: " << command << std::endl |
| << "# Output:" << std::endl |
| << output << std::endl; |
| cmCPackLogger(cmCPackLog::LOG_ERROR, |
| "Problem running PackageMaker command: " |
| << command << std::endl |
| << "Please check " << tmpFile << " for errors" |
| << std::endl); |
| return false; |
| } |
| // sometimes the command finishes but the directory is not yet |
| // created, so try 10 times to see if it shows up |
| int tries = 10; |
| while (tries > 0 && !cmSystemTools::FileExists(packageFile)) { |
| cmSystemTools::Delay(500); |
| tries--; |
| } |
| if (!cmSystemTools::FileExists(packageFile)) { |
| cmCPackLogger(cmCPackLog::LOG_ERROR, |
| "Problem running PackageMaker command: " |
| << command << std::endl |
| << "Package not created: " << packageFile << std::endl); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool cmCPackPackageMakerGenerator::GenerateComponentPackage( |
| const char* packageFile, const char* packageDir, |
| const cmCPackComponent& component) |
| { |
| cmCPackLogger(cmCPackLog::LOG_OUTPUT, |
| "- Building component package: " << packageFile |
| << std::endl); |
| |
| // The command that will be used to run PackageMaker |
| std::ostringstream pkgCmd; |
| |
| if (this->PackageCompatibilityVersion < getVersion(10, 5) || |
| this->PackageMakerVersion < 3.0) { |
| // Create Description.plist and Info.plist files for normal Mac OS |
| // X packages, which work on Mac OS X 10.3 and newer. |
| std::string descriptionFile = this->GetOption("CPACK_TOPLEVEL_DIRECTORY"); |
| descriptionFile += '/' + component.Name + "-Description.plist"; |
| cmsys::ofstream out(descriptionFile.c_str()); |
| cmXMLWriter xout(out); |
| xout.StartDocument(); |
| xout.Doctype("plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\"" |
| "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\""); |
| xout.StartElement("plist"); |
| xout.Attribute("version", "1.4"); |
| xout.StartElement("dict"); |
| xout.Element("key", "IFPkgDescriptionTitle"); |
| xout.Element("string", component.DisplayName); |
| xout.Element("key", "IFPkgDescriptionVersion"); |
| xout.Element("string", this->GetOption("CPACK_PACKAGE_VERSION")); |
| xout.Element("key", "IFPkgDescriptionDescription"); |
| xout.Element("string", component.Description); |
| xout.EndElement(); // dict |
| xout.EndElement(); // plist |
| xout.EndDocument(); |
| out.close(); |
| |
| // Create the Info.plist file for this component |
| std::string moduleVersionSuffix = "."; |
| moduleVersionSuffix += component.Name; |
| this->SetOption("CPACK_MODULE_VERSION_SUFFIX", |
| moduleVersionSuffix.c_str()); |
| std::string infoFileName = component.Name; |
| infoFileName += "-Info.plist"; |
| if (!this->CopyResourcePlistFile("Info.plist", infoFileName.c_str())) { |
| return false; |
| } |
| |
| pkgCmd << "\"" << this->GetOption("CPACK_INSTALLER_PROGRAM") |
| << "\" -build -p \"" << packageFile << "\"" |
| << " -f \"" << packageDir << "\"" |
| << " -i \"" << this->GetOption("CPACK_TOPLEVEL_DIRECTORY") << "/" |
| << infoFileName << "\"" |
| << " -d \"" << descriptionFile << "\""; |
| } else { |
| // Create a "flat" package on Mac OS X 10.5 and newer. Flat |
| // packages are stored in a single file, rather than a directory |
| // like normal packages, and can be downloaded by the installer |
| // on-the-fly in Mac OS X 10.5 or newer. Thus, we need to create |
| // flat packages when the packages will be downloaded on the fly. |
| std::string pkgId = "com."; |
| pkgId += this->GetOption("CPACK_PACKAGE_VENDOR"); |
| pkgId += '.'; |
| pkgId += this->GetOption("CPACK_PACKAGE_NAME"); |
| pkgId += '.'; |
| pkgId += component.Name; |
| |
| pkgCmd << "\"" << this->GetOption("CPACK_INSTALLER_PROGRAM") |
| << "\" --root \"" << packageDir << "\"" |
| << " --id " << pkgId << " --target " |
| << this->GetOption("CPACK_OSX_PACKAGE_VERSION") << " --out \"" |
| << packageFile << "\""; |
| } |
| |
| // Run PackageMaker |
| return RunPackageMaker(pkgCmd.str().c_str(), packageFile); |
| } |