Source file wix.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
(**************************************************************************)
(*                                                                        *)
(*    Copyright 2023 OCamlPro                                             *)
(*                                                                        *)
(*  All rights reserved. This file is distributed under the terms of the  *)
(*  GNU Lesser General Public License version 2.1, with the special       *)
(*  exception on linking described in the file LICENSE.                   *)
(*                                                                        *)
(**************************************************************************)

module Version = struct
  type t = string
  let to_string s = s
  let of_string s =
    String.iter (function
        | '0'..'9' | '.' -> ()
        | c ->
            failwith
              (Printf.sprintf "Invalid character '%c' in WIX version '%S'" c s))
      s;
    s
end

type info = {
  plugin_for: string option;
  unique_id: string;
  manufacturer: string;
  name: string;
  version: string;
  subject: string option;
  comments: string option;
  keywords: string list;
  directory: string;
  shortcuts: shortcut list;
  environment: var list;
  registry: key list;
  icon: string;
  banner: string;
  background: string;
  license: string option;
}

and shortcut =
  | File of { name: string; description: string; target: string }
  | URL of { name: string; target: string }

and var = {
  var_name: string;
  var_value: string;
  var_part: part;
}

and key = {
  key_name: string option;
  key_type: string;
  key_value: string;
}

and part =
  | All
  | First
  | Last

let print_header fmt _info =
  Format.fprintf fmt {|<?xml version="1.0" encoding="UTF-8"?>

<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
     xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui"
     xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
|}

let print_package fmt info =
  Format.fprintf fmt {|
  <Package Id="%s" Scope="perUserOrMachine"
    Manufacturer="%s" Name="%s" Version="%s" Language="0"
    InstallerVersion="500" Compressed="yes" UpgradeStrategy="majorUpgrade">

    <SummaryInformation
      Manufacturer="%s" %s
      %s %s />

    <MajorUpgrade Schedule="afterInstallInitialize" MigrateFeatures="yes"
      DowngradeErrorMessage="A newer version of this product is already installed"
      AllowSameVersionUpgrades="no" AllowDowngrades="no" />

    <MediaTemplate EmbedCab="yes" CompressionLevel="high" MaximumUncompressedMediaSize="64" />
|} info.unique_id info.manufacturer info.name info.version
   info.manufacturer
   (match info.subject with
    | None -> ""
    | Some d -> Printf.sprintf {|Description="%s"|} d)
   (match info.comments with
    | None -> ""
    | Some c -> Printf.sprintf {|Comments="%s"|} c)
   (match info.keywords with
    | [] -> ""
    | kw -> Printf.sprintf {|Keywords="%s"|} (String.concat "; " kw))

let print_plugin_specifics fmt info =
  match info.plugin_for with
  | Some (app_name) ->
      Format.fprintf fmt {|
    <Property Id="WIXPERUSERAPPFOLDER">
      <RegistrySearch Root="HKCU" Key="SOFTWARE\%s" Type="raw" />
    </Property>
    <Property Id="WIXPERMACHINEAPPFOLDER">
      <RegistrySearch Root="HKLM" Key="SOFTWARE\%s" Type="raw" />
    </Property>
    <Launch Message="This plugin requires %s"
            Condition="Installed OR NOT WIXPERUSERAPPFOLDER = &quot;&quot; OR NOT WIXPERMACHINEAPPFOLDER = &quot;&quot;" />
|} app_name app_name app_name
  | None ->
      ()

let print_application fmt info =
  Format.fprintf fmt {|
    <StandardDirectory Id="ProgramFiles64Folder">
      <Directory Id="APPLICATIONFOLDER" Name="%s" />
    </StandardDirectory>

    <ComponentGroup Id="APPLICATION" Directory="APPLICATIONFOLDER">
      <Files Include="%s\**" />
    </ComponentGroup>
|} info.name info.directory

let print_shortcut fmt _info shortcut =
  match shortcut with
  | File { name; description; target } ->
      Format.fprintf fmt {|
        <Shortcut Name="%s" Description="%s"
                  Target="%s" WorkingDirectory="APPLICATIONFOLDER" />
|} name description target
  | URL { name; target } ->
      Format.fprintf fmt {|
        <util:InternetShortcut Name="%s"
                               Target="%s"
                               IconFile="[System32Folder]SHELL32.dll" IconIndex="221" />
|} name target

let print_shortcuts fmt info =
  match info.shortcuts with
  | [] ->
      ()
  | shortcuts ->
      Format.fprintf fmt {|
    <StandardDirectory Id="ProgramMenuFolder">
      <Directory Id="ShortcutsFolder" Name="%s" />
    </StandardDirectory>

    <ComponentGroup Id="SHORTCUTS" Directory="ShortcutsFolder">
      <Component>
        <RegistryValue Root="HKMU" Key="SOFTWARE\%s\Components" Name="SHORTCUTS" Type="integer" Value="1" KeyPath="yes" />
|} info.name info.name;
      List.iter (print_shortcut fmt info) shortcuts;
      Format.fprintf fmt {|
        <RemoveFile Name="*.*" On="uninstall" />
        <RemoveFolder On="uninstall" />
      </Component>
    </ComponentGroup>
|}

let print_var fmt info (var : var) =
  let part =
    match var.var_part with
    | All -> "all"
    | First -> "first"
    | Last -> "last"
  in
  Format.fprintf fmt
{|
      <Component Condition="NOT ALLUSERS = 1">
        <RegistryValue Root="HKMU" Key="SOFTWARE\%s\Components" Name="%s_USER" Type="integer" Value="1" KeyPath="yes" />
        <Environment Action="set" Part="%s" Name="%s" Value="%s" />
      </Component>
      <Component Condition="ALLUSERS = 1">
        <RegistryValue Root="HKMU" Key="SOFTWARE\%s\Components" Name="%s_SYS" Type="integer" Value="1" KeyPath="yes" />
        <Environment System="yes" Action="set" Part="%s" Name="%s" Value="%s" />
      </Component>
|} info.name var.var_name part var.var_name var.var_value
   info.name var.var_name part var.var_name var.var_value

let print_environment fmt info =
  match info.environment with
  | [] ->
      ()
  | vars ->
      Format.fprintf fmt {|
    <ComponentGroup Id="ENVIRONMENT">
|};
      List.iter (print_var fmt info) vars;
      Format.fprintf fmt {|
    </ComponentGroup>
|}

let print_key fmt info (key : key) =
  Format.fprintf fmt
{|
      <Component>
        <RegistryValue Root="HKMU" Key="SOFTWARE\%s" %s Type="%s" Value="%s" KeyPath="yes" />
      </Component>
|} info.name
   (match key.key_name with
    | None -> ""
    | Some kn -> Printf.sprintf {|Name="%s"|} kn)
   key.key_type key.key_value

let print_registry fmt info =
  let regkeys =
    { key_name = None; key_type = "string"; key_value = "[APPLICATIONFOLDER]" } :: info.registry
  in
  Format.fprintf fmt {|
    <ComponentGroup Id="REGISTRY">
|};
  List.iter (print_key fmt info) regkeys;
  Format.fprintf fmt {|
    </ComponentGroup>
|}

let print_features fmt info =
  Format.fprintf fmt {|
    <Feature Id="ALLFEAT" Title="Full install" Description="Install the whole application" AllowAbsent="no" Level="1">
      <ComponentGroupRef Id="APPLICATION" />|};
  if info.shortcuts <> [] then
    Format.fprintf fmt {|
      <ComponentGroupRef Id="SHORTCUTS" />|};
  if info.environment <> [] then
    Format.fprintf fmt {|
      <ComponentGroupRef Id="ENVIRONMENT" />|};
  Format.fprintf fmt {|
      <ComponentGroupRef Id="REGISTRY" />|};
  Format.fprintf fmt {|
    </Feature>
|}

let print_assets fmt info =
  Format.fprintf fmt {|
    <Property Id="ARPPRODUCTICON" Value="ICON" />
    <Icon Id="ICON" SourceFile="%s" />
    <WixVariable Id="WixUIBannerBmp" Value="%s" />
    <WixVariable Id="WixUIDialogBmp" Value="%s" />
|} info.icon info.banner info.background;
  match info.license with
  | None ->
      ()
  | Some (license) ->
    Format.fprintf fmt {|
    <WixVariable Id="WixUILicenseRtf" Value="%s" />
|} license

let print_ui fmt info =
  Format.fprintf fmt {|
    <Property Id="WIXUI_INSTALLDIR" Value="APPLICATIONFOLDER" />
    <Property Id="WIXUI_EXITDIALOGOPTIONALTEXT" Value="%s has been installed" />
    <Property Id="ApplicationFolderName" Value="%s" />
    <ui:WixUI Id="WixUI_Custom%s" />
|} info.name info.name
    (match info.plugin_for with Some _ -> "Plugin" | None -> "App")

let print_footer fmt =
  Format.fprintf fmt {|
  </Package>

</Wix>
|}

let print_wix fmt info =
  print_header fmt info;
  print_package fmt info;
  print_plugin_specifics fmt info;
  print_application fmt info;
  print_shortcuts fmt info;
  print_environment fmt info;
  print_registry fmt info;
  print_features fmt info;
  print_assets fmt info;
  print_ui fmt info;
  print_footer fmt;
  ()