/*============================================================================ CMake - Cross Platform Makefile Generator Copyright 2000-2009 Kitware, Inc., Insight Software Consortium Distributed under the OSI-approved BSD License (the "License"); see accompanying file Copyright.txt for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the License for more information. ============================================================================*/ #include "cmCPackIFWGenerator.h" #include "cmGlobalGenerator.h" #include "cmLocalGenerator.h" #include "cmSystemTools.h" #include "cmMakefile.h" #include "cmGeneratedFileStream.h" #include "cmCPackLog.h" #include "cmCPackComponentGroup.h" #include "cmTimestamp.h" #include #include #include #include #include //---------------------------------------------------------------------- cmCPackIFWGenerator::cmCPackIFWGenerator() { } //---------------------------------------------------------------------- cmCPackIFWGenerator::~cmCPackIFWGenerator() { } //---------------------------------------------------------------------- int cmCPackIFWGenerator::PackageFiles() { cmCPackLogger(cmCPackLog::LOG_OUTPUT, "- Configuration" << std::endl); if (!IfwCreateConfigFile()) { cmCPackLogger(cmCPackLog::LOG_ERROR, "CPack error: Could not create IFW \"config.xml\" file." << std::endl); return false; } if (Components.empty() && !IfwCreatePackageFile()) { cmCPackLogger(cmCPackLog::LOG_ERROR, "CPack error: Could not create IFW " "\"root/meta/package.xml\" file." << std::endl); return false; } std::string ifwTLD = this->GetOption("CPACK_TOPLEVEL_DIRECTORY"); std::string ifwTmpFile = ifwTLD; ifwTmpFile += "/IFWOutput.log"; std::set ifwDependsComponents; std::string ifwBinaryComponents; std::string ifwDownloadedComponents; // Create groups meta information std::map::iterator groupIt; for(groupIt = this->ComponentGroups.begin(); groupIt != this->ComponentGroups.end(); ++groupIt ) { std::string macroPrefix = "CPACK_IFW_COMPONENT_GROUP_" + cmsys::SystemTools::UpperCase(groupIt->second.Name); std::string groupId = IfwGetGroupId(&groupIt->second); if(!ifwBinaryComponents.empty()) ifwBinaryComponents += ","; ifwBinaryComponents += groupId; std::string pkgMetaDir = this->toplevel + "/packages/" + groupId + "/meta"; std::string pkgXmlFileName = pkgMetaDir + "/package.xml"; cmGeneratedFileStream pkgXml(pkgXmlFileName.data()); pkgXml << "" << std::endl; pkgXml << "" << std::endl; pkgXml << " " << groupIt->second.DisplayName << "" << std::endl; pkgXml << " " << groupIt->second.Description << "" << std::endl; pkgXml << " " << groupId << "" << std::endl; // Version const char* ifwPackageVersion = this->GetOption("CPACK_PACKAGE_VERSION"); const char* ifwGroupVersion = this->GetOption(macroPrefix + "_VERSION"); pkgXml << " " << (ifwGroupVersion ? ifwGroupVersion : ifwPackageVersion) << "" << std::endl; pkgXml << " " << IfwCreateCurrentDate() << "" << std::endl; // Licenses std::vector licenses; if(IfwParseLicenses(licenses, macroPrefix + "_LICENSES", pkgMetaDir)) { pkgXml << " " << std::endl; for(size_t i = 0; i < licenses.size(); i += 2) { pkgXml << " " <" << std::endl; } // Priority if(const char* ifwGroupPriority = this->GetOption(macroPrefix + "_PRIORITY")) { pkgXml << " " << ifwGroupPriority << "" << std::endl; } pkgXml << "" << std::endl; } // Create components meta information std::map::iterator compIt; for (compIt = this->Components.begin(); compIt != this->Components.end(); ++compIt) { // Component id std::string ifwCompId = IfwGetComponentId(&compIt->second); std::string pkgMetaDir = this->toplevel + "/" + GetComponentInstallDirNamePrefix(compIt->second.Name) + ifwCompId + "/meta"; std::string pkgXmlFileName = pkgMetaDir + "/package.xml"; cmGeneratedFileStream pkgXml(pkgXmlFileName.data()); // Check IFW version for component std::string macroPrefix = "CPACK_IFW_COMPONENT_" + cmsys::SystemTools::UpperCase(compIt->second.Name); pkgXml << "" << std::endl; pkgXml << "" << std::endl; pkgXml << " " << compIt->second.DisplayName << "" << std::endl; pkgXml << " " << compIt->second.Description << "" << std::endl; pkgXml << " " << ifwCompId << "" << std::endl; // Version const char* ifwPackageVersion = this->GetOption("CPACK_PACKAGE_VERSION"); const char* ifwCompVersion = this->GetOption(macroPrefix + "_VERSION"); pkgXml << " " << (ifwCompVersion ? ifwCompVersion : ifwPackageVersion) << "" << std::endl; pkgXml << " " << IfwCreateCurrentDate() << "" << std::endl; // Script const char* ifwCompScript = this->GetOption(macroPrefix + "_SCRIPT"); if (ifwCompScript) { // Copy file std::string ifwCompScriptFile = pkgMetaDir + "/operations.qs"; cmsys::SystemTools::CopyFileIfDifferent(ifwCompScript, ifwCompScriptFile.data()); pkgXml << " " << std::endl; } // Check dependencies std::set compDepSet; // CMake dependencies if (!compIt->second.Dependencies.empty()) { std::vector::iterator depCompIt; for(depCompIt = compIt->second.Dependencies.begin(); depCompIt != compIt->second.Dependencies.end(); ++depCompIt) { compDepSet.insert(IfwGetComponentId(*depCompIt)); } } // QtIFW dependencies if(const char *ifwCompDepsStr = this->GetOption(macroPrefix + "_DEPENDS")) { std::vector ifwCompDepsVector; cmSystemTools::ExpandListArgument(ifwCompDepsStr, ifwCompDepsVector); for(std::vector::iterator depCompIt = ifwCompDepsVector.begin(); depCompIt != ifwCompDepsVector.end(); ++depCompIt) { compDepSet.insert(*depCompIt); ifwDependsComponents.insert(*depCompIt); } } // Write dependencies if (!compDepSet.empty()) { pkgXml << " "; std::set::iterator it = compDepSet.begin(); pkgXml << *it; ++it; while(it != compDepSet.end()) { pkgXml << "," << *it; ++it; } pkgXml << "" << std::endl; } // Licenses std::vector licenses; if(IfwParseLicenses(licenses, macroPrefix + "_LICENSES", pkgMetaDir)) { pkgXml << " " << std::endl; for(size_t i = 0; i < licenses.size(); i += 2) { pkgXml << " " <" << std::endl; } // TODO: Check how enable virtual component (now it's allways disabled) if (compIt->second.IsRequired) { pkgXml << " true" << std::endl; } else if (compIt->second.IsDisabledByDefault) { pkgXml << " false" << std::endl; } else if (compIt->second.IsHidden) { pkgXml << " true" << std::endl; } else { pkgXml << " true" << std::endl; } // Priority if(const char* ifwCompPriority = this->GetOption(macroPrefix + "_PRIORITY")) { pkgXml << " " << ifwCompPriority << "" << std::endl; } pkgXml << "" << std::endl; // Downloaded if (compIt->second.IsDownloaded) { if (!ifwDownloadedComponents.empty()) ifwDownloadedComponents += ","; ifwDownloadedComponents += ifwCompId; } else { if (!ifwBinaryComponents.empty()) ifwBinaryComponents += ","; ifwBinaryComponents += ifwCompId; } } // Run repogen if (!ifwDownloadSite.empty()) { std::string ifwCmd = ifwRepoGen; ifwCmd += " -c " + this->toplevel + "/config/config.xml"; ifwCmd += " -p " + this->toplevel + "/packages"; if(!ifwPkgsDirsVector.empty()) { for(std::vector::iterator it = ifwPkgsDirsVector.begin(); it != ifwPkgsDirsVector.end(); ++it) { ifwCmd += " -p " + *it; } } if (!ifwOnlineOnly && !ifwDownloadedComponents.empty()) { ifwCmd += " -i " + ifwDownloadedComponents; } ifwCmd += " " + this->toplevel + "/repository"; cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Execute: " << ifwCmd << std::endl); std::string output; int retVal = 1; cmCPackLogger(cmCPackLog::LOG_OUTPUT, "- Generate repository" << std::endl); bool res = cmSystemTools::RunSingleCommand( ifwCmd.c_str(), &output, &retVal, 0, this->GeneratorVerbose, 0); if ( !res || retVal ) { cmGeneratedFileStream ofs(ifwTmpFile.c_str()); ofs << "# Run command: " << ifwCmd << std::endl << "# Output:" << std::endl << output << std::endl; cmCPackLogger(cmCPackLog::LOG_ERROR, "Problem running IFW command: " << ifwCmd << std::endl << "Please check " << ifwTmpFile << " for errors" << std::endl); return 0; } } // Run binary creator { std::string ifwCmd = ifwBinCreator; ifwCmd += " -c " + this->toplevel + "/config/config.xml"; ifwCmd += " -p " + this->toplevel + "/packages"; if(!ifwPkgsDirsVector.empty()) { for(std::vector::iterator it = ifwPkgsDirsVector.begin(); it != ifwPkgsDirsVector.end(); ++it) { ifwCmd += " -p " + *it; } } if (ifwOnlineOnly) { ifwCmd += " --online-only"; } else if (!ifwDownloadedComponents.empty() && !ifwDownloadSite.empty()) { ifwCmd += " -e " + ifwDownloadedComponents; } else if (!ifwDependsComponents.empty()) { ifwCmd += " -i "; std::set::iterator it = ifwDependsComponents.begin(); ifwCmd += *it; ++it; while(it != ifwDependsComponents.end()) { ifwCmd += "," + (*it); ++it; } ifwCmd += "," + ifwBinaryComponents; } // TODO: set correct name for multipackages if (this->packageFileNames.size() > 0) { ifwCmd += " " + packageFileNames[0]; } else { ifwCmd += " installer"; } cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Execute: " << ifwCmd << std::endl); std::string output; int retVal = 1; cmCPackLogger(cmCPackLog::LOG_OUTPUT, "- Generate package" << std::endl); bool res = cmSystemTools::RunSingleCommand( ifwCmd.c_str(), &output, &retVal, 0, this->GeneratorVerbose, 0); if ( !res || retVal ) { cmGeneratedFileStream ofs(ifwTmpFile.c_str()); ofs << "# Run command: " << ifwCmd << std::endl << "# Output:" << std::endl << output << std::endl; cmCPackLogger(cmCPackLog::LOG_ERROR, "Problem running IFW command: " << ifwCmd << std::endl << "Please check " << ifwTmpFile << " for errors" << std::endl); return 0; } } return 1; } //---------------------------------------------------------------------- const char *cmCPackIFWGenerator::GetPackagingInstallPrefix() { const char *defPrefix = cmCPackGenerator::GetPackagingInstallPrefix(); std::string tmpPref = defPrefix ? defPrefix : ""; if(this->Components.empty()) { tmpPref += "packages/root/data"; } this->SetOption("CPACK_IFW_PACKAGING_INSTALL_PREFIX", tmpPref.c_str()); return this->GetOption("CPACK_IFW_PACKAGING_INSTALL_PREFIX"); } //---------------------------------------------------------------------- const char *cmCPackIFWGenerator::GetOutputExtension() { const char *suffix = this->GetOption("CMAKE_EXECUTABLE_SUFFIX"); return suffix ? suffix : ""; } //---------------------------------------------------------------------- std::string cmCPackIFWGenerator::IfwGetGroupId(cmCPackComponentGroup *group) { std::string ifwGroupId; std::string ifwGroupName; std::list groups; while(group) { groups.push_front(group); group = group->ParentGroup; } std::list::iterator it = groups.begin(); if(it != groups.end()) { ifwGroupId = IfwGetGroupName(*it); ++it; } while(it != groups.end()) { ifwGroupName = IfwGetGroupName(*it); if(ifwResolveDuplicateNames) { if(ifwGroupName.substr(0, ifwGroupId.size()) == ifwGroupId) { ifwGroupId = ifwGroupName; ++it; continue; } } ifwGroupId += "." + ifwGroupName; ++it; } return ifwGroupId; } //---------------------------------------------------------------------- std::string cmCPackIFWGenerator::IfwGetComponentId(cmCPackComponent *component) { std::string ifwCompId; if(component) { ifwCompId = IfwGetGroupId(component->Group); if(!ifwCompId.empty()) ifwCompId += "."; std::string ifwCompName = IfwGetComponentName(component); if(ifwResolveDuplicateNames && (ifwCompName.substr(0, ifwCompId.size()) == ifwCompId)) { ifwCompId = ifwCompName; } else { ifwCompId += ifwCompName; } } return ifwCompId; } //---------------------------------------------------------------------- std::string cmCPackIFWGenerator::IfwGetGroupName(cmCPackComponentGroup *group) { std::string ifwGroupName = group->Name; if(const char* name = this->GetOption("CPACK_IFW_COMPONENT_GROUP_" + cmsys::SystemTools::UpperCase(group->Name) + "_NAME")) { ifwGroupName = name; } return ifwGroupName; } //---------------------------------------------------------------------- std::string cmCPackIFWGenerator::IfwGetComponentName(cmCPackComponent *component) { return IfwGetComponentName(component->Name); } //---------------------------------------------------------------------- std::string cmCPackIFWGenerator::IfwGetComponentName(const std::string &componentName) { std::string ifwCompName = componentName; if(const char* name = this->GetOption("CPACK_IFW_COMPONENT_" + cmsys::SystemTools::UpperCase(componentName) + "_NAME")) { ifwCompName = name; } return ifwCompName; } //---------------------------------------------------------------------- int cmCPackIFWGenerator::InitializeInternal() { // Search Qt Installer Framework tools if(!this->IsOn("CPACK_IFW_BINARYCREATOR_EXECUTABLE_FOUND") || !this->IsOn("CPACK_IFW_REPOGEN_EXECUTABLE_FOUND")) { this->ReadListFile("CPackIFW.cmake"); } // Look 'binarycreator' executable (needs) if(this->IsOn("CPACK_IFW_BINARYCREATOR_EXECUTABLE_FOUND")) { const char *ifwBinCreatorStr = this->GetOption("CPACK_IFW_BINARYCREATOR_EXECUTABLE"); ifwBinCreator = ifwBinCreatorStr ? ifwBinCreatorStr : ""; } else { ifwBinCreator = ""; } if (ifwBinCreator.empty()) { cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot find QtIFW compiler \"binarycreator\": " "likely it is not installed, or not in your PATH" << std::endl); return 0; } // Look 'repogen' executable (optional) if(this->IsOn("CPACK_IFW_REPOGEN_EXECUTABLE_FOUND")) { const char *ifwRepoGenStr = this->GetOption("CPACK_IFW_REPOGEN_EXECUTABLE"); ifwRepoGen = ifwRepoGenStr ? ifwRepoGenStr : ""; } else { ifwRepoGen = ""; } // // Variables that Change Behavior // Resolve duplicate names ifwResolveDuplicateNames = this->IsOn("CPACK_IFW_RESOLVE_DUPLICATE_NAMES"); // Additional packages dirs ifwPkgsDirsVector.clear(); if(const char* dirs = this->GetOption("CPACK_IFW_PACKAGES_DIRECTORIES")) { cmSystemTools::ExpandListArgument(dirs, ifwPkgsDirsVector); } // Remote repository if (const char *site = this->GetOption("CPACK_DOWNLOAD_SITE")) { ifwDownloadSite = site; } ifwOnlineOnly = this->IsOn("CPACK_DOWNLOAD_ALL") ? true : false; if (!ifwDownloadSite.empty() && ifwRepoGen.empty()) { cmCPackLogger(cmCPackLog::LOG_ERROR, "Cannot find QtIFW repository generator \"repogen\": " "likely it is not installed, or not in your PATH" << std::endl); return 0; } return this->Superclass::InitializeInternal(); } //---------------------------------------------------------------------- std::string cmCPackIFWGenerator::GetComponentInstallDirNamePrefix( const std::string& /*componentName*/) { return "packages/"; } //---------------------------------------------------------------------- std::string cmCPackIFWGenerator::GetComponentInstallDirNameSuffix( const std::string& componentName) { std::map::iterator compIt = this->Components.find(componentName); cmCPackComponent *comp = compIt != this->Components.end() ? &compIt->second : 0; const std::string prefix = GetComponentInstallDirNamePrefix(componentName); const std::string suffix = "/data"; if (componentPackageMethod == ONE_PACKAGE_PER_COMPONENT) { return prefix + IfwGetComponentId(comp) + suffix; } if (componentPackageMethod == ONE_PACKAGE) { return std::string(prefix + "ALL_COMPONENTS_IN_ONE" + suffix); } return prefix + IfwGetComponentId(comp) + suffix; } //---------------------------------------------------------------------- bool cmCPackIFWGenerator::GetListOfSubdirectories( const char* topdir, std::vector& dirs) { cmsys::Directory dir; dir.Load(topdir); size_t fileNum; for (fileNum = 0; fileNum < dir.GetNumberOfFiles(); ++fileNum) { if (strcmp(dir.GetFile(static_cast(fileNum)),".") && strcmp(dir.GetFile(static_cast(fileNum)),"..")) { cmsys_stl::string fullPath = topdir; fullPath += "/"; fullPath += dir.GetFile(static_cast(fileNum)); if(cmsys::SystemTools::FileIsDirectory(fullPath.c_str()) && !cmsys::SystemTools::FileIsSymlink(fullPath.c_str())) { if (!this->GetListOfSubdirectories(fullPath.c_str(), dirs)) { return false; } } } } dirs.push_back(topdir); return true; } //---------------------------------------------------------------------- enum cmCPackGenerator::CPackSetDestdirSupport cmCPackIFWGenerator::SupportsSetDestdir() const { return cmCPackGenerator::SETDESTDIR_SHOULD_NOT_BE_USED; } //---------------------------------------------------------------------- bool cmCPackIFWGenerator::SupportsAbsoluteDestination() const { return false; } //---------------------------------------------------------------------- bool cmCPackIFWGenerator::SupportsComponentInstallation() const { return true; } //---------------------------------------------------------------------- int cmCPackIFWGenerator::IfwCreateConfigFile() { cmGeneratedFileStream cfg((this->toplevel + "/config/config.xml").data()); std::string ifwPkgName; if (const char *name = this->GetOption("CPACK_PACKAGE_NAME")) { ifwPkgName = name; } else { ifwPkgName = "Your package"; } std::string pkgTitle; if (const char *title = this->GetOption("CPACK_IFW_PACKAGE_TITLE")) { pkgTitle = title; } else if (const char *description = this->GetOption("CPACK_PACKAGE_DESCRIPTION_SUMMARY")) { pkgTitle = description; } else { pkgTitle = "Your package description"; } std::string ifwPkgVersion; if (const char *version = this->GetOption("CPACK_PACKAGE_VERSION")) { ifwPkgVersion = version; } else { ifwPkgVersion = "1.0.0"; } const char *ifwPkgInstDir = this->GetOption("CPACK_PACKAGE_INSTALL_DIRECTORY"); const char *ifwTargetDir = this->GetOption("CPACK_IFW_TARGET_DIRECTORY"); const char *ifwAdminTargetDir = this->GetOption("CPACK_IFW_ADMIN_TARGET_DIRECTORY"); cfg << "" << std::endl; cfg << "" << std::endl; cfg << " " << cmXMLSafe(ifwPkgName).str() << "" << std::endl; cfg << " " << ifwPkgVersion << "" << std::endl; cfg << " " << cmXMLSafe(pkgTitle).str() << "" << std::endl; // Publisher std::string ifwPublisher; if(const char *publisher = GetOption("CPACK_IFW_PACKAGE_PUBLISHER")) { ifwPublisher = publisher; } else if(const char *vendor = GetOption("CPACK_PACKAGE_VENDOR")) { ifwPublisher = vendor; } if(!ifwPublisher.empty()) { cfg << " " << cmXMLSafe(ifwPublisher).str() << "" << std::endl; } // ProductUrl if(const char *url = GetOption("CPACK_IFW_PRODUCT_URL")) { cfg << " " << url << "" << std::endl; } // ApplicationIcon const char *pkgApplicationIcon = GetOption("CPACK_IFW_PACKAGE_ICON"); if(pkgApplicationIcon && cmSystemTools::FileExists(pkgApplicationIcon)) { std::string name = cmSystemTools::GetFilenameName(pkgApplicationIcon); std::string path = this->toplevel + "/config/" + name; name = cmSystemTools::GetFilenameWithoutExtension(name); cmsys::SystemTools::CopyFileIfDifferent(pkgApplicationIcon, path.data()); cfg << " " << name << "" << std::endl; } // WindowIcon const char *pkgWindowIcon = GetOption("CPACK_IFW_PACKAGE_WINDOW_ICON"); if(pkgWindowIcon && cmSystemTools::FileExists(pkgWindowIcon)) { std::string name = cmSystemTools::GetFilenameName(pkgWindowIcon); std::string path = this->toplevel + "/config/" + name; cmsys::SystemTools::CopyFileIfDifferent(pkgWindowIcon, path.data()); cfg << " " << name << "" << std::endl; } // Logo const char *pkgLogo = GetOption("CPACK_IFW_PACKAGE_LOGO"); if(pkgLogo && cmSystemTools::FileExists(pkgLogo)) { std::string name = cmSystemTools::GetFilenameName(pkgLogo); std::string path = this->toplevel + "/config/" + name; cmsys::SystemTools::CopyFileIfDifferent(pkgLogo, path.data()); cfg << " " << name << "" << std::endl; } // Default target directory for installation if (ifwTargetDir) { cfg << " " << ifwTargetDir << "" << std::endl; } else if (ifwPkgInstDir) { cfg << " @ApplicationsDir@/" << ifwPkgInstDir << "" << std::endl; } else { cfg << " @RootDir@/usr/local" << std::endl; } // Default target directory for installation with administrator rights if (ifwAdminTargetDir) { cfg << " " << ifwAdminTargetDir << "" << std::endl; } if (!ifwDownloadSite.empty()) { cfg << " " << std::endl; cfg << " " << std::endl; cfg << " " << ifwDownloadSite << "" << std::endl; // These properties can now be set from "cpack_configure_downloads" // 1 // user // password // Example repository cfg << " " << std::endl; cfg << " " << std::endl; } // CPack IFW default policy cfg << " " << std::endl; cfg << " true" << std::endl; cfg << " true" << std::endl; cfg << "" << std::endl; return 1; } //---------------------------------------------------------------------- // Create default package file int cmCPackIFWGenerator::IfwCreatePackageFile() { std::string ifwPkgName; if (const char *name = this->GetOption("CPACK_PACKAGE_NAME")) { ifwPkgName = name; } else { ifwPkgName = "Your package"; } std::string ifwPkgDescription; if (const char *name = this->GetOption("CPACK_PACKAGE_DESCRIPTION_SUMMARY")) { ifwPkgDescription = name; } else { ifwPkgDescription = "Your package description"; } cmGeneratedFileStream pkgXml((this->toplevel + "/packages/root/meta/package.xml").data()); pkgXml << "" << std::endl; pkgXml << "" << std::endl; pkgXml << " " << ifwPkgName << "" << std::endl; pkgXml << " " << ifwPkgDescription << "" << std::endl; pkgXml << " " << "root" << "" << std::endl; pkgXml << " " << this->GetOption("CPACK_PACKAGE_VERSION") << "" << std::endl; pkgXml << " " << IfwCreateCurrentDate() << "" << std::endl; pkgXml << " true" << std::endl; pkgXml << " true" << std::endl; pkgXml << "" << std::endl; return 1; } //---------------------------------------------------------------------- std::string cmCPackIFWGenerator::IfwCreateCurrentDate() { cmTimestamp timestamp; return timestamp.CurrentTime("%Y-%m-%d", false); } //---------------------------------------------------------------------- bool cmCPackIFWGenerator::IfwParseLicenses(std::vector &licenses, const std::string &variable, const std::string &metaDir) { if (const char *option = this->GetOption(variable)) { if(!licenses.empty()) licenses.clear(); cmSystemTools::ExpandListArgument( option, licenses ); } else { return false; } if ( licenses.size() % 2 != 0 ) { cmCPackLogger(cmCPackLog::LOG_ERROR, variable << " should contain pairs of and ." << std::endl); return false; } for(size_t i = 1; i < licenses.size(); i += 2) { std::string name = cmSystemTools::GetFilenameName(licenses[i]); std::string path = metaDir + "/" + name; cmsys::SystemTools::CopyFileIfDifferent(licenses[i].data(), path.data()); licenses[i] = name; } return licenses.size() > 1; }