Skip to content

Commit 1c4727c

Browse files
authored
[Xamarin.Android.Build.Tasks] Allow BuildApk to run incrementally. (#3999)
* [Xamarin.Android.Build.Tasks] Allow BuildApk to run incrementally. The `BuildApk` task currently does not work incrementally. When it is called it throws away the previously built apk in favour of using the `base` on that `aapt/aapt2` just built. With incremental builds it is highly likely that only a few files have actually changed so we are wasting a ton of time. During my tests on a simple app `BuildApk` generally takes about 4 seconds to run. This is the same for both clean and incremental builds. With these changes, this time could now go down to 600 ms for an incremental build. Which is a decent improvement. The new system does not throw away all the work done in the previous build. Instead it uses the previous output apk as a base on which to produce the new one. The output from `aapt/aapt2` is instead `merged` into the current apk. We do this by checking the `CRC` values of each entry and only merging in files which have actually changed. We need to use the `CRC` here because `aapt/aapt2` is NEVER setting the `ModifiedDate` on the zip items :(. It seems to always be 01/01/1980. Once those changes are out of the way , we can now move on to our custom content. This time we can use the `ModifiedDate` of both the `ZipEntry` AND the source file. This allows us to, again, only update the items which have accutally changed. One final part is we need to keep track of what files are in the `apk` in the first place and which ones get added to it. This is what the `existingEntries` List is used for. This allows us to detect when files are REMOVED from the system. Otherwise we end up with stale files in the apk from previous builds. On my tests when changing a single AndroidResource file I got the following results 3664 ms BuildApk 1 calls (Before) 669 ms BuildApk 1 calls (After) Which is a decent improvement. * Update test Timings
1 parent e746703 commit 1c4727c

File tree

7 files changed

+270
-56
lines changed

7 files changed

+270
-56
lines changed

Configuration.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@
108108
<_TestsProfiledAotName Condition=" '$(AndroidEnableProfiledAot)' == 'true' ">-Profiled</_TestsProfiledAotName>
109109
<_TestsBundleName Condition=" '$(BundleAssemblies)' == 'true' ">-Bundle</_TestsBundleName>
110110
<TestsFlavor>$(_TestsProfiledAotName)$(_TestsAotName)$(_TestsBundleName)</TestsFlavor>
111-
<LibZipSharpVersion>1.0.7</LibZipSharpVersion>
111+
<LibZipSharpVersion>1.0.8</LibZipSharpVersion>
112112
</PropertyGroup>
113113
<PropertyGroup>
114114
<MingwCommandPrefix32>i686-w64-mingw32</MingwCommandPrefix32>

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

Lines changed: 111 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
using Java.Interop.Tools.Cecil;
1515

16-
using ArchiveFileList = System.Collections.Generic.List<System.Tuple<string, string>>;
16+
using ArchiveFileList = System.Collections.Generic.List<(string filePath, string archivePath)>;
1717
using Mono.Cecil;
1818
using Xamarin.Android.Tools;
1919
using Xamarin.Tools.Zip;
@@ -109,22 +109,61 @@ bool _Debug {
109109

110110
protected virtual void FixupArchive (ZipArchiveEx zip) { }
111111

112+
List<string> existingEntries = new List<string> ();
113+
112114
void ExecuteWithAbi (string [] supportedAbis, string apkInputPath, string apkOutputPath)
113115
{
114-
var temp = apkOutputPath + "new";
115116
ArchiveFileList files = new ArchiveFileList ();
116-
if (apkInputPath != null)
117-
File.Copy (apkInputPath, temp, overwrite: true);
117+
bool refresh = true;
118+
if (apkInputPath != null && File.Exists (apkInputPath) && !File.Exists (apkOutputPath)) {
119+
Log.LogDebugMessage ($"Copying {apkInputPath} to {apkInputPath}");
120+
File.Copy (apkInputPath, apkOutputPath, overwrite: true);
121+
refresh = false;
122+
}
118123
using (var notice = Assembly.GetExecutingAssembly ().GetManifestResourceStream ("NOTICE.txt"))
119-
using (var apk = new ZipArchiveEx (temp, apkInputPath != null ? FileMode.Open : FileMode.Create )) {
124+
using (var apk = new ZipArchiveEx (apkOutputPath, File.Exists (apkOutputPath) ? FileMode.Open : FileMode.Create )) {
125+
if (refresh) {
126+
for (long i = 0; i < apk.Archive.EntryCount; i++) {
127+
ZipEntry e = apk.Archive.ReadEntry ((ulong) i);
128+
Log.LogDebugMessage ($"Registering item {e.FullName}");
129+
existingEntries.Add (e.FullName);
130+
}
131+
}
132+
if (apkInputPath != null && File.Exists (apkInputPath) && refresh) {
133+
var lastWriteOutput = File.Exists (apkOutputPath) ? File.GetLastWriteTimeUtc (apkOutputPath) : DateTime.MinValue;
134+
var lastWriteInput = File.GetLastWriteTimeUtc (apkInputPath);
135+
using (var packaged = new ZipArchiveEx (apkInputPath, FileMode.Open)) {
136+
foreach (var entry in packaged.Archive) {
137+
Log.LogDebugMessage ($"Deregistering item {entry.FullName}");
138+
existingEntries.Remove (entry.FullName);
139+
if (lastWriteInput <= lastWriteOutput)
140+
continue;
141+
if (apk.Archive.ContainsEntry (entry.FullName)) {
142+
ZipEntry e = apk.Archive.ReadEntry (entry.FullName);
143+
// check the CRC values as the ModifiedDate is always 01/01/1980 in the aapt generated file.
144+
if (entry.CRC == e.CRC) {
145+
Log.LogDebugMessage ($"Skipping {entry.FullName} from {apkInputPath} as its up to date.");
146+
continue;
147+
}
148+
}
149+
var ms = new MemoryStream ();
150+
entry.Extract (ms);
151+
Log.LogDebugMessage ($"Refreshing {entry.FullName} from {apkInputPath}");
152+
apk.Archive.AddStream (ms, entry.FullName, compressionMethod: entry.CompressionMethod);
153+
}
154+
}
155+
}
120156
apk.FixupWindowsPathSeparators ((a, b) => Log.LogDebugMessage ($"Fixing up malformed entry `{a}` -> `{b}`"));
121-
apk.Archive.AddEntry (RootPath + "NOTICE", notice);
157+
string noticeName = RootPath + "NOTICE";
158+
existingEntries.Remove (noticeName);
159+
if (!apk.Archive.ContainsEntry (noticeName))
160+
apk.Archive.AddEntry (noticeName, notice);
122161

123162
// Add classes.dx
124163
foreach (var dex in DalvikClasses) {
125164
string apkName = dex.GetMetadata ("ApkName");
126165
string dexPath = string.IsNullOrWhiteSpace (apkName) ? Path.GetFileName (dex.ItemSpec) : apkName;
127-
apk.Archive.AddFile (dex.ItemSpec, DalvikPath + dexPath);
166+
AddFileToArchiveIfNewer (apk, dex.ItemSpec, DalvikPath + dexPath);
128167
}
129168

130169
if (EmbedAssemblies && !BundleAssemblies)
@@ -133,25 +172,25 @@ void ExecuteWithAbi (string [] supportedAbis, string apkInputPath, string apkOut
133172
AddRuntimeLibraries (apk, supportedAbis);
134173
apk.Flush();
135174
AddNativeLibraries (files, supportedAbis);
136-
apk.Flush();
137175
AddAdditionalNativeLibraries (files, supportedAbis);
138-
apk.Flush();
139176

140177
if (TypeMappings != null) {
141178
foreach (ITaskItem typemap in TypeMappings) {
142-
apk.Archive.AddFile (typemap.ItemSpec, RootPath + Path.GetFileName(typemap.ItemSpec), compressionMethod: UncompressedMethod);
179+
AddFileToArchiveIfNewer (apk, typemap.ItemSpec, RootPath + Path.GetFileName(typemap.ItemSpec), compressionMethod: UncompressedMethod);
143180
}
144181
}
145182

146183
int count = 0;
147184
foreach (var file in files) {
148-
var item = Path.Combine (file.Item2, Path.GetFileName (file.Item1))
185+
var item = Path.Combine (file.archivePath, Path.GetFileName (file.filePath))
149186
.Replace (Path.DirectorySeparatorChar, '/');
150-
if (apk.Archive.ContainsEntry (item)) {
151-
Log.LogCodedWarning ("XA4301", file.Item1, 0, "Apk already contains the item {0}; ignoring.", item);
187+
existingEntries.Remove (item);
188+
if (apk.SkipExistingFile (file.filePath, item)) {
189+
Log.LogDebugMessage ($"Skipping {file.filePath} as the archive file is up to date.");
152190
continue;
153191
}
154-
apk.Archive.AddFile (file.Item1, item, compressionMethod: GetCompressionMethod (file.Item1));
192+
Log.LogDebugMessage ("\tAdding {0}", file.filePath);
193+
apk.Archive.AddFile (file.filePath, item, compressionMethod: GetCompressionMethod (file.filePath));
155194
count++;
156195
if (count == ZipArchiveEx.ZipFlushLimit) {
157196
apk.Flush();
@@ -181,16 +220,19 @@ void ExecuteWithAbi (string [] supportedAbis, string apkInputPath, string apkOut
181220
if (!PackagingUtils.CheckEntryForPackaging (name)) {
182221
continue;
183222
}
223+
var path = RootPath + name;
224+
existingEntries.Remove (path);
225+
if (apk.SkipExistingEntry (jarItem, path)) {
226+
Log.LogDebugMessage ($"Skipping {path} as the archive file is up to date.");
227+
continue;
228+
}
184229
byte [] data;
185-
using (var d = new System.IO.MemoryStream ()) {
230+
using (var d = new MemoryStream ()) {
186231
jarItem.Extract (d);
187232
data = d.ToArray ();
188233
}
189-
var path = RootPath + name;
190-
if (apk.Archive.Any (e => e.FullName == path))
191-
Log.LogMessage ("Warning: failed to add jar entry {0} from {1}: the same file already exists in the apk", name, Path.GetFileName (jarFile));
192-
else
193-
apk.Archive.AddEntry (data, path);
234+
Log.LogDebugMessage ($"Adding {path} as the archive file is out of date.");
235+
apk.Archive.AddEntry (data, path);
194236
}
195237
}
196238
count++;
@@ -199,14 +241,14 @@ void ExecuteWithAbi (string [] supportedAbis, string apkInputPath, string apkOut
199241
count = 0;
200242
}
201243
}
244+
// Clean up Removed files.
245+
foreach (var entry in existingEntries) {
246+
Log.LogDebugMessage ($"Removing {entry} as it is not longer required.");
247+
apk.Archive.DeleteEntry (entry);
248+
}
249+
apk.Flush ();
202250
FixupArchive (apk);
203251
}
204-
if (MonoAndroidHelper.CopyIfZipChanged (temp, apkOutputPath)) {
205-
Log.LogDebugMessage ($"Copied {temp} to {apkOutputPath}");
206-
} else {
207-
Log.LogDebugMessage ($"Skipped {apkOutputPath}: up to date");
208-
}
209-
File.Delete (temp);
210252
}
211253

212254
public override bool RunTask ()
@@ -220,10 +262,12 @@ public override bool RunTask ()
220262
var outputFiles = new List<string> ();
221263
uncompressedFileExtensions = UncompressedFileExtensions?.Split (new char [] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries) ?? new string [0];
222264

265+
existingEntries.Clear ();
223266
ExecuteWithAbi (SupportedAbis, ApkInputPath, ApkOutputPath);
224267
outputFiles.Add (ApkOutputPath);
225268
if (CreatePackagePerAbi && SupportedAbis.Length > 1) {
226269
foreach (var abi in SupportedAbis) {
270+
existingEntries.Clear ();
227271
var path = Path.GetDirectoryName (ApkOutputPath);
228272
var apk = Path.GetFileNameWithoutExtension (ApkOutputPath);
229273
ExecuteWithAbi (new [] { abi }, String.Format ("{0}-{1}", ApkInputPath, abi),
@@ -254,7 +298,7 @@ private void AddAssemblies (ZipArchiveEx apk)
254298
Log.LogCodedWarning ("XA0107", assembly.ItemSpec, 0, "{0} is a Reference Assembly!", assembly.ItemSpec);
255299
}
256300
// Add assembly
257-
apk.Archive.AddFile (assembly.ItemSpec, GetTargetDirectory (assembly.ItemSpec) + "/" + Path.GetFileName (assembly.ItemSpec), compressionMethod: UncompressedMethod);
301+
AddFileToArchiveIfNewer (apk, assembly.ItemSpec, GetTargetDirectory (assembly.ItemSpec) + "/" + Path.GetFileName (assembly.ItemSpec), compressionMethod: UncompressedMethod);
258302

259303
// Try to add config if exists
260304
var config = Path.ChangeExtension (assembly.ItemSpec, "dll.config");
@@ -265,12 +309,12 @@ private void AddAssemblies (ZipArchiveEx apk)
265309
var symbols = Path.ChangeExtension (assembly.ItemSpec, "dll.mdb");
266310

267311
if (File.Exists (symbols))
268-
apk.Archive.AddFile (symbols, AssembliesPath + Path.GetFileName (symbols), compressionMethod: UncompressedMethod);
312+
AddFileToArchiveIfNewer (apk, symbols, AssembliesPath + Path.GetFileName (symbols), compressionMethod: UncompressedMethod);
269313

270314
symbols = Path.ChangeExtension (assembly.ItemSpec, "pdb");
271315

272316
if (File.Exists (symbols))
273-
apk.Archive.AddFile (symbols, AssembliesPath + Path.GetFileName (symbols), compressionMethod: UncompressedMethod);
317+
AddFileToArchiveIfNewer (apk, symbols, AssembliesPath + Path.GetFileName (symbols), compressionMethod: UncompressedMethod);
274318
}
275319
count++;
276320
if (count == ZipArchiveEx.ZipFlushLimit) {
@@ -292,20 +336,20 @@ private void AddAssemblies (ZipArchiveEx apk)
292336
if (MonoAndroidHelper.IsReferenceAssembly (assembly.ItemSpec)) {
293337
Log.LogCodedWarning ("XA0107", assembly.ItemSpec, 0, "{0} is a Reference Assembly!", assembly.ItemSpec);
294338
}
295-
apk.Archive.AddFile (assembly.ItemSpec, AssembliesPath + Path.GetFileName (assembly.ItemSpec), compressionMethod: UncompressedMethod);
339+
AddFileToArchiveIfNewer (apk, assembly.ItemSpec, AssembliesPath + Path.GetFileName (assembly.ItemSpec), compressionMethod: UncompressedMethod);
296340
var config = Path.ChangeExtension (assembly.ItemSpec, "dll.config");
297341
AddAssemblyConfigEntry (apk, config);
298342
// Try to add symbols if Debug
299343
if (debug) {
300344
var symbols = Path.ChangeExtension (assembly.ItemSpec, "dll.mdb");
301345

302346
if (File.Exists (symbols))
303-
apk.Archive.AddFile (symbols, AssembliesPath + Path.GetFileName (symbols), compressionMethod: UncompressedMethod);
347+
AddFileToArchiveIfNewer (apk, symbols, AssembliesPath + Path.GetFileName (symbols), compressionMethod: UncompressedMethod);
304348

305349
symbols = Path.ChangeExtension (assembly.ItemSpec, "pdb");
306350

307351
if (File.Exists (symbols))
308-
apk.Archive.AddFile (symbols, AssembliesPath + Path.GetFileName (symbols), compressionMethod: UncompressedMethod);
352+
AddFileToArchiveIfNewer (apk, symbols, AssembliesPath + Path.GetFileName (symbols), compressionMethod: UncompressedMethod);
309353
}
310354
count++;
311355
if (count == ZipArchiveEx.ZipFlushLimit) {
@@ -315,17 +359,38 @@ private void AddAssemblies (ZipArchiveEx apk)
315359
}
316360
}
317361

362+
bool AddFileToArchiveIfNewer (ZipArchiveEx apk, string file, string inArchivePath, CompressionMethod compressionMethod = CompressionMethod.Default)
363+
{
364+
existingEntries.Remove (inArchivePath);
365+
if (apk.SkipExistingFile (file, inArchivePath)) {
366+
Log.LogDebugMessage ($"Skipping {file} as the archive file is up to date.");
367+
return false;
368+
}
369+
Log.LogDebugMessage ($"Adding {file} as the archive file is out of date.");
370+
apk.Archive.AddFile (file, inArchivePath, compressionMethod: compressionMethod);
371+
return true;
372+
}
373+
318374
void AddAssemblyConfigEntry (ZipArchiveEx apk, string configFile)
319375
{
376+
string inArchivePath = AssembliesPath + Path.GetFileName (configFile);
377+
existingEntries.Remove (inArchivePath);
378+
320379
if (!File.Exists (configFile))
321380
return;
322381

382+
if (apk.SkipExistingFile (configFile, inArchivePath)) {
383+
Log.LogDebugMessage ($"Skipping {configFile} as the archive file is up to date.");
384+
return;
385+
}
386+
387+
Log.LogDebugMessage ($"Adding {configFile} as the archive file is out of date.");
323388
using (var source = File.OpenRead (configFile)) {
324389
var dest = new MemoryStream ();
325390
source.CopyTo (dest);
326391
dest.WriteByte (0);
327392
dest.Position = 0;
328-
apk.Archive.AddEntry (AssembliesPath + Path.GetFileName (configFile), dest, compressionMethod: UncompressedMethod);
393+
apk.Archive.AddEntry (inArchivePath, dest, compressionMethod: UncompressedMethod);
329394
}
330395
}
331396

@@ -380,6 +445,11 @@ CompressionMethod GetCompressionMethod (string fileName)
380445
void AddNativeLibraryToArchive (ZipArchiveEx apk, string abi, string filesystemPath, string inArchiveFileName)
381446
{
382447
string archivePath = $"lib/{abi}/{inArchiveFileName}";
448+
existingEntries.Remove (archivePath);
449+
if (apk.SkipExistingFile (filesystemPath, archivePath)) {
450+
Log.LogDebugMessage ($"Skipping {filesystemPath} (APK path: {archivePath}) as it is up to date.");
451+
return;
452+
}
383453
Log.LogDebugMessage ($"Adding native library: {filesystemPath} (APK path: {archivePath})");
384454
apk.Archive.AddEntry (archivePath, File.OpenRead (filesystemPath), compressionMethod: GetCompressionMethod (archivePath));
385455
}
@@ -484,8 +554,14 @@ private void AddAdditionalNativeLibraries (ArchiveFileList files, string [] supp
484554

485555
void AddNativeLibrary (ArchiveFileList files, string path, string abi)
486556
{
487-
Log.LogMessage (MessageImportance.Low, "\tAdding {0}", path);
488-
files.Add (new Tuple<string, string> (path, string.Format ("lib/{0}", abi)));
557+
var item = (filePath: path, archivePath: $"lib/{abi}");
558+
string filename = "/" + Path.GetFileName (item.filePath);
559+
string inArchivePath = item.archivePath + filename;
560+
if (files.Any (x => (x.archivePath + "/" + Path.GetFileName(x.filePath)) == inArchivePath)) {
561+
Log.LogCodedWarning ("XA4301", path, 0, $"Apk already contains the item {inArchivePath}; ignoring.");
562+
return;
563+
}
564+
files.Add (item);
489565
}
490566
}
491567
}

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,6 +1085,12 @@ public string GetFoo () {
10851085
<color name=""theme_devicedefault_background"">#ffffffff</color>
10861086
</resources>",
10871087
};
1088+
var raw = new AndroidItem.AndroidResource ("Resources\\raw\\test.txt") {
1089+
TextContent = () => @"Test Build 1",
1090+
};
1091+
var rawToDelete = new AndroidItem.AndroidResource ("Resources\\raw\\test2.txt") {
1092+
TextContent = () => @"Test Raw To Delete",
1093+
};
10881094
var libProj = new XamarinAndroidLibraryProject () {
10891095
IsRelease = true,
10901096
ProjectName = "Lib1",
@@ -1093,6 +1099,8 @@ public string GetFoo () {
10931099
},
10941100
AndroidResources = {
10951101
theme,
1102+
raw,
1103+
rawToDelete,
10961104
},
10971105
};
10981106
var appProj = new XamarinAndroidApplicationProject () {
@@ -1112,27 +1120,52 @@ public string GetFoo () {
11121120
foo.Timestamp = DateTimeOffset.UtcNow;
11131121
Assert.IsTrue (libBuilder.Build (libProj, doNotCleanupOnUpdate: true, saveProject: false), "Library project should have built");
11141122
Assert.IsTrue (libBuilder.Output.IsTargetSkipped ("_CreateManagedLibraryResourceArchive"), "_CreateManagedLibraryResourceArchive should be skipped.");
1123+
appBuilder.BuildLogFile = "build1.log";
11151124
Assert.IsTrue (appBuilder.Build (appProj, doNotCleanupOnUpdate: true, saveProject: false), "Application Build should have succeeded.");
11161125
Assert.IsTrue (appBuilder.Output.IsTargetSkipped ("_UpdateAndroidResgen"), "_UpdateAndroidResgen target should be skipped.");
1126+
// Check Contents of the file in the apk are correct.
1127+
string apk = Path.Combine (Root, appBuilder.ProjectDirectory, appProj.OutputPath, appProj.PackageName + "-Signed.apk");
1128+
byte[] rawContentBuildOne = ZipHelper.ReadFileFromZip (apk,
1129+
"res/raw/test.txt");
1130+
1131+
Assert.IsNotNull (rawContentBuildOne, "res/raw/test.txt should have been in the apk ");
1132+
string txt = Encoding.UTF8.GetString (rawContentBuildOne ?? new byte[0]);
1133+
StringAssert.Contains ("Test Build 1", txt, $"res/raw/test.txt should have been 'Test Build 1' not {txt}");
1134+
Assert.IsNotNull (ZipHelper.ReadFileFromZip (apk, "res/raw/test2.txt"), "res/raw/test2.txt should have been in the apk.");
11171135
theme.TextContent = () => @"<?xml version=""1.0"" encoding=""utf-8""?>
11181136
<resources>
11191137
<color name=""theme_devicedefault_background"">#00000000</color>
11201138
<color name=""theme_devicedefault_background2"">#ffffffff</color>
11211139
</resources>";
11221140
theme.Timestamp = DateTimeOffset.UtcNow;
1141+
raw.TextContent = () => @"Test Build 2 Now";
1142+
raw.Timestamp = DateTimeOffset.UtcNow;
11231143
Assert.IsTrue (libBuilder.Build (libProj, doNotCleanupOnUpdate: true, saveProject: false), "Library project should have built");
11241144
Assert.IsFalse (libBuilder.Output.IsTargetSkipped ("_CreateManagedLibraryResourceArchive"), "_CreateManagedLibraryResourceArchive should not be skipped.");
1145+
appBuilder.BuildLogFile = "build2.log";
11251146
Assert.IsTrue (appBuilder.Build (appProj, doNotCleanupOnUpdate: true, saveProject: false), "Application Build should have succeeded.");
11261147
string text = File.ReadAllText (Path.Combine (Root, path, appProj.ProjectName, "Resources", "Resource.designer.cs"));
11271148
Assert.IsTrue (text.Contains ("theme_devicedefault_background2"), "Resource.designer.cs was not updated.");
11281149
Assert.IsFalse (appBuilder.Output.IsTargetSkipped ("_UpdateAndroidResgen"), "_UpdateAndroidResgen target should NOT be skipped.");
1150+
Assert.IsFalse (appBuilder.Output.IsTargetSkipped ("_CreateBaseApk"), "_CreateBaseApk target should NOT be skipped.");
1151+
Assert.IsFalse (appBuilder.Output.IsTargetSkipped ("_BuildApkEmbed"), "_BuildApkEmbed target should NOT be skipped.");
1152+
byte[] rawContentBuildTwo = ZipHelper.ReadFileFromZip (apk,
1153+
"res/raw/test.txt");
1154+
Assert.IsNotNull (rawContentBuildTwo, "res/raw/test.txt should have been in the apk ");
1155+
txt = Encoding.UTF8.GetString (rawContentBuildTwo ?? new byte[0]);
1156+
StringAssert.Contains ("Test Build 2 Now", txt, $"res/raw/test.txt should have been 'Test Build 2' not {txt}");
1157+
rawToDelete.Deleted = true;
1158+
rawToDelete.Timestamp = DateTimeOffset.UtcNow;
11291159
theme.Deleted = true;
11301160
theme.Timestamp = DateTimeOffset.UtcNow;
11311161
Assert.IsTrue (libBuilder.Build (libProj, saveProject: true), "Library project should have built");
11321162
var themeFile = Path.Combine (Root, path, libProj.ProjectName, libProj.IntermediateOutputPath, "res", "values", "theme.xml");
11331163
Assert.IsTrue (!File.Exists (themeFile), $"{themeFile} should have been deleted.");
11341164
var archive = Path.Combine (Root, path, libProj.ProjectName, libProj.IntermediateOutputPath, "__AndroidLibraryProjects__.zip");
11351165
Assert.IsNull (ZipHelper.ReadFileFromZip (archive, "res/values/theme.xml"), "res/values/theme.xml should have been removed from __AndroidLibraryProjects__.zip");
1166+
appBuilder.BuildLogFile = "build3.log";
1167+
Assert.IsTrue (appBuilder.Build (appProj, doNotCleanupOnUpdate: true, saveProject: false), "Application Build should have succeeded.");
1168+
Assert.IsNull (ZipHelper.ReadFileFromZip (apk, "res/raw/test2.txt"), "res/raw/test2.txt should have been removed from the apk.");
11361169
}
11371170
}
11381171
}

0 commit comments

Comments
 (0)