package dkml-package-console
Console setup and uninstall executables for Diskuv OCaml (DKML) installation
Install
Dune Dependency
Authors
Maintainers
Sources
v0.2.0.tar.gz
md5=d65935d6a9a790fec09cfc50f6b04c43
sha512=08aeffeffa41b10ca54c9329bf9f6ec9219060c2e62c3a389d08b096513fe3dc048782e28d5006cae6a35e124cfd09441eb41a063c6a3f12fc6e60fa9d2acef7
doc/src/dkml-package-console.create/installer_sfx.ml.html
Source file installer_sfx.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348
(* Documentation: https://info.nrao.edu/computing/guide/file-access-and-archiving/7zip/7z-7za-command-line-guide Beware that the 7zip configuration file is usually CRLF, and must always be UTF-8. *) open Bos (** Highest compression. *) let sevenz_compression_level_opts = Cmd.v "-mx9" let sevenz_log_level_opts = (* 7z is super chatty! *) let output_log_level_min = Cmd.v "-bb0" in let output_log_level_max = Cmd.v "-bb3" in let disable_stdout_stream = Cmd.v "-bso0" in match Logs.level () with | Some Debug -> output_log_level_max | Some Info -> Cmd.(output_log_level_min %% disable_stdout_stream) | _ -> disable_stdout_stream let create_7z_archive ~sevenz_exe ~install_direction ~abi_selector ~archive_path ~archive_dir = let ( let* ) = Rresult.R.bind in let pwd = Dkml_package_console_common.get_ok_or_failwith_rresult (OS.Dir.current ()) in let archive_rel_dir = if Fpath.is_rel archive_dir then Fpath.(v "." // archive_dir) else match Fpath.relativize ~root:pwd archive_dir with | Some v -> v | None -> let msg = Fmt.str "The archive directory %a cannot be made relative to %a" Fpath.pp archive_dir Fpath.pp pwd in Logs.err (fun l -> l "FATAL: %s" msg); failwith msg in let run_7z cmd action = let* status = OS.Cmd.run_status cmd in match status with | `Exited 0 -> Ok () | `Exited status -> let msg = Fmt.str "%a could not %s. Exited with error code %d" Fpath.pp sevenz_exe action status in Logs.err (fun l -> l "FATAL: %s" msg); failwith msg | `Signaled signal -> (* https://stackoverflow.com/questions/1101957/are-there-any-standard-exit-status-codes-in-linux/1535733#1535733 *) let msg = Fmt.str "%a could not %s. Exited with signal %d" Fpath.pp sevenz_exe action signal in Logs.err (fun l -> l "FATAL: %s" msg); failwith msg in (* Step 1: Bundle up everything in the archive directory *) let cmd_create = Cmd.( v (Fpath.to_string sevenz_exe) % "a" %% sevenz_log_level_opts %% sevenz_compression_level_opts % "-y" % Fpath.to_string archive_path (* DIR/* is 7z's syntax for the contents of DIR *) % Fpath.(to_string (archive_rel_dir / "*"))) in Logs.debug (fun l -> l "Creating 7z archive with: %a" Cmd.pp cmd_create); let* () = run_7z cmd_create "create a self-extracting archive" in (* Step 2 7xS2con.sfx and 7xS2.sfx will autolaunch "setup.exe" (or the first .exe, which is ambiguous). We'll rename bin/dkml-package-entry.exe so that it is setup.exe. Syntax: rn <archive_name> <src_file_1> <dest_file_1> [ <src_file_2> <dest_file_2> ... ] Confer: https://documentation.help/7-Zip-18.0/rename.htm *) let cmd_rename = Cmd.( v (Fpath.to_string sevenz_exe) % "rn" %% sevenz_log_level_opts %% sevenz_compression_level_opts % "-y" % Fpath.to_string archive_path % "bin/dkml-package-entry.exe" % match install_direction with | Dkml_install_runner.Path_eval.Global_context.Install -> "setup.exe" | Dkml_install_runner.Path_eval.Global_context.Uninstall -> "uninstall.exe") in Logs.debug (fun l -> l "Renaming within a 7z archive with: %a" Cmd.pp cmd_rename); let* () = run_7z cmd_rename "rename within a self-extracting archive" in (* Step 3 Need vcruntime140.dll (or later) when 7z autolaunches setup.exe since the renamed dkml-package-entry.exe was compiled with Visual Studio. In addition, vc_redist.x64.exe or similar needs to be available if we can't guarantee the "Visual C++ Redistributable Packages" are already installed. For example, the OCaml installer does install Visual Studio, which will install the redistributable packages automatically as part of the Visual Studio Installer ... but that is the exception and not the rule. So we always bundle the redistributable packages. For simplicity we name it `vc_redist.dkml-target-abi.exe`. https://docs.microsoft.com/en-us/cpp/windows/redistributing-visual-cpp-files TODO: We also need ucrtbase.dll and api-ms-win-*.dll; https://docs.microsoft.com/en-us/cpp/porting/upgrade-your-code-to-the-universal-crt?view=msvc-170#deployment-and-redistribution-of-the-universal-crt *) let* redist_dir_str = OS.Env.req_var "VCToolsRedistDir" in let* redist_dir = Fpath.of_string redist_dir_str in let* redist_dir = OS.Dir.must_exist redist_dir in let* () = let latest_vccrt arch = (* Get lexographically highest path ex. x64/Microsoft.VC143.CRT > x64/Microsoft.VC142.CRT *) let basename_pat = "Microsoft.VC$(vcver).CRT" in let crt_pat = Fpath.(redist_dir / arch / basename_pat) in let* crt_candidates = OS.Path.query crt_pat in let best_crt_candidate = List.fold_right (fun (fp_a, defs_a) -> function | None -> Some (fp_a, defs_a) | Some (fp_b, defs_b) -> if Fpath.compare fp_a fp_b > 0 then Some (fp_a, defs_a) else Some (fp_b, defs_b)) crt_candidates None in match best_crt_candidate with | None -> Rresult.R.error_msgf "No files matched the pattern %a" Fpath.pp crt_pat | Some (src, _defs) -> Ok src in let update_with_latest_vcruntimes arch = let* z = latest_vccrt arch in (* ex. x64/Microsoft.VC142.CRT/vcruntime140.dll, x64/Microsoft.VC142.CRT/vcruntime140_1.dll *) (* 7z u: https://documentation.help/7-Zip-18.0/update.htm *) let cmd_update = Cmd.( v (Fpath.to_string sevenz_exe) % "u" %% sevenz_log_level_opts %% sevenz_compression_level_opts % "-y" % Fpath.to_string archive_path (* DIR/* is 7z's syntax for the contents of DIR *) % Fpath.(to_string (z / "vcruntime*.dll"))) in Logs.debug (fun l -> l "Updating 7z archive with: %a" Cmd.pp cmd_update); run_7z cmd_update "update a self-extracting archive" in let add_vcredist ~src = (* 7z a: https://documentation.help/7-Zip-18.0/add1.htm *) let cmd_add = Cmd.( v (Fpath.to_string sevenz_exe) % "a" %% sevenz_log_level_opts %% sevenz_compression_level_opts % "-y" % Fpath.to_string archive_path (* DIR/* is 7z's syntax for the contents of DIR *) % (Fpath.to_string src ^ "*")) in Logs.debug (fun l -> l "Adding to 7z archive with: %a" Cmd.pp cmd_add); let* () = run_7z cmd_add "add to a self-extracting archive" in (* 7z rn: https://documentation.help/7-Zip-18.0/rename.htm *) let cmd_rename = Cmd.( v (Fpath.to_string sevenz_exe) % "rn" %% sevenz_log_level_opts %% sevenz_compression_level_opts % "-y" % Fpath.to_string archive_path % Fpath.basename src % "vc_redist.dkml-target-abi.exe") in Logs.debug (fun l -> l "Renaming within a 7z archive with: %a" Cmd.pp cmd_rename); run_7z cmd_rename "rename within a self-extracting archive" in match abi_selector with | Dkml_install_runner.Path_location.Generic -> Ok () | Abi Windows_x86_64 -> let* () = update_with_latest_vcruntimes "x64" in add_vcredist ~src:Fpath.(redist_dir / "vc_redist.x64.exe") | Abi Windows_x86 -> let* () = update_with_latest_vcruntimes "x86" in add_vcredist ~src:Fpath.(redist_dir / "vc_redist.x86.exe") | Abi Windows_arm64 -> let* () = update_with_latest_vcruntimes "arm64" in add_vcredist ~src:Fpath.(redist_dir / "vc_redist.arm64.exe") | Abi _ -> Ok () in Ok () let create_sfx_exe ~sfx_path ~archive_path ~installer_path = let write_file_contents ~output file = let rec helper input = match input () with | Some (b, pos, len) -> output (Some (b, pos, len)); helper input | None -> () in Dkml_package_console_common.get_ok_or_failwith_rresult (OS.File.with_input file (fun input () -> helper input) ()) in Dkml_package_console_common.get_ok_or_failwith_rresult @@ OS.File.with_output installer_path (fun output () -> (* Mimic DOS command given in 7z documentation: copy /b 7zS.sfx + config.txt + archive.7z archive.exe *) (* 7zS.sfx or something similar and perhaps its manifest customized *) write_file_contents ~output sfx_path; (* archive.7z *) write_file_contents ~output archive_path; (* EOF *) output None; Ok installer_path) () let modify_manifest ~pe_file ~work_dir ~organization ~program_name ~program_version = let ( let* ) = Rresult.R.bind in let translate s = Str.( s |> global_replace (regexp_string "__PLACEHOLDER_ORG_NOSPACE__") organization .Dkml_package_console_common.Author_types .common_name_camel_case_nospaces |> global_replace (regexp_string "__PLACEHOLDER_PROGRAM_NOSPACE__") program_name .Dkml_package_console_common.Author_types.name_camel_case_nospaces |> global_replace (regexp_string "__PLACEHOLDER_VERSION_MNOP__") (Dkml_package_console_common.version_m_n_o_p program_version)) in let* manifest = let path = Fpath.(work_dir / "setup.exe.manifest") in let content = Option.get (Manifests.read "setup.exe.manifest") in let* () = OS.File.write path (translate content) in Ok path in let* mt_exe = OS.Cmd.get_tool (Cmd.v "mt") in let cmd = Cmd.( v (Fpath.to_string mt_exe) % "-manifest" % Fpath.to_string manifest % "-verbose" % "-validate_manifest" % "-nologo" % Fmt.str "-outputresource:%a;1" Fpath.pp pe_file) in let* status = OS.Cmd.run_status cmd in match status with | `Exited 0 -> Ok () | `Exited status -> let msg = Fmt.str "%a could not modify the manifest. Exited with error code %d" Fpath.pp mt_exe status in Logs.err (fun l -> l "FATAL: %s" msg); failwith msg | `Signaled signal -> (* https://stackoverflow.com/questions/1101957/are-there-any-standard-exit-status-codes-in-linux/1535733#1535733 *) let msg = Fmt.str "%a could not modify the manifest. Exited with signal %d" Fpath.pp mt_exe signal in Logs.err (fun l -> l "FATAL: %s" msg); failwith msg let generate ~install_direction ~archive_dir ~target_dir ~abi_selector ~organization ~program_name ~program_version ~work_dir = let abi_name = Dkml_install_runner.Path_location.show_abi_selector abi_selector in let program_name_kebab_lower_case = program_name.Dkml_package_console_common.Author_types.name_kebab_lower_case in let direction = match install_direction with | Dkml_install_runner.Path_eval.Global_context.Install -> "i" | Uninstall -> "u" in let installer_basename = Fmt.str "unsigned-%s-%s-%s-%s.exe" program_name_kebab_lower_case abi_name direction program_version in let installer_path = Fpath.(target_dir / installer_basename) in Logs.info (fun l -> l "Generating %a" Fpath.pp installer_path); Dkml_package_console_common.get_ok_or_failwith_rresult (let ( let* ) = Rresult.R.bind in let sfx_dir = Fpath.(work_dir / "sfx") in let archive_path = Fpath.( target_dir / Fmt.str "%s-%s-%s-%s.7z" program_name_kebab_lower_case abi_name direction program_version) in let sevenz_exe = Fpath.(sfx_dir / "7zr.exe") in let* (_was_created : bool) = OS.Dir.create sfx_dir in let* () = OS.File.write ~mode:0o750 sevenz_exe (Option.get (Seven_z.read "7zr.exe")) in let sfx = Option.get (Seven_z.read "7zS2con.sfx") in (* Step 1. Create custom 7zS2con.sfx. If we did MtExeModifiedManifest(SFX || ARCHIVE) then mt.exe would corrupt the 7zip archive (it would insert RT_MANIFEST resources at the end of the SFX executable, overwriting the 7zip 32-byte signature that SFX uses to find the start of the 7zip archive. Results in: 7-Zip Error: Can't find 7z archive But we can do MtExeModifiedManifest(SFX) || ARCHIVE which preserves the 7zip archive. Even signing after with SignToolExe(MtExeModifiedManifest(SFX) || ARCHIVE) should be fine because the Authenticode procedure used in PE (modern .exe) files by signtool.exe will safely update the executable sections and correctly hash the "extra data" (the ARCHIVE) after the executable sections; confer: http://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/authenticode_pe.docx *) let sfx_basename = Fmt.str "%s-%s-%s-%s.sfx" program_name_kebab_lower_case abi_name direction program_version in let sfx_path = Fpath.(target_dir / sfx_basename) in let* () = OS.File.write sfx_path sfx in let* () = modify_manifest ~work_dir ~pe_file:sfx_path ~organization ~program_name ~program_version in (* Step 2. Create ARCHIVE *) let* () = create_7z_archive ~sevenz_exe ~install_direction ~abi_selector ~archive_path ~archive_dir in (* Step 3. Create SFX || ARCHIVE. Return sfx.exe *) create_sfx_exe ~sfx_path ~archive_path ~installer_path)
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>