Skip to content

Commit 7473928

Browse files
authored
[Xamarin.Android.Build.Tasks] Bump ZipFlushFilesLimit (#7957)
Context: https://dev.azure.com/DevDiv/DevDiv/_workitems/edit/1782014 Context: 9166e03 On Windows customers are seeing the following error: error XABLD7000: Xamarin.Tools.Zip.ZipException: Renaming temporary file failed: Permission denied at Xamarin.Tools.Zip.ZipArchive.Close() in /Users/runner/work/1/s/LibZipSharp/Xamarin.Tools.Zip/ZipArchive.cs:line 939 We have traced this to the use of the [`MoveFileExW()`][0] / `MoveFileExA()` API: when libzip is trying to move its temp file to the final location, Windows is raising this error: Renaming temporary file failed: Permission denied Turning off Anti Virus seems to help, however adding an exclusion does not. This is very confusing, so we are unsure why this error is being raised. The process has the correct permissions and the file being moved is in the same directory, so its not a TEMP folder issue. Perhaps it's the number of temp files we create? Part of the `<BuildApk/>` system is that as we add files we `Flush()` the zip file to commit those changes to disk. This is partly to work around how libzip works: it does not write any data to the main file until [`zip_close()`][1] is called. To work around issues around too many files being open (9166e03), we added this flush. The limit of 50 files was picked out of a hat. Try pushing the limit up a bit to see if that helps. Additionally, introduce the following two (private!) MSBuild properties: * `$(_ZipFlushFilesLimit)`: Call `Flush()` after `$(_ZipFlushFilesLimit)` files have been added to the `.apk`. * `$(_ZipFlushSizeLimit)`: Call `Flush()` after `$(_ZipFlushSizeLimit)` bytes of data have been added to the `.apk`. [0]: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefileexw [1]: https://libzip.org/documentation/zip_close.html
1 parent 24affb5 commit 7473928

File tree

4 files changed

+89
-28
lines changed

4 files changed

+89
-28
lines changed

src/Xamarin.Android.Build.Tasks/Tasks/BuildApk.cs

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ public class BuildApk : AndroidTask
9595

9696
public bool UseAssemblyStore { get; set; }
9797

98+
public string ZipFlushFilesLimit { get; set; }
99+
100+
public string ZipFlushSizeLimit { get; set; }
101+
98102
[Required]
99103
public string ProjectFullPath { get; set; }
100104

@@ -136,6 +140,12 @@ void ExecuteWithAbi (string [] supportedAbis, string apkInputPath, string apkOut
136140
refresh = false;
137141
}
138142
using (var apk = new ZipArchiveEx (apkOutputPath, File.Exists (apkOutputPath) ? FileMode.Open : FileMode.Create )) {
143+
if (int.TryParse (ZipFlushFilesLimit, out int flushFilesLimit)) {
144+
apk.ZipFlushFilesLimit = flushFilesLimit;
145+
}
146+
if (int.TryParse (ZipFlushSizeLimit, out int flushSizeLimit)) {
147+
apk.ZipFlushSizeLimit = flushSizeLimit;
148+
}
139149
if (refresh) {
140150
for (long i = 0; i < apk.Archive.EntryCount; i++) {
141151
ZipEntry e = apk.Archive.ReadEntry ((ulong) i);
@@ -206,7 +216,6 @@ void ExecuteWithAbi (string [] supportedAbis, string apkInputPath, string apkOut
206216
AddFileToArchiveIfNewer (apk, RuntimeConfigBinFilePath, $"{AssembliesPath}rc.bin", compressionMethod: UncompressedMethod);
207217
}
208218

209-
int count = 0;
210219
foreach (var file in files) {
211220
var item = Path.Combine (file.archivePath.Replace (Path.DirectorySeparatorChar, '/'));
212221
existingEntries.Remove (item);
@@ -216,12 +225,7 @@ void ExecuteWithAbi (string [] supportedAbis, string apkInputPath, string apkOut
216225
continue;
217226
}
218227
Log.LogDebugMessage ("\tAdding {0}", file.filePath);
219-
apk.Archive.AddFile (file.filePath, item, compressionMethod: compressionMethod);
220-
count++;
221-
if (count >= ZipArchiveEx.ZipFlushFilesLimit) {
222-
apk.Flush();
223-
count = 0;
224-
}
228+
apk.AddFileAndFlush (file.filePath, item, compressionMethod: compressionMethod);
225229
}
226230

227231
var jarFiles = (JavaSourceFiles != null) ? JavaSourceFiles.Where (f => f.ItemSpec.EndsWith (".jar", StringComparison.OrdinalIgnoreCase)) : null;
@@ -236,7 +240,6 @@ void ExecuteWithAbi (string [] supportedAbis, string apkInputPath, string apkOut
236240
var jarFilePaths = libraryProjectJars.Concat (jarFiles != null ? jarFiles.Select (j => j.ItemSpec) : Enumerable.Empty<string> ());
237241
jarFilePaths = MonoAndroidHelper.DistinctFilesByContent (jarFilePaths);
238242

239-
count = 0;
240243
foreach (var jarFile in jarFilePaths) {
241244
using (var stream = File.OpenRead (jarFile))
242245
using (var jar = ZipArchive.Open (stream)) {
@@ -278,14 +281,9 @@ void ExecuteWithAbi (string [] supportedAbis, string apkInputPath, string apkOut
278281
data = d.ToArray ();
279282
}
280283
Log.LogDebugMessage ($"Adding {path} from {jarFile} as the archive file is out of date.");
281-
apk.Archive.AddEntry (data, path);
284+
apk.AddEntryAndFlush (data, path);
282285
}
283286
}
284-
count++;
285-
if (count >= ZipArchiveEx.ZipFlushFilesLimit) {
286-
apk.Flush();
287-
count = 0;
288-
}
289287
}
290288
// Clean up Removed files.
291289
foreach (var entry in existingEntries) {
@@ -377,7 +375,6 @@ void AddAssemblies (ZipArchiveEx apk, bool debug, bool compress, IDictionary<str
377375
storeGenerator = null;
378376
}
379377

380-
int count = 0;
381378
AssemblyStoreAssemblyInfo storeAssembly = null;
382379

383380
//
@@ -398,7 +395,6 @@ void AddAssemblies (ZipArchiveEx apk, bool debug, bool compress, IDictionary<str
398395
AddAssembliesFromCollection (ResolvedUserAssemblies);
399396

400397
// Add framework assemblies
401-
count = 0;
402398
AddAssembliesFromCollection (ResolvedFrameworkAssemblies);
403399

404400
if (!UseAssemblyStore) {
@@ -482,12 +478,6 @@ void AddAssembliesFromCollection (ITaskItem[] assemblies)
482478

483479
if (UseAssemblyStore) {
484480
storeGenerator.Add (assemblyStoreApkName, storeAssembly);
485-
} else {
486-
count++;
487-
if (count >= ZipArchiveEx.ZipFlushFilesLimit) {
488-
apk.Flush();
489-
count = 0;
490-
}
491481
}
492482
}
493483
}
@@ -554,7 +544,7 @@ bool AddFileToArchiveIfNewer (ZipArchiveEx apk, string file, string inArchivePat
554544
return false;
555545
}
556546
Log.LogDebugMessage ($"Adding {file} as the archive file is out of date.");
557-
apk.Archive.AddFile (file, inArchivePath, compressionMethod: compressionMethod);
547+
apk.AddFileAndFlush (file, inArchivePath, compressionMethod: compressionMethod);
558548
return true;
559549
}
560550

@@ -578,7 +568,7 @@ void AddAssemblyConfigEntry (ZipArchiveEx apk, string assemblyPath, string confi
578568
source.CopyTo (dest);
579569
dest.WriteByte (0);
580570
dest.Position = 0;
581-
apk.Archive.AddEntry (inArchivePath, dest, compressionMethod);
571+
apk.AddEntryAndFlush (inArchivePath, dest, compressionMethod);
582572
}
583573
}
584574

@@ -625,7 +615,7 @@ void AddNativeLibraryToArchive (ZipArchiveEx apk, string abi, string filesystemP
625615
return;
626616
}
627617
Log.LogDebugMessage ($"Adding native library: {filesystemPath} (APK path: {archivePath})");
628-
apk.Archive.AddEntry (archivePath, File.OpenRead (filesystemPath), compressionMethod);
618+
apk.AddEntryAndFlush (archivePath, File.OpenRead (filesystemPath), compressionMethod);
629619
}
630620

631621
void AddRuntimeLibraries (ZipArchiveEx apk, string [] supportedAbis)

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -962,6 +962,46 @@ public void CheckExcludedFilesAreMissing ()
962962
}
963963
}
964964

965+
[Test]
966+
[TestCase (1, -1)]
967+
[TestCase (5, -1)]
968+
[TestCase (50, -1)]
969+
[TestCase (100, -1)]
970+
[TestCase (512, -1)]
971+
[TestCase (1024, -1)]
972+
[TestCase (-1, 1)]
973+
[TestCase (-1, 5)]
974+
[TestCase (-1, 10)]
975+
[TestCase (-1, 100)]
976+
[TestCase (-1, 200)]
977+
public void BuildApkWithZipFlushLimits (int filesLimit, int sizeLimit)
978+
{
979+
var proj = new XamarinAndroidApplicationProject {
980+
IsRelease = false,
981+
PackageReferences = {
982+
KnownPackages.SupportDesign_27_0_2_1,
983+
KnownPackages.SupportV7CardView_27_0_2_1,
984+
KnownPackages.AndroidSupportV4_27_0_2_1,
985+
KnownPackages.SupportCoreUtils_27_0_2_1,
986+
KnownPackages.SupportMediaCompat_27_0_2_1,
987+
KnownPackages.SupportFragment_27_0_2_1,
988+
KnownPackages.SupportCoreUI_27_0_2_1,
989+
KnownPackages.SupportCompat_27_0_2_1,
990+
KnownPackages.SupportV7AppCompat_27_0_2_1,
991+
KnownPackages.SupportV7MediaRouter_27_0_2_1,
992+
},
993+
};
994+
proj.SetProperty ("EmbedAssembliesIntoApk", "true");
995+
if (filesLimit > 0)
996+
proj.SetProperty ("_ZipFlushFilesLimit", filesLimit.ToString ());
997+
if (sizeLimit > 0)
998+
proj.SetProperty ("_ZipFlushSizeLimit", (sizeLimit * 1024 * 1024).ToString ());
999+
using (var b = CreateApkBuilder ()) {
1000+
Assert.IsTrue (b.Build (proj), "Build should have succeeded.");
1001+
1002+
}
1003+
}
1004+
9651005
[Test]
9661006
public void ExtractNativeLibsTrue ()
9671007
{

src/Xamarin.Android.Build.Tasks/Utilities/ZipArchiveEx.cs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ namespace Xamarin.Android.Tasks
88
public class ZipArchiveEx : IDisposable
99
{
1010

11-
public static int ZipFlushSizeLimit = 50 * 1024 * 1024;
12-
public static int ZipFlushFilesLimit = 50;
11+
const int DEFAULT_FLUSH_SIZE_LIMIT = 100 * 1024 * 1024;
12+
const int DEFAULT_FLUSH_FILES_LIMIT = 512;
1313

1414
ZipArchive zip;
1515
string archive;
@@ -24,6 +24,9 @@ public ZipArchive Archive {
2424

2525
public bool CreateDirectoriesInZip { get; set; } = true;
2626

27+
public int ZipFlushSizeLimit { get; set; } = DEFAULT_FLUSH_SIZE_LIMIT;
28+
public int ZipFlushFilesLimit { get; set; } = DEFAULT_FLUSH_FILES_LIMIT;
29+
2730
public ZipArchiveEx (string archive) : this (archive, FileMode.CreateNew)
2831
{
2932
}
@@ -65,7 +68,31 @@ void AddFileAndFlush (string filename, long fileLength, string archiveFileName,
6568
{
6669
filesWrittenTotalSize += fileLength;
6770
zip.AddFile (filename, archiveFileName, compressionMethod: compressionMethod);
68-
if ((filesWrittenTotalSize >= ZipArchiveEx.ZipFlushSizeLimit || filesWrittenTotalCount >= ZipArchiveEx.ZipFlushFilesLimit) && AutoFlush) {
71+
if ((filesWrittenTotalSize >= ZipFlushSizeLimit || filesWrittenTotalCount >= ZipFlushFilesLimit) && AutoFlush) {
72+
Flush ();
73+
}
74+
}
75+
76+
public void AddFileAndFlush (string filename, string archiveFileName, CompressionMethod compressionMethod)
77+
{
78+
var fi = new FileInfo (filename);
79+
AddFileAndFlush (filename, fi.Length, archiveFileName, compressionMethod);
80+
}
81+
82+
public void AddEntryAndFlush (byte[] data, string archiveFileName)
83+
{
84+
filesWrittenTotalSize += data.Length;
85+
zip.AddEntry (data, archiveFileName);
86+
if ((filesWrittenTotalSize >= ZipFlushSizeLimit || filesWrittenTotalCount >= ZipFlushFilesLimit) && AutoFlush) {
87+
Flush ();
88+
}
89+
}
90+
91+
public void AddEntryAndFlush (string archiveFileName, Stream data, CompressionMethod method)
92+
{
93+
filesWrittenTotalSize += data.Length;
94+
zip.AddEntry (archiveFileName, data, method);
95+
if ((filesWrittenTotalSize >= ZipFlushSizeLimit || filesWrittenTotalCount >= ZipFlushFilesLimit) && AutoFlush) {
6996
Flush ();
7097
}
7198
}

src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2152,6 +2152,8 @@ because xbuild doesn't support framework reference assemblies.
21522152
CheckedBuild="$(_AndroidCheckedBuild)"
21532153
RuntimeConfigBinFilePath="$(_BinaryRuntimeConfigPath)"
21542154
ExcludeFiles="@(AndroidPackagingOptionsExclude)"
2155+
ZipFlushFilesLimit="$(_ZipFlushFilesLimit)"
2156+
ZipFlushSizeLimit="$(_ZipFlushSizeLimit)"
21552157
UseAssemblyStore="$(AndroidUseAssemblyStore)">
21562158
<Output TaskParameter="OutputFiles" ItemName="ApkFiles" />
21572159
</BuildApk>
@@ -2185,6 +2187,8 @@ because xbuild doesn't support framework reference assemblies.
21852187
CheckedBuild="$(_AndroidCheckedBuild)"
21862188
RuntimeConfigBinFilePath="$(_BinaryRuntimeConfigPath)"
21872189
ExcludeFiles="@(AndroidPackagingOptionsExclude)"
2190+
ZipFlushFilesLimit="$(_ZipFlushFilesLimit)"
2191+
ZipFlushSizeLimit="$(_ZipFlushSizeLimit)"
21882192
UseAssemblyStore="$(AndroidUseAssemblyStore)">
21892193
<Output TaskParameter="OutputFiles" ItemName="BaseZipFile" />
21902194
</BuildBaseAppBundle>

0 commit comments

Comments
 (0)