cmCPackNSISGenerator.cxx 36 KB

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