1use std::fmt::Debug;
6use std::fs::{self, File};
7use std::io::Write;
8use std::path::{Path, PathBuf};
9use std::process::Command;
10
11use super::config::NsisConfig;
12use crate::base::{expand_file_macro, Arch, PlatformTarget};
13use crate::config::{Config, WindowsConfig};
14use crate::error::{Error, ErrorKind};
15
16pub fn build_nsis(conf: &Config, windows_conf: &WindowsConfig, arch: Arch) -> Result<(), Error> {
17 let nsis_conf = if let Some(nsis_conf) = windows_conf.nsis.as_ref() {
18 nsis_conf
19 } else {
20 return Err(Error::new(
21 ErrorKind::InvalidConfError,
22 "`nsis` config not set!",
23 ));
24 };
25
26 if let Some(script) = nsis_conf.script.as_ref() {
27 compile_nsis(&script)
28 } else {
29 let nsis_file = generate_nsis_file(conf, windows_conf, arch, nsis_conf)?;
30 compile_nsis(&nsis_file)
31 }
32}
33
34fn define_header(
35 conf: &Config,
36 arch: Arch,
37 nsis_conf: &NsisConfig,
38 nsis_fd: &mut File,
39) -> Result<(), Error> {
40 writeln!(nsis_fd, "# Generated by pifu. DO NOT EDIT!\n")?;
42 writeln!(nsis_fd, "!include \"MUI2.nsh\"\n")?;
43
44 if let Some(include_file) = nsis_conf.include.as_ref() {
45 writeln!(nsis_fd, "!include {:?}\n", fs::canonicalize(include_file)?)?;
46 }
47
48 writeln!(nsis_fd, "Name {}", &conf.metadata.name)?;
49
50 if nsis_conf.unicode {
51 writeln!(nsis_fd, "Unicode True")?;
52 } else {
53 writeln!(nsis_fd, "Unicode False")?;
54 }
55
56 let artifact_name =
57 expand_file_macro(&nsis_conf.artifact_name, conf, arch, PlatformTarget::Nsis)?;
58 writeln!(nsis_fd, r#"OutFile "{artifact_name}""#)?;
59 writeln!(
60 nsis_fd,
61 "SetCompressor /SOLID {}\n",
62 nsis_conf.compress_method
63 )?;
64
65 if nsis_conf.warnings_as_errors {
66 writeln!(nsis_fd, "!define MUI_ABORTWARNING")?;
67 }
68 Ok(())
69}
70
71fn define_icons(nsis_conf: &NsisConfig, nsis_fd: &mut File) -> Result<(), Error> {
72 writeln!(
74 nsis_fd,
75 "!define MUI_ICON {:?}",
76 fs::canonicalize(&nsis_conf.installer_icon)?
77 )?;
78 writeln!(
79 nsis_fd,
80 "!define MUI_UNICON {:?}",
81 fs::canonicalize(&nsis_conf.uninstaller_icon)?
82 )?;
83
84 if let Some(header_icon) = nsis_conf.installer_header_icon.as_ref() {
85 writeln!(nsis_fd, "!define MUI_HEADERIMAGE")?;
86 writeln!(
87 nsis_fd,
88 "!define MUI_HEADERIMAGE_BITMAP {:?}",
89 fs::canonicalize(header_icon)?
90 )?;
91 }
92 if let Some(installer_sidebar) = nsis_conf.installer_sidebar.as_ref() {
93 writeln!(
94 nsis_fd,
95 "!define MUI_WELCOMEFINISHPAGE_BITMAP {:?}",
96 fs::canonicalize(installer_sidebar)?
97 )?;
98 }
99 if let Some(uninstaller_sidebar) = nsis_conf.uninstaller_sidebar.as_ref() {
100 writeln!(
101 nsis_fd,
102 "!define MUI_UNWELCOMEFINISHPAGE_BITMAP {:?}",
103 fs::canonicalize(uninstaller_sidebar)?
104 )?;
105 }
106
107 Ok(())
108}
109
110fn define_pages(
111 conf: &Config,
112 windows_conf: &WindowsConfig,
113 arch: Arch,
114 nsis_conf: &NsisConfig,
115 nsis_fd: &mut File,
116) -> Result<(), Error> {
117 if nsis_conf.one_click {
119 writeln!(
120 nsis_fd,
121 r#"InstallDir "$LOCALAPPDATA\Programs\{}""#,
122 &conf.metadata.name
123 )?;
124 writeln!(nsis_fd, "RequestExecutionlevel User")?;
125 writeln!(nsis_fd, "SilentInstall silent")?;
127 } else {
128 if nsis_conf.per_machine {
129 if arch == Arch::X86_64 {
130 writeln!(
131 nsis_fd,
132 r#"InstallDir "$PROGRAMFILES64\{}""#,
133 &conf.metadata.name
134 )?;
135 } else {
136 writeln!(
137 nsis_fd,
138 r#"InstallDir $PROGRAMFILES\{}"#,
139 &conf.metadata.name
140 )?;
141 }
142 writeln!(nsis_fd, "RequestExecutionlevel Admin")?;
143 } else {
144 writeln!(
145 nsis_fd,
146 r#"InstallDir "$LocalAppData\Programs\{}""#,
147 &conf.metadata.name
148 )?;
149 writeln!(nsis_fd, "RequestExecutionlevel User")?;
150 }
151
152 writeln!(nsis_fd)?;
153
154 if nsis_conf.installer_sidebar.is_some() {
155 writeln!(nsis_fd, "!insertmacro MUI_PAGE_WELCOME")?;
156 }
157
158 if let Some(license_file) = conf.metadata.license_file.as_ref() {
159 writeln!(
160 nsis_fd,
161 "!insertmacro MUI_PAGE_LICENSE {:?}",
162 fs::canonicalize(license_file)?
163 )?;
164 }
165
166 if nsis_conf.allow_to_change_installation_directory {
167 writeln!(nsis_fd, "!insertmacro MUI_PAGE_DIRECTORY")?;
168 }
169
170 writeln!(nsis_fd, "!insertmacro MUI_PAGE_INSTFILES\n")?;
171
172 if nsis_conf.run_after_finish {
173 writeln!(
174 nsis_fd,
175 r#"!define MUI_FINISHPAGE_RUN "$INSTDIR\{}""#,
176 &windows_conf.exe_file
177 )?;
178 writeln!(nsis_fd, "!define MUI_FINISHPAGE_NOREBOOTSUPPORT")?;
179 writeln!(nsis_fd, "!insertmacro MUI_PAGE_FINISH\n")?;
180 }
181
182 if nsis_conf.uninstaller_sidebar.is_some() {
183 writeln!(nsis_fd, "!insertmacro MUI_UNPAGE_WELCOME")?;
184 }
185 writeln!(nsis_fd, "!insertmacro MUI_UNPAGE_CONFIRM")?;
186 writeln!(nsis_fd, "!insertmacro MUI_UNPAGE_INSTFILES")?;
187 }
188
189 Ok(())
190}
191
192fn define_languages(conf: &Config, nsis_fd: &mut File) -> Result<(), Error> {
193 writeln!(nsis_fd)?;
194 writeln!(nsis_fd, r#"!insertmacro MUI_LANGUAGE "English""#)?;
195
196 let build_version = format!("{}.{}", &conf.metadata.version, &conf.metadata.build_id);
197
198 writeln!(nsis_fd, r#"VIProductVersion "{}""#, &build_version)?;
200 writeln!(nsis_fd, r#"VIFileVersion "{}""#, &build_version)?;
201
202 writeln!(
203 nsis_fd,
204 r#"VIAddVersionKey /LANG=${{LANG_ENGLISH}} "ProductName" "{}""#,
205 &conf.metadata.product_name
206 )?;
207 writeln!(
208 nsis_fd,
209 r#"VIAddVersionKey /LANG=${{LANG_ENGLISH}} "ProductVersion" "{}""#,
210 &conf.metadata.version
211 )?;
212 writeln!(
213 nsis_fd,
214 r#"VIAddVersionKey /LANG=${{LANG_ENGLISH}} "FileDescription" "{}""#,
215 &conf.metadata.description
216 )?;
217 if let Some(ref company) = conf.metadata.company {
218 writeln!(
219 nsis_fd,
220 r#"VIAddVersionKey /LANG=${{LANG_ENGLISH}} "CompanyName" "{company}""#
221 )?;
222 }
223 if let Some(ref copyright) = conf.metadata.copyright {
224 writeln!(
225 nsis_fd,
226 r#"VIAddVersionKey /LANG=${{LANG_ENGLISH}} "LegalCopyright" "{copyright}""#
227 )?;
228 }
229 writeln!(
230 nsis_fd,
231 r#"VIAddVersionKey /LANG=${{LANG_ENGLISH}} "FileVersion" "{}""#,
232 &build_version
233 )?;
234
235 Ok(())
236}
237
238fn define_uninstall_section(
239 conf: &Config,
240 nsis_conf: &NsisConfig,
241 nsis_fd: &mut File,
242 reg_section: &str,
243 reg_uninst_key: &str,
244) -> Result<(), Error> {
245 writeln!(nsis_fd, "\nSection \"Uninstall\"")?;
247 writeln!(nsis_fd, r#" Delete "$INSTDIR\Uninstall.exe""#)?;
248 writeln!(nsis_fd, r#" RMDir /r "$INSTDIR""#)?;
249 if nsis_conf.run_on_startup {
250 writeln!(
251 nsis_fd,
252 r#" DeleteRegKey {} "Software\Microsoft\Windows\CurrentVersion\Run\{}""#,
253 reg_section, &conf.metadata.product_name,
254 )?;
255 }
256 writeln!(
257 nsis_fd,
258 r#" DeleteRegKey {reg_section} "{reg_uninst_key}""#
259 )?;
260 if nsis_conf.create_start_menu_shortcut {
261 writeln!(
262 nsis_fd,
263 r#" Delete "$SMPROGRAMS\{}.lnk""#,
264 &conf.metadata.product_name
265 )?;
266 }
267 if nsis_conf.create_desktop_shortcut {
268 writeln!(
269 nsis_fd,
270 r#" Delete "$DESKTOP\{}.lnk""#,
271 &conf.metadata.product_name
272 )?;
273 }
274 writeln!(nsis_fd, "SectionEnd")?;
275 Ok(())
276}
277
278fn define_install_section(
279 conf: &Config,
280 windows_conf: &WindowsConfig,
281 nsis_conf: &NsisConfig,
282 nsis_fd: &mut File,
283 nsis_dir: &Path,
284) -> Result<(), Error> {
285 let files = if let Some(files) = nsis_conf.files.as_ref() {
286 files
287 } else if let Some(files) = windows_conf.files.as_ref() {
288 files
289 } else {
290 return Err(Error::new(
291 ErrorKind::FilesNotSet,
292 "`files` property not set for nsis",
293 ));
294 };
295
296 writeln!(nsis_fd, "\nSection \"Install\"")?;
298 writeln!(nsis_fd, r#" SetOutPath "$INSTDIR""#)?;
299 for file in files {
300 file.copy_to(&conf.metadata.src_dir, nsis_dir)?;
301 writeln!(nsis_fd, " File {}", &file.to)?;
302 }
303 writeln!(nsis_fd, r#" WriteUninstaller "$INSTDIR\Uninstall.exe""#)?;
304
305 let reg_section = if nsis_conf.per_machine {
306 "HKLM"
307 } else {
308 "HKCU"
309 };
310
311 let reg_uninst_key = format!(
312 r#"Software\Microsoft\Windows\CurrentVersion\Uninstall\{}"#,
313 &conf.metadata.product_name
314 );
315
316 writeln!(
317 nsis_fd,
318 r#" WriteRegStr {reg_section} "{reg_uninst_key}" "UninstallString" '"$INSTDIR\Uninstall.exe"'"#
319 )?;
320 writeln!(
321 nsis_fd,
322 r#" WriteRegStr {reg_section} "{reg_uninst_key}" "QuietUninstallString" '"$INSTDIR\Uninstall.exe" /S'"#
323 )?;
324 writeln!(
325 nsis_fd,
326 r#" WriteRegStr {reg_section} "{reg_uninst_key}" "InstallLocation" "$INSTDIR""#
327 )?;
328 writeln!(
329 nsis_fd,
330 r#" WriteRegStr {} "{}" "DisplayName" "{}""#,
331 reg_section, reg_uninst_key, &conf.metadata.product_name
332 )?;
333 writeln!(
334 nsis_fd,
335 r#"WriteRegStr {} "{}" "DisplayIcon" "$INSTDIR\{},0""#,
336 reg_section, reg_uninst_key, &windows_conf.exe_file
337 )?;
338 writeln!(
339 nsis_fd,
340 r#"WriteRegStr {} "{}" "DisplayVersion" "{}""#,
341 reg_section, reg_uninst_key, &conf.metadata.version
342 )?;
343 if let Some(company) = conf.metadata.company.as_ref() {
344 writeln!(
345 nsis_fd,
346 r#"WriteRegStr {reg_section} "{reg_uninst_key}" "Publisher" "{company}""#
347 )?;
348 }
349 writeln!(
350 nsis_fd,
351 r#" WriteRegDWORD {reg_section} "{reg_uninst_key}" "NoModify" "1""#
352 )?;
353 writeln!(
354 nsis_fd,
355 r#" WriteRegDWORD {reg_section} "{reg_uninst_key}" "NoRepair" "1""#
356 )?;
357 if nsis_conf.run_on_startup {
360 writeln!(
361 nsis_fd,
362 r#" WriteRegStr {} "Software\Microsoft\Windows\CurrentVersion\Run" "{}" '"$INSTDIR\{}"'"#,
363 reg_section, &conf.metadata.product_name, &windows_conf.exe_file
364 )?;
365 }
366 if nsis_conf.create_start_menu_shortcut {
367 writeln!(
368 nsis_fd,
369 r#" CreateShortcut "$SMPROGRAMS\{}.lnk" "$INSTDIR\{}""#,
370 &conf.metadata.product_name, &windows_conf.exe_file
371 )?;
372 }
373 if nsis_conf.create_desktop_shortcut {
374 writeln!(
375 nsis_fd,
376 r#" CreateShortcut "$DESKTOP\{}.lnk" "$INSTDIR\{}""#,
377 &conf.metadata.product_name, &windows_conf.exe_file
378 )?;
379 }
380 writeln!(nsis_fd, "SectionEnd")?;
381
382 define_uninstall_section(conf, nsis_conf, nsis_fd, reg_section, ®_uninst_key)
383}
384
385fn generate_nsis_file(
386 conf: &Config,
387 windows_conf: &WindowsConfig,
388 arch: Arch,
389 nsis_conf: &NsisConfig,
390) -> Result<PathBuf, Error> {
391 let workdir = Path::new(&conf.metadata.workdir);
392 let nsis_dir = workdir.join("nsis");
393 fs::create_dir_all(&nsis_dir)?;
394 let nsis_file = nsis_dir.join("app.nsi");
395 let mut nsis_fd = File::create(&nsis_file)?;
396
397 define_header(conf, arch, nsis_conf, &mut nsis_fd)?;
398 define_icons(nsis_conf, &mut nsis_fd)?;
399 define_pages(conf, windows_conf, arch, nsis_conf, &mut nsis_fd)?;
400 define_languages(conf, &mut nsis_fd)?;
401 define_install_section(conf, windows_conf, nsis_conf, &mut nsis_fd, &nsis_dir)?;
402
403 Ok(nsis_file)
404}
405
406fn compile_nsis<P>(nsis_file: &P) -> Result<(), Error>
408where
409 P: AsRef<Path> + Debug,
410{
411 let status = Command::new("makensis").arg(nsis_file.as_ref()).status()?;
412 if status.success() {
413 Ok(())
414 } else {
415 Err(Error::from_string(
416 ErrorKind::NsisCompilerError,
417 format!("`makensis` returns error while compiling {nsis_file:?}"),
418 ))
419 }
420}