-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathMinGW_Installer.iss
More file actions
302 lines (265 loc) · 12 KB
/
Copy pathMinGW_Installer.iss
File metadata and controls
302 lines (265 loc) · 12 KB
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
; Preprocessor directives and comments first. The Environment.iss include below
; opens a [Code] section, so every line between it and [Setup] is Pascal source
; -- a ';' comment placed after the include is parsed as code and fails with
; "'BEGIN' expected".
; WizardStyle's "dynamic" appearance mode (automatic light/dark) landed in Inno
; Setup 6.6.0. On anything older the directive below is rejected and the error
; points at a line the contributor did not write, so fail early and say why.
; WizardStyle itself does not exist at all in Inno Setup 5.
#if VER < EncodeVer(6,6,0)
#error "Inno Setup 6.6.0 or later is required (WizardStyle 'dynamic'). Download: https://jrsoftware.org/isdl.php"
#endif
#define MyAppPublisher "Ehsan"
#define MyAppURL "https://ehsan.pages.dev"
#ifndef MyAppName
#define MyAppName "Easy MinGW Installer"
#endif
#ifndef MyOutputName
#define MyOutputName MyAppName
#endif
#ifndef MyAppVersion
#define MyAppVersion "0.0.1"
#endif
#ifndef Arch
#define Arch "64"
#endif
; Deliberately NOT called SourcePath. ISPP predefines a variable of that name
; holding the directory of the .iss itself, so "#ifndef SourcePath" was always
; false and this guard never fired. Compiling the script directly, without the
; build passing a value, silently resolved {#MinGWSource} to the repo root and
; failed later with a confusing message about a missing version_info.txt.
#ifndef MinGWSource
#error "MinGWSource not defined. Pass /DMinGWSource=<extracted mingw dir> -- Builder.ps1 does this for you."
#endif
; Defines EnvironmentKeyPath and the [Code] EnvRemovePath used below.
#include "inno\Environment.iss"
[Setup]
AppId={{078C8544-DE40-43A5-B293-58408E30C089}
AppName={#MyAppName}
SetupIconFile="assets\icon{#Arch}.ico"
UninstallDisplayIcon="{sd}\MinGW{#Arch}\icon{#Arch}.ico"
AppVersion={#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
; Without these the published setup.exe has a blank FileVersion and no company:
; VersionInfoVersion defaults to 0.0.0.0 and is NOT derived from AppVersion.
VersionInfoVersion={#MyAppVersion}
VersionInfoCompany={#MyAppPublisher}
VersionInfoProductName={#MyAppName}
VersionInfoDescription={#MyAppName} {#Arch}-bit Setup
; Only one architecture is ever installed, but nothing in the Add/Remove
; Programs entry said which one. Without this it falls back to AppVerName.
UninstallDisplayName={#MyAppName} ({#Arch}-bit)
; With CreateAppDir=no, {app} is {win}, so Inno puts the uninstall data in the
; Windows directory -- roughly 12 MB of unins000.exe/.dat in C:\Windows.
;
; That is untidy, and UninstallFilesDir={sd}\MinGW{#Arch} was tried to fix it.
; Do not do that. It puts the uninstaller inside the tree PrepareToInstall
; deletes, and DelTree deletes every file it CAN before reporting failure. A
; locked file -- the exact case the removal error message describes -- then
; destroys unins000.exe while Setup aborts and installs nothing, leaving an
; Add/Remove Programs entry pointing at a file that no longer exists and cannot
; be uninstalled. Keeping the uninstaller outside the deleted tree means a
; failed removal is always recoverable: the entry still works and its log can
; clean up whatever survived.
CreateAppDir=no
PrivilegesRequired=admin
OutputDir={#OutputPath}
OutputBaseFilename="{#MyOutputName}.v{#MyAppVersion}.{#Arch}-bit"
Compression=lzma2/ultra64
SolidCompression=yes
WizardStyle=modern dynamic
ChangesEnvironment=yes
InfoBeforeFile="{#MinGWSource}\version_info.txt"
DisableWelcomePage=yes
DisableDirPage=yes
DisableProgramGroupPage=yes
DisableReadyPage=yes
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Messages]
; Custom string overrides
ButtonNext=&Install
WizardInfoBefore=Package Info
InfoBeforeLabel=Details regarding the packages included in this build.
SetupWindowTitle={#MyAppName} v{#MyAppVersion}
[Files]
Source: "{#MinGWSource}\*"; DestDir: "{sd}\MinGW{#Arch}"; Flags: ignoreversion recursesubdirs createallsubdirs
; ignoreversion to match the line above: an .ico carries no version resource, so
; without it Inno falls back to timestamps and can skip replacing the icon.
Source: "assets\icon{#Arch}.ico"; DestDir: "{sd}\MinGW{#Arch}"; Flags: ignoreversion
[Registry]
; Prepend the MinGW bin directory to the system PATH. {olddata} preserves the
; existing value, which is why this entry cannot carry uninsdeletevalue -- it
; does not own the value it writes. Removal on uninstall is NOT automatic; it is
; done explicitly by CurUninstallStepChanged at the bottom of [Code].
Root: HKLM; Subkey: "{#EnvironmentKeyPath}"; \
ValueType: expandsz; ValueName: "Path"; ValueData: "{sd}\MinGW{#Arch}\bin;{olddata}"; \
Check: NeedsAddPath(ExpandConstant('{sd}\MinGW{#Arch}\bin'))
[Code]
const
{ Auto-click constants for skipping pages }
BN_CLICKED = 0;
WM_COMMAND = $0111;
CN_BASE = $BC00;
CN_COMMAND = CN_BASE + WM_COMMAND;
var
MinGWDir: string; { Initialized in InitializeSetup }
MinGWBinDir: string; { Initialized in InitializeSetup }
OtherDir: string; { The other architecture's install, if any }
OtherBinDir: string; { Initialized in InitializeSetup }
IsUpgrade: Boolean; { True if this architecture is already installed }
HasOtherArch: Boolean; { True if the other architecture is installed }
function NeedsAddPath(Path: string): Boolean;
var
Paths: string;
begin
{ Returns True if the path is not already in PATH. EnvironmentKey comes from
the included Environment.iss, so this and EnvRemovePath cannot end up
reading and writing different registry keys. }
if not RegQueryStringValue(HKLM, EnvironmentKey, 'Path', Paths) then
Result := True
else
Result := Pos(Uppercase(Path) + ';', Uppercase(Paths + ';')) = 0;
end;
function InitializeSetup: Boolean;
begin
MinGWDir := ExpandConstant('{sd}\MinGW{#Arch}');
MinGWBinDir := MinGWDir + '\bin';
{ Only one architecture may be installed at a time. Both builds put a gcc.exe
on the system PATH, and whichever entry comes first silently wins, so a
32-bit install left sitting next to a 64-bit one hands the user a toolchain
they did not ask for. Installing either build therefore replaces the other.
Sharing one AppId across both architectures is deliberate -- it keeps a
single Add/Remove Programs entry -- but it does NOT make Inno remove the
other build. Same AppId means Setup *appends* to the existing uninstall log.
The replacement has to be done here. }
if '{#Arch}' = '64' then
OtherDir := ExpandConstant('{sd}\MinGW32')
else
OtherDir := ExpandConstant('{sd}\MinGW64');
OtherBinDir := OtherDir + '\bin';
IsUpgrade := DirExists(MinGWDir);
HasOtherArch := DirExists(OtherDir);
Result := True;
end;
{ Removes one installation and its PATH entry. Returns False if the tree could
not be fully deleted, which in practice means a file is still locked by a
running gcc.exe, gdb.exe or a shell sitting in the directory.
Safe to call for a directory that does not exist, which is what lets the
caller ask for both architectures unconditionally. }
function RemoveInstallation(const Dir, BinDir: string): Boolean;
begin
Result := True;
if not DirExists(Dir) then
Exit;
{ PATH entry first: if the delete then fails part-way, the user is left with a
stale directory rather than a PATH entry pointing into a half-deleted tree. }
EnvRemovePath(BinDir);
Result := DelTree(Dir, True, True, True);
end;
function PrepareToInstall(var NeedsRestart: Boolean): String;
var
ProgressPage: TOutputProgressWizardPage;
Prompt: string;
Removed: Boolean;
begin
Result := '';
if IsUpgrade or HasOtherArch then
begin
{ Ask the user for confirmation, but only when there is a user to ask.
PrepareToInstall still runs under /SILENT and /VERYSILENT, where this
MsgBox appears with no visible parent and blocks Setup until someone
finds and clicks it -- a headless deployment hangs until it is killed.
Passing /SILENT is itself consent to an unattended upgrade, so silent
runs proceed as if Yes was chosen.
Note: this is deliberately a nested if rather than
"if (not WizardSilent) and (MsgBox(...) = IDNO)". Pascal Script does not
guarantee short-circuit evaluation, so the combined form would still
call MsgBox during a silent install. }
if not WizardSilent then
begin
Prompt := '';
if IsUpgrade then
Prompt := 'A previous version of Easy MinGW Installer is installed.' + #13#10;
if HasOtherArch then
Prompt := Prompt +
'A different architecture is installed at ' + OtherDir + '.' + #13#10 +
'Only one architecture can be installed at a time, because both' + #13#10 +
'add a compiler to the system PATH.' + #13#10;
if MsgBox(Prompt + #13#10 +
'The existing installation will be removed before installing this version.' + #13#10 + #13#10 +
'Do you want to continue?', mbConfirmation, MB_YESNO) = IDNO then
begin
{ Inno renders any non-empty return as an error message, so say plainly
that nothing happened rather than making a deliberate choice read as
a failure. }
Result := 'Setup was cancelled. Nothing was installed or removed.';
Exit;
end;
end;
{ Create progress page }
ProgressPage := CreateOutputProgressPage('Removing Previous Version',
'Please wait while the previous installation is being removed...');
try
ProgressPage.Show;
ProgressPage.SetText('Removing previous installation...', '');
ProgressPage.SetProgress(0, 2);
{ This architecture first, then the other one. Each call is a no-op when
its directory is absent, so one pair of calls covers all three cases:
a same-arch upgrade, an architecture switch, and a machine that ended
up with both installed before this replacement logic existed.
The second removal is skipped when the first failed. Setup is already
going to abort at that point, so tearing down the other architecture as
well would destroy a working toolchain and install nothing in its place.
Both are only ever present in the legacy both-installed state, which is
exactly where that would hurt.
Written as a nested if, not "if A and B", because Pascal Script does not
guarantee short-circuit evaluation and the second call must not run when
the first failed. }
Removed := RemoveInstallation(MinGWDir, MinGWBinDir);
ProgressPage.SetProgress(1, 2);
if Removed then
begin
if not RemoveInstallation(OtherDir, OtherBinDir) then
Removed := False;
end;
ProgressPage.SetProgress(2, 2);
{ A locked file used to fail silently: DeleteFile's result was discarded
and the page still reported success, so Setup went on to install over a
half-removed tree.
Unlike the confirmation prompt above, this is a real error and must stop
the install. Inno shows it in a message box, which under /SILENT blocks
exactly like any other Setup error, so unattended callers should pass
/SUPPRESSMSGBOXES -- they then get exit code 7 instead of a hang. }
if not Removed then
Result := 'The previous installation could not be fully removed.' + #13#10 + #13#10 +
'Close anything using MinGW -- a terminal, an editor, a running' + #13#10 +
'gcc or gdb -- and run Setup again.';
finally
ProgressPage.Hide;
end;
end;
end;
procedure InitializeWizard;
begin
WizardForm.InfoBeforeClickLabel.Hide;
end;
procedure CurPageChanged(CurPageID: Integer);
var
Param: Longint;
begin
{ Auto-click Next on Ready page since we disabled it }
if CurPageID = wpReady then
begin
Param := 0 or BN_CLICKED shl 16;
PostMessage(WizardForm.NextButton.Handle, CN_COMMAND, Param, 0);
end;
end;
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
if CurUninstallStep = usPostUninstall then
EnvRemovePath(ExpandConstant('{sd}\MinGW{#Arch}\bin'));
end;