我正在使用Inno Setup为Windows打包Java应用程序;应用程序树如下所示:
| MyApp.jar
\---lib
| dependency-A-1.2.3.jar
| dependency-B-2.3.4.jar
| dependency-Z-x.y.z.jar
我使用Ant预先准备了整棵树(所有文件和文件夹),包括lib目录(使用* .jar通配符复制依赖项),然后我只用以下方式调用ISCC:
[Files]
Source: "PreparedFolder\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs
现在,每次用户升级应用程序时,我都需要清理lib目录,因为我想删除所有过时的依赖项.我可以将以下部分添加到我的.iss文件中:
[InstallDelete]
{app}\lib\*.jar
但我感到不安全,因为如果用户决定将应用程序安装在包含非空lib子文件夹的现有文件夹中(稀有但并非不可能),则有可能在升级时删除某些用户文件.
是否有一些最佳实践来避免此类麻烦?其他安装程序是否可以解决这些头痛问题?谢谢.
解决方法:
您可以在安装之前卸载以前的版本:
> Inno Setup: How to automatically uninstall previous installed version?
> How to detect old installation and offer removal?
如果无法完全卸载,则必须实施部分卸载.
理想的做法是对卸载程序日志(unins000.dat)进行反向工程,仅将安装解压缩到lib子文件夹并进行处理(撤消).但是由于这是一个未公开的二进制文件,所以可能很难做到.
如果您在[文件]部分中维护要安装的文件的明确列表,例如
[Files]
Source: "lib\dependency-A-1.2.3.jar"; Dest: "{app}\lib"
Source: "lib\dependency-B-2.3.4.jar"; Dest: "{app}\lib"
然后,每当依赖项更改时,将先前版本移至[InstallDelete]部分:
[Files]
Source: "lib\dependency-A-1.3.0.jar"; Dest: "{app}"
Source: "lib\dependency-B-2.3.4.jar"; Dest: "{app}"
[InstallDelete]
{app}\lib\dependency-A-1.2.3.jar
如果您使用通配符安装依赖项,
[Files]
Source: "lib\*.jar"; Dest: "{app}\lib"
并且您无法对卸载程序日志进行反向工程,则必须通过自己的方式复制其功能.
您可以使用preprocessor生成具有已安装依赖性的文件.将该文件安装到{app}文件夹中,并在安装前处理该文件.
[Files]
Source: "MyApp.jar"; DestDir: "{app}"
Source: "lib\*.jar"; DestDir: "{app}\lib"
#define ProcessFile(Source, FindResult, FindHandle) \
Local[0] = FindGetFileName(FindHandle), \
Local[1] = Source + "\\" + Local[0], \
Local[2] = FindNext(FindHandle), \
"'" + Local[0] + "'#13#10" + \
(Local[2] ? ProcessFile(Source, Local[2], FindHandle) : "")
#define ProcessFolder(Source) \
Local[0] = FindFirst(Source + "\\*.jar", faAnyFile), \
ProcessFile(Source, Local[0], Local[0])
#define DepedenciesToInstall ProcessFolder("lib")
#define DependenciesLog "{app}\dependencies.log"
[UninstallDelete]
Type: files; Name: "{#DependenciesLog}"
[Code]
procedure CurStepChanged(CurStep: TSetupStep);
var
AppPath, DependenciesLogPath: string;
Dependencies: TArrayOfString;
Count, I: Integer;
begin
DependenciesLogPath := ExpandConstant('{#DependenciesLog}');
if CurStep = ssInstall then
begin
{ If dependencies log already exists, remove the previously installed dependencies }
if LoadStringsFromFile(DependenciesLogPath, Dependencies) then
begin
Count := GetArrayLength(Dependencies);
Log(Format('Loaded %d dependencies, deleting...', [Count]));
for I := 0 to Count - 1 do
DeleteFile(ExpandConstant('{app}\lib\' + Dependencies[I]));
end;
end
else
if CurStep = ssPostInstall then
begin
{ Now that the app folder already exists, }
{ save dependencies log (to be processed by future upgrade) }
if SaveStringToFile(DependenciesLogPath, {#DepedenciesToInstall}, False) then
begin
Log('Created dependencies log');
end
else
begin
Log('Failed to create dependencies log');
end;
end;
end;
另一种方法是删除最新安装程序未安装的安装文件夹中的所有文件.
最简单的解决方案是在安装之前删除安装文件夹中的所有文件.
您可以使用[InstallDelete]
section.但是,如果您在安装文件夹中有一些具有配置的文件夹/文件,则不允许您排除它们.
您可以改为编写该Pascal脚本.请参阅Inno Setup – Delete whole application folder except for data subdirectory.您可以从我对CurStepChanged(ssInstall)
事件函数中的那个问题的回答中调用DelTreeExceptSavesDir函数:
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssInstall then
begin
DelTreeExceptSavesDir(WizardDirValue);
end;
end;
如果您确实只想删除过时的文件,为避免删除和重新创建现有文件,可以使用预处理器生成要为Pascal脚本安装的文件列表,并使用该列表仅删除真正过时的文件.
#pragma parseroption -p-
#define FileEntry(DestDir) \
" FilesNotToBeDeleted.Add('" + LowerCase(DestDir) + "');\n"
#define ProcessFile(Source, Dest, FindResult, FindHandle) \
FindResult \
? \
Local[0] = FindGetFileName(FindHandle), \
Local[1] = Source + "\\" + Local[0], \
Local[2] = Dest + "\\" + Local[0], \
(Local[0] != "." && Local[0] != ".." \
? FileEntry(Local[2]) + \
(DirExists(Local[1]) ? ProcessFolder(Local[1], Local[2]) : "") \
: "") + \
ProcessFile(Source, Dest, FindNext(FindHandle), FindHandle) \
: \
""
#define ProcessFolder(Source, Dest) \
Local[0] = FindFirst(Source + "\\*", faAnyFile), \
ProcessFile(Source, Dest, Local[0], Local[0])
#pragma parseroption -p+
[Code]
var
FilesNotToBeDeleted: TStringList;
function InitializeSetup(): Boolean;
begin
FilesNotToBeDeleted := TStringList.Create;
FilesNotToBeDeleted.Add('\data');
{#Trim(ProcessFolder('build\exe.win-amd64-3.6', ''))}
FilesNotToBeDeleted.Sorted := True;
Result := True;
end;
procedure DeleteObsoleteFiles(Path: string; RelativePath: string);
var
FindRec: TFindRec;
FilePath: string;
FileRelativePath: string;
begin
if FindFirst(Path + '\*', FindRec) then
begin
try
repeat
if (FindRec.Name <> '.') and (FindRec.Name <> '..') then
begin
FilePath := Path + '\' + FindRec.Name;
FileRelativePath := RelativePath + '\' + FindRec.Name;
if FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY <> 0 then
begin
DeleteObsoleteFiles(FilePath, FileRelativePath);
end;
if FilesNotToBeDeleted.IndexOf(Lowercase(FileRelativePath)) < 0 then
begin
if FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY <> 0 then
begin
if RemoveDir(FilePath) then
begin
Log(Format('Deleted obsolete directory %s', [FilePath]));
end
else
begin
Log(Format('Failed to delete obsolete directory %s', [FilePath]));
end;
end
else
begin
if DeleteFile(FilePath) then
begin
Log(Format('Deleted obsolete file %s', [FilePath]));
end
else
begin
Log(Format('Failed to delete obsolete file %s', [FilePath]));
end;
end;
end;
end;
until not FindNext(FindRec);
finally
FindClose(FindRec);
end;
end
else
begin
Log(Format('Failed to list %s', [Path]));
end;
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssInstall then
begin
Log('Looking for obsolete files...');
DeleteObsoleteFiles(WizardDirValue, '');
end;
end;