cmCPackNSISGenerator.cxx 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file Copyright.txt or https://cmake.org/licensing for details. */
  3. #include "cmCPackNSISGenerator.h"
  4. #include <algorithm>
  5. #include <cstdlib>
  6. #include <cstring>
  7. #include <map>
  8. #include <sstream>
  9. #include <utility>
  10. #include <cmext/algorithm>
  11. #include "cmsys/Directory.hxx"
  12. #include "cmsys/RegularExpression.hxx"
  13. #include "cmCPackComponentGroup.h"
  14. #include "cmCPackGenerator.h"
  15. #include "cmCPackLog.h"
  16. #include "cmDuration.h"
  17. #include "cmGeneratedFileStream.h"
  18. #include "cmStringAlgorithms.h"
  19. #include "cmSystemTools.h"
  20. /* NSIS uses different command line syntax on Windows and others */
  21. #ifdef _WIN32
  22. # define NSIS_OPT "/"
  23. #else
  24. # define NSIS_OPT "-"
  25. #endif
  26. cmCPackNSISGenerator::cmCPackNSISGenerator(bool nsis64)
  27. {
  28. this->Nsis64 = nsis64;
  29. }
  30. cmCPackNSISGenerator::~cmCPackNSISGenerator() = default;
  31. int cmCPackNSISGenerator::PackageFiles()
  32. {
  33. // TODO: Fix nsis to force out file name
  34. std::string nsisInFileName = this->FindTemplate("NSIS.template.in");
  35. if (nsisInFileName.empty()) {
  36. cmCPackLogger(cmCPackLog::LOG_ERROR,
  37. "CPack error: Could not find NSIS installer template file."
  38. << std::endl);
  39. return false;
  40. }
  41. std::string nsisInInstallOptions =
  42. this->FindTemplate("NSIS.InstallOptions.ini.in");
  43. if (nsisInInstallOptions.empty()) {
  44. cmCPackLogger(cmCPackLog::LOG_ERROR,
  45. "CPack error: Could not find NSIS installer options file."
  46. << std::endl);
  47. return false;
  48. }
  49. std::string nsisFileName = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
  50. std::string tmpFile = cmStrCat(nsisFileName, "/NSISOutput.log");
  51. std::string nsisInstallOptions = nsisFileName + "/NSIS.InstallOptions.ini";
  52. nsisFileName += "/project.nsi";
  53. std::ostringstream str;
  54. for (std::string const& file : this->files) {
  55. std::string outputDir = "$INSTDIR";
  56. std::string fileN = cmSystemTools::RelativePath(this->toplevel, file);
  57. if (!this->Components.empty()) {
  58. const std::string::size_type pos = fileN.find('/');
  59. // Use the custom component install directory if we have one
  60. if (pos != std::string::npos) {
  61. auto componentName = cm::string_view(fileN).substr(0, pos);
  62. outputDir = this->CustomComponentInstallDirectory(componentName);
  63. } else {
  64. outputDir = this->CustomComponentInstallDirectory(fileN);
  65. }
  66. // Strip off the component part of the path.
  67. fileN = fileN.substr(pos + 1);
  68. }
  69. std::replace(fileN.begin(), fileN.end(), '/', '\\');
  70. str << " Delete \"" << outputDir << "\\" << fileN << "\"" << std::endl;
  71. }
  72. cmCPackLogger(cmCPackLog::LOG_DEBUG,
  73. "Uninstall Files: " << str.str() << std::endl);
  74. this->SetOptionIfNotSet("CPACK_NSIS_DELETE_FILES", str.str().c_str());
  75. std::vector<std::string> dirs;
  76. this->GetListOfSubdirectories(this->toplevel.c_str(), dirs);
  77. std::ostringstream dstr;
  78. for (std::string const& dir : dirs) {
  79. std::string componentName;
  80. std::string fileN = cmSystemTools::RelativePath(this->toplevel, dir);
  81. if (fileN.empty()) {
  82. continue;
  83. }
  84. if (!this->Components.empty()) {
  85. // If this is a component installation, strip off the component
  86. // part of the path.
  87. std::string::size_type slash = fileN.find('/');
  88. if (slash != std::string::npos) {
  89. // If this is a component installation, determine which component it
  90. // is.
  91. componentName = fileN.substr(0, slash);
  92. // Strip off the component part of the path.
  93. fileN.erase(0, slash + 1);
  94. }
  95. }
  96. std::replace(fileN.begin(), fileN.end(), '/', '\\');
  97. const std::string componentOutputDir =
  98. this->CustomComponentInstallDirectory(componentName);
  99. dstr << " RMDir \"" << componentOutputDir << "\\" << fileN << "\""
  100. << std::endl;
  101. if (!componentName.empty()) {
  102. this->Components[componentName].Directories.push_back(std::move(fileN));
  103. }
  104. }
  105. cmCPackLogger(cmCPackLog::LOG_DEBUG,
  106. "Uninstall Dirs: " << dstr.str() << std::endl);
  107. this->SetOptionIfNotSet("CPACK_NSIS_DELETE_DIRECTORIES", dstr.str().c_str());
  108. cmCPackLogger(cmCPackLog::LOG_VERBOSE,
  109. "Configure file: " << nsisInFileName << " to " << nsisFileName
  110. << std::endl);
  111. if (this->IsSet("CPACK_NSIS_MUI_ICON") ||
  112. this->IsSet("CPACK_NSIS_MUI_UNIICON")) {
  113. std::string installerIconCode;
  114. if (this->IsSet("CPACK_NSIS_MUI_ICON")) {
  115. installerIconCode += cmStrCat(
  116. "!define MUI_ICON \"", this->GetOption("CPACK_NSIS_MUI_ICON"), "\"\n");
  117. }
  118. if (this->IsSet("CPACK_NSIS_MUI_UNIICON")) {
  119. installerIconCode +=
  120. cmStrCat("!define MUI_UNICON \"",
  121. this->GetOption("CPACK_NSIS_MUI_UNIICON"), "\"\n");
  122. }
  123. this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_ICON_CODE",
  124. installerIconCode.c_str());
  125. }
  126. std::string installerHeaderImage;
  127. if (this->IsSet("CPACK_NSIS_MUI_HEADERIMAGE")) {
  128. installerHeaderImage = this->GetOption("CPACK_NSIS_MUI_HEADERIMAGE");
  129. } else if (this->IsSet("CPACK_PACKAGE_ICON")) {
  130. installerHeaderImage = this->GetOption("CPACK_PACKAGE_ICON");
  131. }
  132. if (!installerHeaderImage.empty()) {
  133. std::string installerIconCode = cmStrCat(
  134. "!define MUI_HEADERIMAGE_BITMAP \"", installerHeaderImage, "\"\n");
  135. this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_ICON_CODE",
  136. installerIconCode.c_str());
  137. }
  138. if (this->IsSet("CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP")) {
  139. std::string installerBitmapCode = cmStrCat(
  140. "!define MUI_WELCOMEFINISHPAGE_BITMAP \"",
  141. this->GetOption("CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP"), "\"\n");
  142. this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_WELCOMEFINISH_CODE",
  143. installerBitmapCode.c_str());
  144. }
  145. if (this->IsSet("CPACK_NSIS_MUI_UNWELCOMEFINISHPAGE_BITMAP")) {
  146. std::string installerBitmapCode = cmStrCat(
  147. "!define MUI_UNWELCOMEFINISHPAGE_BITMAP \"",
  148. this->GetOption("CPACK_NSIS_MUI_UNWELCOMEFINISHPAGE_BITMAP"), "\"\n");
  149. this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_UNWELCOMEFINISH_CODE",
  150. installerBitmapCode.c_str());
  151. }
  152. if (this->IsSet("CPACK_NSIS_MUI_FINISHPAGE_RUN")) {
  153. std::string installerRunCode =
  154. cmStrCat("!define MUI_FINISHPAGE_RUN \"$INSTDIR\\",
  155. this->GetOption("CPACK_NSIS_EXECUTABLES_DIRECTORY"), '\\',
  156. this->GetOption("CPACK_NSIS_MUI_FINISHPAGE_RUN"), "\"\n");
  157. this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_FINISHPAGE_RUN_CODE",
  158. installerRunCode.c_str());
  159. }
  160. if (this->IsSet("CPACK_NSIS_WELCOME_TITLE")) {
  161. std::string welcomeTitleCode =
  162. cmStrCat("!define MUI_WELCOMEPAGE_TITLE \"",
  163. this->GetOption("CPACK_NSIS_WELCOME_TITLE"), "\"");
  164. this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_WELCOME_TITLE_CODE",
  165. welcomeTitleCode.c_str());
  166. }
  167. if (this->IsSet("CPACK_NSIS_WELCOME_TITLE_3LINES")) {
  168. this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_WELCOME_TITLE_3LINES_CODE",
  169. "!define MUI_WELCOMEPAGE_TITLE_3LINES");
  170. }
  171. if (this->IsSet("CPACK_NSIS_FINISH_TITLE")) {
  172. std::string finishTitleCode =
  173. cmStrCat("!define MUI_FINISHPAGE_TITLE \"",
  174. this->GetOption("CPACK_NSIS_FINISH_TITLE"), "\"");
  175. this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_FINISH_TITLE_CODE",
  176. finishTitleCode.c_str());
  177. }
  178. if (this->IsSet("CPACK_NSIS_FINISH_TITLE_3LINES")) {
  179. this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_FINISH_TITLE_3LINES_CODE",
  180. "!define MUI_FINISHPAGE_TITLE_3LINES");
  181. }
  182. if (this->IsSet("CPACK_NSIS_MANIFEST_DPI_AWARE")) {
  183. this->SetOptionIfNotSet("CPACK_NSIS_MANIFEST_DPI_AWARE_CODE",
  184. "ManifestDPIAware true");
  185. }
  186. // Setup all of the component sections
  187. if (this->Components.empty()) {
  188. this->SetOptionIfNotSet("CPACK_NSIS_INSTALLATION_TYPES", "");
  189. this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_COMPONENTS_DESC", "");
  190. this->SetOptionIfNotSet("CPACK_NSIS_PAGE_COMPONENTS", "");
  191. this->SetOptionIfNotSet("CPACK_NSIS_FULL_INSTALL",
  192. R"(File /r "${INST_DIR}\*.*")");
  193. this->SetOptionIfNotSet("CPACK_NSIS_COMPONENT_SECTIONS", "");
  194. this->SetOptionIfNotSet("CPACK_NSIS_COMPONENT_SECTION_LIST", "");
  195. this->SetOptionIfNotSet("CPACK_NSIS_SECTION_SELECTED_VARS", "");
  196. } else {
  197. std::string componentCode;
  198. std::string sectionList;
  199. std::string selectedVarsList;
  200. std::string componentDescriptions;
  201. std::string groupDescriptions;
  202. std::string installTypesCode;
  203. std::string defines;
  204. std::ostringstream macrosOut;
  205. bool anyDownloadedComponents = false;
  206. // Create installation types. The order is significant, so we first fill
  207. // in a vector based on the indices, and print them in that order.
  208. std::vector<cmCPackInstallationType*> installTypes(
  209. this->InstallationTypes.size());
  210. for (auto& installType : this->InstallationTypes) {
  211. installTypes[installType.second.Index - 1] = &installType.second;
  212. }
  213. for (cmCPackInstallationType* installType : installTypes) {
  214. installTypesCode += "InstType \"";
  215. installTypesCode += installType->DisplayName;
  216. installTypesCode += "\"\n";
  217. }
  218. // Create installation groups first
  219. for (auto& group : this->ComponentGroups) {
  220. if (group.second.ParentGroup == nullptr) {
  221. componentCode +=
  222. this->CreateComponentGroupDescription(&group.second, macrosOut);
  223. }
  224. // Add the group description, if any.
  225. if (!group.second.Description.empty()) {
  226. groupDescriptions += " !insertmacro MUI_DESCRIPTION_TEXT ${" +
  227. group.first + "} \"" +
  228. cmCPackNSISGenerator::TranslateNewlines(group.second.Description) +
  229. "\"\n";
  230. }
  231. }
  232. // Create the remaining components, which aren't associated with groups.
  233. for (auto& comp : this->Components) {
  234. if (comp.second.Files.empty()) {
  235. // NSIS cannot cope with components that have no files.
  236. continue;
  237. }
  238. anyDownloadedComponents =
  239. anyDownloadedComponents || comp.second.IsDownloaded;
  240. if (!comp.second.Group) {
  241. componentCode +=
  242. this->CreateComponentDescription(&comp.second, macrosOut);
  243. }
  244. // Add this component to the various section lists.
  245. sectionList += R"( !insertmacro "${MacroName}" ")";
  246. sectionList += comp.first;
  247. sectionList += "\"\n";
  248. selectedVarsList += "Var " + comp.first + "_selected\n";
  249. selectedVarsList += "Var " + comp.first + "_was_installed\n";
  250. // Add the component description, if any.
  251. if (!comp.second.Description.empty()) {
  252. componentDescriptions += " !insertmacro MUI_DESCRIPTION_TEXT ${" +
  253. comp.first + "} \"" +
  254. cmCPackNSISGenerator::TranslateNewlines(comp.second.Description) +
  255. "\"\n";
  256. }
  257. }
  258. componentCode += macrosOut.str();
  259. if (componentDescriptions.empty() && groupDescriptions.empty()) {
  260. // Turn off the "Description" box
  261. this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_COMPONENTS_DESC",
  262. "!define MUI_COMPONENTSPAGE_NODESC");
  263. } else {
  264. componentDescriptions = "!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN\n" +
  265. componentDescriptions + groupDescriptions +
  266. "!insertmacro MUI_FUNCTION_DESCRIPTION_END\n";
  267. this->SetOptionIfNotSet("CPACK_NSIS_INSTALLER_MUI_COMPONENTS_DESC",
  268. componentDescriptions.c_str());
  269. }
  270. if (anyDownloadedComponents) {
  271. defines += "!define CPACK_USES_DOWNLOAD\n";
  272. if (cmIsOn(this->GetOption("CPACK_ADD_REMOVE"))) {
  273. defines += "!define CPACK_NSIS_ADD_REMOVE\n";
  274. }
  275. }
  276. this->SetOptionIfNotSet("CPACK_NSIS_INSTALLATION_TYPES",
  277. installTypesCode.c_str());
  278. this->SetOptionIfNotSet("CPACK_NSIS_PAGE_COMPONENTS",
  279. "!insertmacro MUI_PAGE_COMPONENTS");
  280. this->SetOptionIfNotSet("CPACK_NSIS_FULL_INSTALL", "");
  281. this->SetOptionIfNotSet("CPACK_NSIS_COMPONENT_SECTIONS",
  282. componentCode.c_str());
  283. this->SetOptionIfNotSet("CPACK_NSIS_COMPONENT_SECTION_LIST",
  284. sectionList.c_str());
  285. this->SetOptionIfNotSet("CPACK_NSIS_SECTION_SELECTED_VARS",
  286. selectedVarsList.c_str());
  287. this->SetOption("CPACK_NSIS_DEFINES", defines.c_str());
  288. }
  289. this->ConfigureFile(nsisInInstallOptions, nsisInstallOptions);
  290. this->ConfigureFile(nsisInFileName, nsisFileName);
  291. std::string nsisCmd =
  292. cmStrCat('"', this->GetOption("CPACK_INSTALLER_PROGRAM"), "\" \"",
  293. nsisFileName, '"');
  294. cmCPackLogger(cmCPackLog::LOG_VERBOSE, "Execute: " << nsisCmd << std::endl);
  295. std::string output;
  296. int retVal = 1;
  297. bool res = cmSystemTools::RunSingleCommand(
  298. nsisCmd, &output, &output, &retVal, nullptr, this->GeneratorVerbose,
  299. cmDuration::zero());
  300. if (!res || retVal) {
  301. cmGeneratedFileStream ofs(tmpFile);
  302. ofs << "# Run command: " << nsisCmd << std::endl
  303. << "# Output:" << std::endl
  304. << output << std::endl;
  305. cmCPackLogger(cmCPackLog::LOG_ERROR,
  306. "Problem running NSIS command: " << nsisCmd << std::endl
  307. << "Please check "
  308. << tmpFile << " for errors"
  309. << std::endl);
  310. return 0;
  311. }
  312. return 1;
  313. }
  314. int cmCPackNSISGenerator::InitializeInternal()
  315. {
  316. if (cmIsOn(this->GetOption("CPACK_INCLUDE_TOPLEVEL_DIRECTORY"))) {
  317. cmCPackLogger(
  318. cmCPackLog::LOG_WARNING,
  319. "NSIS Generator cannot work with CPACK_INCLUDE_TOPLEVEL_DIRECTORY set. "
  320. "This option will be reset to 0 (for this generator only)."
  321. << std::endl);
  322. this->SetOption("CPACK_INCLUDE_TOPLEVEL_DIRECTORY", nullptr);
  323. }
  324. cmCPackLogger(cmCPackLog::LOG_DEBUG,
  325. "cmCPackNSISGenerator::Initialize()" << std::endl);
  326. std::vector<std::string> path;
  327. std::string nsisPath;
  328. bool gotRegValue = false;
  329. #ifdef _WIN32
  330. if (Nsis64) {
  331. if (!gotRegValue &&
  332. cmsys::SystemTools::ReadRegistryValue(
  333. "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS\\Unicode", nsisPath,
  334. cmsys::SystemTools::KeyWOW64_64)) {
  335. gotRegValue = true;
  336. }
  337. if (!gotRegValue &&
  338. cmsys::SystemTools::ReadRegistryValue(
  339. "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS", nsisPath,
  340. cmsys::SystemTools::KeyWOW64_64)) {
  341. gotRegValue = true;
  342. }
  343. }
  344. if (!gotRegValue &&
  345. cmsys::SystemTools::ReadRegistryValue(
  346. "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS\\Unicode", nsisPath,
  347. cmsys::SystemTools::KeyWOW64_32)) {
  348. gotRegValue = true;
  349. }
  350. if (!gotRegValue &&
  351. cmsys::SystemTools::ReadRegistryValue(
  352. "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS\\Unicode", nsisPath)) {
  353. gotRegValue = true;
  354. }
  355. if (!gotRegValue &&
  356. cmsys::SystemTools::ReadRegistryValue(
  357. "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS", nsisPath,
  358. cmsys::SystemTools::KeyWOW64_32)) {
  359. gotRegValue = true;
  360. }
  361. if (!gotRegValue &&
  362. cmsys::SystemTools::ReadRegistryValue(
  363. "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS", nsisPath)) {
  364. gotRegValue = true;
  365. }
  366. if (gotRegValue) {
  367. path.push_back(nsisPath);
  368. }
  369. #endif
  370. nsisPath = cmSystemTools::FindProgram("makensis", path, false);
  371. if (nsisPath.empty()) {
  372. cmCPackLogger(
  373. cmCPackLog::LOG_ERROR,
  374. "Cannot find NSIS compiler makensis: likely it is not installed, "
  375. "or not in your PATH"
  376. << std::endl);
  377. if (!gotRegValue) {
  378. cmCPackLogger(
  379. cmCPackLog::LOG_ERROR,
  380. "Could not read NSIS registry value. This is usually caused by "
  381. "NSIS not being installed. Please install NSIS from "
  382. "http://nsis.sourceforge.net"
  383. << std::endl);
  384. }
  385. return 0;
  386. }
  387. std::string nsisCmd = "\"" + nsisPath + "\" " NSIS_OPT "VERSION";
  388. cmCPackLogger(cmCPackLog::LOG_VERBOSE,
  389. "Test NSIS version: " << nsisCmd << std::endl);
  390. std::string output;
  391. int retVal = 1;
  392. bool resS = cmSystemTools::RunSingleCommand(
  393. nsisCmd, &output, &output, &retVal, nullptr, this->GeneratorVerbose,
  394. cmDuration::zero());
  395. cmsys::RegularExpression versionRex("v([0-9]+.[0-9]+)");
  396. cmsys::RegularExpression versionRexCVS("v(.*)\\.cvs");
  397. if (!resS || retVal ||
  398. (!versionRex.find(output) && !versionRexCVS.find(output))) {
  399. const char* topDir = this->GetOption("CPACK_TOPLEVEL_DIRECTORY");
  400. std::string tmpFile = cmStrCat(topDir ? topDir : ".", "/NSISOutput.log");
  401. cmGeneratedFileStream ofs(tmpFile);
  402. ofs << "# Run command: " << nsisCmd << std::endl
  403. << "# Output:" << std::endl
  404. << output << std::endl;
  405. cmCPackLogger(cmCPackLog::LOG_ERROR,
  406. "Problem checking NSIS version with command: "
  407. << nsisCmd << std::endl
  408. << "Please check " << tmpFile << " for errors"
  409. << std::endl);
  410. return 0;
  411. }
  412. if (versionRex.find(output)) {
  413. double nsisVersion = atof(versionRex.match(1).c_str());
  414. double minNSISVersion = 3.0;
  415. cmCPackLogger(cmCPackLog::LOG_DEBUG,
  416. "NSIS Version: " << nsisVersion << std::endl);
  417. if (nsisVersion < minNSISVersion) {
  418. cmCPackLogger(cmCPackLog::LOG_ERROR,
  419. "CPack requires NSIS Version 3.0 or greater. "
  420. "NSIS found on the system was: "
  421. << nsisVersion << std::endl);
  422. return 0;
  423. }
  424. }
  425. if (versionRexCVS.find(output)) {
  426. // No version check for NSIS cvs build
  427. cmCPackLogger(cmCPackLog::LOG_DEBUG,
  428. "NSIS Version: CVS " << versionRexCVS.match(1) << std::endl);
  429. }
  430. this->SetOptionIfNotSet("CPACK_INSTALLER_PROGRAM", nsisPath.c_str());
  431. this->SetOptionIfNotSet("CPACK_NSIS_EXECUTABLES_DIRECTORY", "bin");
  432. const char* cpackPackageExecutables =
  433. this->GetOption("CPACK_PACKAGE_EXECUTABLES");
  434. const char* cpackPackageDeskTopLinks =
  435. this->GetOption("CPACK_CREATE_DESKTOP_LINKS");
  436. const char* cpackNsisExecutablesDirectory =
  437. this->GetOption("CPACK_NSIS_EXECUTABLES_DIRECTORY");
  438. std::vector<std::string> cpackPackageDesktopLinksVector;
  439. if (cpackPackageDeskTopLinks) {
  440. cmCPackLogger(cmCPackLog::LOG_DEBUG,
  441. "CPACK_CREATE_DESKTOP_LINKS: " << cpackPackageDeskTopLinks
  442. << std::endl);
  443. cmExpandList(cpackPackageDeskTopLinks, cpackPackageDesktopLinksVector);
  444. for (std::string const& cpdl : cpackPackageDesktopLinksVector) {
  445. cmCPackLogger(cmCPackLog::LOG_DEBUG,
  446. "CPACK_CREATE_DESKTOP_LINKS: " << cpdl << std::endl);
  447. }
  448. } else {
  449. cmCPackLogger(cmCPackLog::LOG_DEBUG,
  450. "CPACK_CREATE_DESKTOP_LINKS: "
  451. << "not set" << std::endl);
  452. }
  453. std::ostringstream str;
  454. std::ostringstream deleteStr;
  455. if (cpackPackageExecutables) {
  456. cmCPackLogger(cmCPackLog::LOG_DEBUG,
  457. "The cpackPackageExecutables: " << cpackPackageExecutables
  458. << "." << std::endl);
  459. std::vector<std::string> cpackPackageExecutablesVector =
  460. cmExpandedList(cpackPackageExecutables);
  461. if (cpackPackageExecutablesVector.size() % 2 != 0) {
  462. cmCPackLogger(
  463. cmCPackLog::LOG_ERROR,
  464. "CPACK_PACKAGE_EXECUTABLES should contain pairs of <executable> and "
  465. "<icon name>."
  466. << std::endl);
  467. return 0;
  468. }
  469. std::vector<std::string>::iterator it;
  470. for (it = cpackPackageExecutablesVector.begin();
  471. it != cpackPackageExecutablesVector.end(); ++it) {
  472. std::string execName = *it;
  473. ++it;
  474. std::string linkName = *it;
  475. str << R"( CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\)" << linkName
  476. << R"(.lnk" "$INSTDIR\)" << cpackNsisExecutablesDirectory << "\\"
  477. << execName << ".exe\"" << std::endl;
  478. deleteStr << R"( Delete "$SMPROGRAMS\$MUI_TEMP\)" << linkName
  479. << ".lnk\"" << std::endl;
  480. // see if CPACK_CREATE_DESKTOP_LINK_ExeName is on
  481. // if so add a desktop link
  482. if (cm::contains(cpackPackageDesktopLinksVector, execName)) {
  483. str << " StrCmp \"$INSTALL_DESKTOP\" \"1\" 0 +2\n";
  484. str << " CreateShortCut \"$DESKTOP\\" << linkName
  485. << R"(.lnk" "$INSTDIR\)" << cpackNsisExecutablesDirectory << "\\"
  486. << execName << ".exe\"" << std::endl;
  487. deleteStr << " StrCmp \"$INSTALL_DESKTOP\" \"1\" 0 +2\n";
  488. deleteStr << " Delete \"$DESKTOP\\" << linkName << ".lnk\""
  489. << std::endl;
  490. }
  491. }
  492. }
  493. this->CreateMenuLinks(str, deleteStr);
  494. this->SetOptionIfNotSet("CPACK_NSIS_CREATE_ICONS", str.str().c_str());
  495. this->SetOptionIfNotSet("CPACK_NSIS_DELETE_ICONS", deleteStr.str().c_str());
  496. this->SetOptionIfNotSet("CPACK_NSIS_COMPRESSOR", "lzma");
  497. return this->Superclass::InitializeInternal();
  498. }
  499. void cmCPackNSISGenerator::CreateMenuLinks(std::ostream& str,
  500. std::ostream& deleteStr)
  501. {
  502. const char* cpackMenuLinks = this->GetOption("CPACK_NSIS_MENU_LINKS");
  503. if (!cpackMenuLinks) {
  504. return;
  505. }
  506. cmCPackLogger(cmCPackLog::LOG_DEBUG,
  507. "The cpackMenuLinks: " << cpackMenuLinks << "." << std::endl);
  508. std::vector<std::string> cpackMenuLinksVector =
  509. cmExpandedList(cpackMenuLinks);
  510. if (cpackMenuLinksVector.size() % 2 != 0) {
  511. cmCPackLogger(
  512. cmCPackLog::LOG_ERROR,
  513. "CPACK_NSIS_MENU_LINKS should contain pairs of <shortcut target> and "
  514. "<shortcut label>."
  515. << std::endl);
  516. return;
  517. }
  518. static cmsys::RegularExpression urlRegex(
  519. "^(mailto:|(ftps?|https?|news)://).*$");
  520. std::vector<std::string>::iterator it;
  521. for (it = cpackMenuLinksVector.begin(); it != cpackMenuLinksVector.end();
  522. ++it) {
  523. std::string sourceName = *it;
  524. const bool url = urlRegex.find(sourceName);
  525. // Convert / to \ in filenames, but not in urls:
  526. //
  527. if (!url) {
  528. std::replace(sourceName.begin(), sourceName.end(), '/', '\\');
  529. }
  530. ++it;
  531. std::string linkName = *it;
  532. if (!url) {
  533. str << R"( CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\)" << linkName
  534. << R"(.lnk" "$INSTDIR\)" << sourceName << "\"" << std::endl;
  535. deleteStr << R"( Delete "$SMPROGRAMS\$MUI_TEMP\)" << linkName
  536. << ".lnk\"" << std::endl;
  537. } else {
  538. str << R"( WriteINIStr "$SMPROGRAMS\$STARTMENU_FOLDER\)" << linkName
  539. << R"(.url" "InternetShortcut" "URL" ")" << sourceName << "\""
  540. << std::endl;
  541. deleteStr << R"( Delete "$SMPROGRAMS\$MUI_TEMP\)" << linkName
  542. << ".url\"" << std::endl;
  543. }
  544. // see if CPACK_CREATE_DESKTOP_LINK_ExeName is on
  545. // if so add a desktop link
  546. std::string desktop = cmStrCat("CPACK_CREATE_DESKTOP_LINK_", linkName);
  547. if (this->IsSet(desktop)) {
  548. str << " StrCmp \"$INSTALL_DESKTOP\" \"1\" 0 +2\n";
  549. str << " CreateShortCut \"$DESKTOP\\" << linkName
  550. << R"(.lnk" "$INSTDIR\)" << sourceName << "\"" << std::endl;
  551. deleteStr << " StrCmp \"$INSTALL_DESKTOP\" \"1\" 0 +2\n";
  552. deleteStr << " Delete \"$DESKTOP\\" << linkName << ".lnk\""
  553. << std::endl;
  554. }
  555. }
  556. }
  557. bool cmCPackNSISGenerator::GetListOfSubdirectories(
  558. const char* topdir, std::vector<std::string>& dirs)
  559. {
  560. cmsys::Directory dir;
  561. dir.Load(topdir);
  562. for (unsigned long i = 0; i < dir.GetNumberOfFiles(); ++i) {
  563. const char* fileName = dir.GetFile(i);
  564. if (strcmp(fileName, ".") != 0 && strcmp(fileName, "..") != 0) {
  565. std::string const fullPath =
  566. std::string(topdir).append("/").append(fileName);
  567. if (cmsys::SystemTools::FileIsDirectory(fullPath) &&
  568. !cmsys::SystemTools::FileIsSymlink(fullPath)) {
  569. if (!this->GetListOfSubdirectories(fullPath.c_str(), dirs)) {
  570. return false;
  571. }
  572. }
  573. }
  574. }
  575. dirs.emplace_back(topdir);
  576. return true;
  577. }
  578. enum cmCPackGenerator::CPackSetDestdirSupport
  579. cmCPackNSISGenerator::SupportsSetDestdir() const
  580. {
  581. return cmCPackGenerator::SETDESTDIR_SHOULD_NOT_BE_USED;
  582. }
  583. bool cmCPackNSISGenerator::SupportsAbsoluteDestination() const
  584. {
  585. return false;
  586. }
  587. bool cmCPackNSISGenerator::SupportsComponentInstallation() const
  588. {
  589. return true;
  590. }
  591. std::string cmCPackNSISGenerator::CreateComponentDescription(
  592. cmCPackComponent* component, std::ostream& macrosOut)
  593. {
  594. // Basic description of the component
  595. std::string componentCode = "Section ";
  596. if (component->IsDisabledByDefault) {
  597. componentCode += "/o ";
  598. }
  599. componentCode += "\"";
  600. if (component->IsHidden) {
  601. componentCode += "-";
  602. }
  603. componentCode += component->DisplayName + "\" " + component->Name + "\n";
  604. if (component->IsRequired) {
  605. componentCode += " SectionIn RO\n";
  606. } else if (!component->InstallationTypes.empty()) {
  607. std::ostringstream out;
  608. for (cmCPackInstallationType const* installType :
  609. component->InstallationTypes) {
  610. out << " " << installType->Index;
  611. }
  612. componentCode += " SectionIn" + out.str() + "\n";
  613. }
  614. const std::string componentOutputDir =
  615. this->CustomComponentInstallDirectory(component->Name);
  616. componentCode += cmStrCat(" SetOutPath \"", componentOutputDir, "\"\n");
  617. // Create the actual installation commands
  618. if (component->IsDownloaded) {
  619. if (component->ArchiveFile.empty()) {
  620. // Compute the name of the archive.
  621. std::string packagesDir =
  622. cmStrCat(this->GetOption("CPACK_TEMPORARY_DIRECTORY"), ".dummy");
  623. std::ostringstream out;
  624. out << cmSystemTools::GetFilenameWithoutLastExtension(packagesDir) << "-"
  625. << component->Name << ".zip";
  626. component->ArchiveFile = out.str();
  627. }
  628. // Create the directory for the upload area
  629. const char* userUploadDirectory =
  630. this->GetOption("CPACK_UPLOAD_DIRECTORY");
  631. std::string uploadDirectory;
  632. if (cmNonempty(userUploadDirectory)) {
  633. uploadDirectory = userUploadDirectory;
  634. } else {
  635. uploadDirectory =
  636. cmStrCat(this->GetOption("CPACK_PACKAGE_DIRECTORY"), "/CPackUploads");
  637. }
  638. if (!cmSystemTools::FileExists(uploadDirectory)) {
  639. if (!cmSystemTools::MakeDirectory(uploadDirectory)) {
  640. cmCPackLogger(cmCPackLog::LOG_ERROR,
  641. "Unable to create NSIS upload directory "
  642. << uploadDirectory << std::endl);
  643. return "";
  644. }
  645. }
  646. // Remove the old archive, if one exists
  647. std::string archiveFile = uploadDirectory + '/' + component->ArchiveFile;
  648. cmCPackLogger(cmCPackLog::LOG_OUTPUT,
  649. "- Building downloaded component archive: " << archiveFile
  650. << std::endl);
  651. if (cmSystemTools::FileExists(archiveFile, true)) {
  652. if (!cmSystemTools::RemoveFile(archiveFile)) {
  653. cmCPackLogger(cmCPackLog::LOG_ERROR,
  654. "Unable to remove archive file " << archiveFile
  655. << std::endl);
  656. return "";
  657. }
  658. }
  659. // Find a ZIP program
  660. if (!this->IsSet("ZIP_EXECUTABLE")) {
  661. this->ReadListFile("Internal/CPack/CPackZIP.cmake");
  662. if (!this->IsSet("ZIP_EXECUTABLE")) {
  663. cmCPackLogger(cmCPackLog::LOG_ERROR,
  664. "Unable to find ZIP program" << std::endl);
  665. return "";
  666. }
  667. }
  668. // The directory where this component's files reside
  669. std::string dirName = cmStrCat(
  670. this->GetOption("CPACK_TEMPORARY_DIRECTORY"), '/', component->Name, '/');
  671. // Build the list of files to go into this archive, and determine the
  672. // size of the installed component.
  673. std::string zipListFileName = cmStrCat(
  674. this->GetOption("CPACK_TEMPORARY_DIRECTORY"), "/winZip.filelist");
  675. bool needQuotesInFile = cmIsOn(this->GetOption("CPACK_ZIP_NEED_QUOTES"));
  676. unsigned long totalSize = 0;
  677. { // the scope is needed for cmGeneratedFileStream
  678. cmGeneratedFileStream out(zipListFileName);
  679. for (std::string const& file : component->Files) {
  680. if (needQuotesInFile) {
  681. out << "\"";
  682. }
  683. out << file;
  684. if (needQuotesInFile) {
  685. out << "\"";
  686. }
  687. out << std::endl;
  688. totalSize += cmSystemTools::FileLength(dirName + file);
  689. }
  690. }
  691. // Build the archive in the upload area
  692. std::string cmd = this->GetOption("CPACK_ZIP_COMMAND");
  693. cmsys::SystemTools::ReplaceString(cmd, "<ARCHIVE>", archiveFile.c_str());
  694. cmsys::SystemTools::ReplaceString(cmd, "<FILELIST>",
  695. zipListFileName.c_str());
  696. std::string output;
  697. int retVal = -1;
  698. int res = cmSystemTools::RunSingleCommand(
  699. cmd, &output, &output, &retVal, dirName.c_str(),
  700. cmSystemTools::OUTPUT_NONE, cmDuration::zero());
  701. if (!res || retVal) {
  702. std::string tmpFile = cmStrCat(
  703. this->GetOption("CPACK_TOPLEVEL_DIRECTORY"), "/CompressZip.log");
  704. cmGeneratedFileStream ofs(tmpFile);
  705. ofs << "# Run command: " << cmd << std::endl
  706. << "# Output:" << std::endl
  707. << output << std::endl;
  708. cmCPackLogger(cmCPackLog::LOG_ERROR,
  709. "Problem running zip command: " << cmd << std::endl
  710. << "Please check "
  711. << tmpFile << " for errors"
  712. << std::endl);
  713. return "";
  714. }
  715. // Create the NSIS code to download this file on-the-fly.
  716. unsigned long totalSizeInKbytes = (totalSize + 512) / 1024;
  717. if (totalSizeInKbytes == 0) {
  718. totalSizeInKbytes = 1;
  719. }
  720. std::ostringstream out;
  721. /* clang-format off */
  722. out << " AddSize " << totalSizeInKbytes << "\n"
  723. << " Push \"" << component->ArchiveFile << "\"\n"
  724. << " Call DownloadFile\n"
  725. << " ZipDLL::extractall \"$INSTDIR\\"
  726. << component->ArchiveFile << "\" \"$INSTDIR\"\n"
  727. << " Pop $2 ; error message\n"
  728. " StrCmp $2 \"success\" +2 0\n"
  729. " MessageBox MB_OK \"Failed to unzip $2\"\n"
  730. " Delete $INSTDIR\\$0\n";
  731. /* clang-format on */
  732. componentCode += out.str();
  733. } else {
  734. componentCode +=
  735. " File /r \"${INST_DIR}\\" + component->Name + "\\*.*\"\n";
  736. }
  737. componentCode += "SectionEnd\n";
  738. // Macro used to remove the component
  739. macrosOut << "!macro Remove_${" << component->Name << "}\n";
  740. macrosOut << " IntCmp $" << component->Name << "_was_installed 0 noremove_"
  741. << component->Name << "\n";
  742. std::string path;
  743. for (std::string const& pathIt : component->Files) {
  744. path = pathIt;
  745. std::replace(path.begin(), path.end(), '/', '\\');
  746. macrosOut << " Delete \"" << componentOutputDir << "\\" << path << "\"\n";
  747. }
  748. for (std::string const& pathIt : component->Directories) {
  749. path = pathIt;
  750. std::replace(path.begin(), path.end(), '/', '\\');
  751. macrosOut << " RMDir \"" << componentOutputDir << "\\" << path << "\"\n";
  752. }
  753. macrosOut << " noremove_" << component->Name << ":\n";
  754. macrosOut << "!macroend\n";
  755. // Macro used to select each of the components that this component
  756. // depends on.
  757. std::set<cmCPackComponent*> visited;
  758. macrosOut << "!macro Select_" << component->Name << "_depends\n";
  759. macrosOut << this->CreateSelectionDependenciesDescription(component,
  760. visited);
  761. macrosOut << "!macroend\n";
  762. // Macro used to deselect each of the components that depend on this
  763. // component.
  764. visited.clear();
  765. macrosOut << "!macro Deselect_required_by_" << component->Name << "\n";
  766. macrosOut << this->CreateDeselectionDependenciesDescription(component,
  767. visited);
  768. macrosOut << "!macroend\n";
  769. return componentCode;
  770. }
  771. std::string cmCPackNSISGenerator::CreateSelectionDependenciesDescription(
  772. cmCPackComponent* component, std::set<cmCPackComponent*>& visited)
  773. {
  774. // Don't visit a component twice
  775. if (visited.count(component)) {
  776. return std::string();
  777. }
  778. visited.insert(component);
  779. std::ostringstream out;
  780. for (cmCPackComponent* depend : component->Dependencies) {
  781. // Write NSIS code to select this dependency
  782. out << " SectionGetFlags ${" << depend->Name << "} $0\n";
  783. out << " IntOp $0 $0 | ${SF_SELECTED}\n";
  784. out << " SectionSetFlags ${" << depend->Name << "} $0\n";
  785. out << " IntOp $" << depend->Name << "_selected 0 + ${SF_SELECTED}\n";
  786. // Recurse
  787. out
  788. << this->CreateSelectionDependenciesDescription(depend, visited).c_str();
  789. }
  790. return out.str();
  791. }
  792. std::string cmCPackNSISGenerator::CreateDeselectionDependenciesDescription(
  793. cmCPackComponent* component, std::set<cmCPackComponent*>& visited)
  794. {
  795. // Don't visit a component twice
  796. if (visited.count(component)) {
  797. return std::string();
  798. }
  799. visited.insert(component);
  800. std::ostringstream out;
  801. for (cmCPackComponent* depend : component->ReverseDependencies) {
  802. // Write NSIS code to deselect this dependency
  803. out << " SectionGetFlags ${" << depend->Name << "} $0\n";
  804. out << " IntOp $1 ${SF_SELECTED} ~\n";
  805. out << " IntOp $0 $0 & $1\n";
  806. out << " SectionSetFlags ${" << depend->Name << "} $0\n";
  807. out << " IntOp $" << depend->Name << "_selected 0 + 0\n";
  808. // Recurse
  809. out << this->CreateDeselectionDependenciesDescription(depend, visited)
  810. .c_str();
  811. }
  812. return out.str();
  813. }
  814. std::string cmCPackNSISGenerator::CreateComponentGroupDescription(
  815. cmCPackComponentGroup* group, std::ostream& macrosOut)
  816. {
  817. if (group->Components.empty() && group->Subgroups.empty()) {
  818. // Silently skip empty groups. NSIS doesn't support them.
  819. return std::string();
  820. }
  821. std::string code = "SectionGroup ";
  822. if (group->IsExpandedByDefault) {
  823. code += "/e ";
  824. }
  825. if (group->IsBold) {
  826. code += "\"!" + group->DisplayName + "\" " + group->Name + "\n";
  827. } else {
  828. code += "\"" + group->DisplayName + "\" " + group->Name + "\n";
  829. }
  830. for (cmCPackComponentGroup* g : group->Subgroups) {
  831. code += this->CreateComponentGroupDescription(g, macrosOut);
  832. }
  833. for (cmCPackComponent* comp : group->Components) {
  834. if (comp->Files.empty()) {
  835. continue;
  836. }
  837. code += this->CreateComponentDescription(comp, macrosOut);
  838. }
  839. code += "SectionGroupEnd\n";
  840. return code;
  841. }
  842. std::string cmCPackNSISGenerator::CustomComponentInstallDirectory(
  843. cm::string_view componentName)
  844. {
  845. const char* outputDir = this->GetOption(
  846. cmStrCat("CPACK_NSIS_", componentName, "_INSTALL_DIRECTORY"));
  847. return outputDir ? outputDir : "$INSTDIR";
  848. }
  849. std::string cmCPackNSISGenerator::TranslateNewlines(std::string str)
  850. {
  851. cmSystemTools::ReplaceString(str, "\n", "$\\r$\\n");
  852. return str;
  853. }