Skip to content

Fix BuildArchive updates for existing jar entries#11552

Open
Copilot wants to merge 8 commits into
mainfrom
copilot/fix-buildarchive-incremental-build-bug
Open

Fix BuildArchive updates for existing jar entries#11552
Copilot wants to merge 8 commits into
mainfrom
copilot/fix-buildarchive-incremental-build-bug

Conversation

Copilot AI commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

BuildArchive could remove or preserve stale Java/JAR resources from incremental APK publishes when the previous APK already contained those entries. Duplicate jar-entry adds were skipped, but existing output entries were not distinguished from duplicates added by current build inputs.

  • Archive retention and updates

    • Marks jar-sourced FilesToAddToArchive paths as retained before cleanup.
    • Keeps existing APK entries that are still part of the current build inputs.
    • Updates stale existing APK entries when the source jar entry contents change.
    • Preserves existing duplicate handling when a current packaged resource or earlier archive item already added the same path.
    • Preserves cleanup behavior for entries no longer present in FilesToAddToArchive.
  • Regression coverage

    • Adds focused BuildArchive tests for existing jar-sourced APK entries.
    • Verifies stale Java resource entries are replaced with current jar contents.
    • Verifies up-to-date Java resource entries are skipped.
    • Verifies duplicate current-build jar entries keep the first-added APK entry.
    • Verifies unrelated stale entries are still removed.

Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix incremental build bug for Java resources in APK Fix BuildArchive retention for existing jar entries Jun 1, 2026
Copilot AI requested a review from simonrozsival June 1, 2026 10:41
@simonrozsival

Copy link
Copy Markdown
Member

@copilot The current fix prevents deletion of existing jar-sourced entries, but it still preserves stale contents forever when the source jar entry changes.

Please update BuildArchive so jar-sourced entries distinguish between:

  1. entries that were present in the existing output APK and should be retained/updated, and
  2. entries already added by the current build inputs, where the existing duplicate semantics should still win.

A better shape is:

var wasExistingOutputEntry = existingEntries.Remove (apk_path);

using (var stream = File.OpenRead (jar_file_path))
using (var jar = ZipArchive.Open (stream)) {
	var jar_item = jar.ReadEntry (jar_entry_name);

	if (apk.ContainsEntry (apk_path)) {
		if (!wasExistingOutputEntry) {
			// Current packaged resource or earlier archive item wins.
			Log.LogDebugMessage ("Failed to add jar entry {0} from {1}: the same file already exists in the apk", jar_entry_name, Path.GetFileName (jar_file_path));
			continue;
		}

		if (apk.GetEntry (apk_path).CRC == jar_item.CRC) {
			Log.LogDebugMessage ("Skipping {0} from {1} as it is up to date.", jar_entry_name, jar_file_path);
			continue;
		}

		apk.DeleteEntry (apk_path);
	}

	// extract jar_item and add it as today
}

Then update the regression test so the existing APK entry has stale contents and the jar has current contents, and assert that the APK ends with the current contents. That proves the incremental bug is fixed rather than only hiding the deletion symptom. The test can still keep an unrelated stale entry to verify cleanup behavior.

Copilot AI and others added 2 commits June 1, 2026 12:01
Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>

Copilot AI commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

@copilot The current fix prevents deletion of existing jar-sourced entries, but it still preserves stale contents forever when the source jar entry changes.

Please update BuildArchive so jar-source...

Addressed in 95e7370 and 2c430e7. Jar-sourced entries now distinguish existing output entries from current-build duplicates, refresh stale existing APK contents from the jar, skip up-to-date CRC matches, and keep cleanup coverage for unrelated stale entries.

Copilot AI changed the title Fix BuildArchive retention for existing jar entries Fix BuildArchive updates for existing jar entries Jun 1, 2026
@simonrozsival

Copy link
Copy Markdown
Member

/review

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown

Android PR Reviewer completed successfully!

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot ⚠️ Needs Changes — The fix logic is correct and well-structured. A few items to address:

Issues (1 ⚠️, 3 💡):

  • ⚠️ Test field tempDirectory should be nullable (string?) for [SetUp]-initialized fields
  • 💡 New test file missing #nullable enable and file-scoped namespace
  • 💡 Minor perf trade-off: jar is now opened before the duplicate check (acceptable for correctness)
  • 💡 Consider a test for duplicate jar items targeting the same apk_path

Positive notes:

  • CRC comparison for staleness detection is consistent with the existing pattern used for ApkInputPath entries (line 120)
  • Good separation of the "existing output entry" vs "current build duplicate" cases
  • Regression tests cover both stale-update and up-to-date-skip paths

CI: Public checks pass. Internal Xamarin.Android-PR pipeline status should be verified separately.

Generated by Android PR Reviewer for issue #11552 · ● 7M

Comment on lines 162 to 163

using (var stream = File.OpenRead (jar_file_path))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 💡 Performance — The jar is now opened unconditionally, whereas previously the apk.ContainsEntry check was before File.OpenRead/ZipArchive.Open, allowing duplicates from current-build items to be skipped without touching the jar at all. This is probably fine in practice (OS file cache, rare duplicate path), but worth noting as a trade-off for correctness.

Rule: Place cheap checks before expensive ones

@@ -0,0 +1,96 @@
using System.Collections.Generic;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 💡 Formatting — New files should have #nullable enable at the top and use file-scoped namespaces (namespace Xamarin.Android.Build.Tests;). Several other test fixtures in this directory follow those conventions.

Rule: Nullable reference types / File-scoped namespaces

[TestFixture]
public class BuildArchiveTests
{
string tempDirectory;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 ⚠️ NullabletempDirectory is declared as non-nullable string but is only assigned in Setup(). Without #nullable enable this compiles, but the intent is unclear and fragile. Declare as string? tempDirectory; (consistent with the nullable pattern for [SetUp]-initialized fields per repo conventions).

Rule: Nullable reference types

archive.AssertDoesNotContainEntry (apk, "stale.txt");
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 💡 Testing — Consider adding a test for the case where multiple jar items target the same apk_path — this exercises the preserved duplicate-handling logic (!wasExistingOutputEntry branch). The current tests only cover the existing-output-entry path.

Rule: Test edge cases

@simonrozsival

Copy link
Copy Markdown
Member

@copilot address code review

Copilot AI and others added 2 commits June 2, 2026 12:54
Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>

Copilot AI commented Jun 2, 2026

Copy link
Copy Markdown
Contributor Author

@copilot address code review

Addressed in c1168b1 and 2e47ed5. Updated the duplicate jar-entry path to short-circuit before opening duplicate jars, applied nullable/file-scoped test conventions, and added duplicate current-build jar entry coverage.

@simonrozsival

Copy link
Copy Markdown
Member

/review

@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown

Android PR Reviewer failed. Please review the logs for details.

@simonrozsival

Copy link
Copy Markdown
Member

/review

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

Android PR Reviewer failed. Please review the logs for details.

@simonrozsival simonrozsival marked this pull request as ready for review June 4, 2026 08:44
Copilot AI review requested due to automatic review settings June 4, 2026 08:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves incremental APK publishing behavior in BuildArchive for files sourced from jar entries, ensuring that existing APK entries are treated as “retained” build outputs (not as duplicates) and are updated when stale.

Changes:

  • Update BuildArchive jar-entry handling to distinguish existing output entries from same-build duplicates and to replace stale existing entries when contents change (CRC differs).
  • Add focused unit tests covering jar-sourced entries that already exist in the output APK (update, skip when up-to-date, and duplicate handling).
Show a summary per file
File Description
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/BuildArchiveTests.cs Adds regression tests validating update/skip/duplicate behavior for existing jar-sourced APK entries.
src/Xamarin.Android.Build.Tasks/Tasks/BuildArchive.cs Adjusts jar-entry logic to retain existing entries, skip true duplicates, and replace stale entries based on CRC comparison.

Copilot's findings

  • Files reviewed: 2/2 changed files
  • Comments generated: 2

Comment thread src/Xamarin.Android.Build.Tasks/Tasks/BuildArchive.cs
@simonrozsival

Copy link
Copy Markdown
Member

@copilot address review comments

…disk in duplicate test

Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>

Copilot AI commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

@copilot address review comments

Both issues addressed in the latest commit:

  1. Added a null-check for jar.ReadEntry() — if the entry isn't found in the jar, we log a debug message and continue instead of dereferencing a null jar_item.
  2. secondJar is now created on disk (CreateArchive (secondJar, ...)) so the test setup accurately reflects its inputs and is robust to implementation changes.

All 3 BuildArchiveTests pass.

@simonrozsival

Copy link
Copy Markdown
Member

/review

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

Android PR Reviewer completed successfully!

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot

✅ LGTM — solid incremental build fix

CI: All checks green (dotnet-android ✅, Xamarin.Android-PR ✅, license/cla ✅).

Summary

The core logic change correctly distinguishes "entry from a previous build output" vs "entry added by another current-build item," fixing the bug where jar-sourced APK entries were incorrectly treated as duplicates and then deleted during cleanup on every incremental build. The CRC-based staleness check and delete-then-re-add pattern follows the existing established pattern in the same method (lines 117–133).

Positive callouts

  • 👍 The jar_item == null null check (line 173) is a nice defensive addition — the original code would have crashed with a NullReferenceException on jar_item.Extract() if the jar entry was missing.
  • 👍 Tests are well-structured, cover the three main scenarios (update, skip, duplicate), and use appropriate assertions including message verification.

Issues

Severity Count Category
💡 3 Cleanup edge case, log format consistency, test coverage

No blocking issues found — all three inline comments are optional suggestions.

Generated by Android PR Reviewer for issue #11552 · ● 17.3M

Comment thread src/Xamarin.Android.Build.Tasks/Tasks/BuildArchive.cs
Comment thread src/Xamarin.Android.Build.Tasks/Tasks/BuildArchive.cs
@simonrozsival

Copy link
Copy Markdown
Member

@copilot address code review

…anup on missing jar entry, add null-jar-entry test

Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>

Copilot AI commented Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

@copilot address code review

Addressed in the latest commit:

  1. Consistent log format — null-entry message now uses full jar_file_path (matching the "Skipping" and "Adding" messages on lines 182/199).
  2. Stale entry cleanup — when jar_item == null and wasExistingOutputEntry, apk_path is re-added to existingEntries so the cleanup loop removes the orphaned APK entry.
  3. New testMissingJarEntryIsSkippedAndExistingOutputEntryIsRemoved creates a jar that does not contain the requested entry, asserts the expected log message, and verifies the existing APK entry is removed.

@simonrozsival

Copy link
Copy Markdown
Member

/review

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

Android PR Reviewer completed successfully!

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilotLGTM — Clean, well-tested fix.

What the PR does: Fixes incremental BuildArchive behavior so that jar-sourced APK entries from previous builds are properly updated (or removed) instead of being incorrectly treated as "already added" duplicates.

Analysis:

  • The core logic change (existingEntries.Remove + CRC-based up-to-date check + DeleteEntry before re-add) is correct. All code paths converge correctly for stale, up-to-date, duplicate, and missing-entry scenarios.
  • IZipArchive.GetEntry throws on missing entries, so the unchecked call on line 181 is safe given the hasApkEntry guard.
  • The existingEntries re-add when jar_item == null correctly restores cleanup semantics — the stale APK entry will be removed during the end-of-task cleanup loop.
  • Test coverage is solid: 4 focused unit tests exercise the key incremental scenarios without needing a full build.

Issues: 0 ❌ · 0 ⚠️ · 2 💡

Severity File Summary
💡 BuildArchiveTests.cs Use [] collection expressions for ITaskItem[] array initialization (6 instances)
💡 BuildArchive.cs:181 Consider adding a brief comment explaining why CRC-only comparison is used here vs CRC+CompressedSize in the input-APK refresh path

CI: ✅ All checks passing.

Generated by Android PR Reviewer for issue #11552 · ● 30M

};

Assert.IsTrue (task.RunTask (), "task should have succeeded");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 💡 Formatting — This repo prefers [] collection expressions over new T[] { ... } (C# 12 syntax). Several places in this file can be simplified:

Suggested change
FilesToAddToArchive = [item],

Same applies to lines 79, 103, 116, 135, and 145.

Rule: Use [] not Array.Empty<T>() / new T[]

}

if (hasApkEntry) {
if (apk.GetEntry (apk_path).CRC == jar_item.CRC) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 💡 Documentation — The existing input-APK refresh check above (line 120) compares both CRC and CompressedSize, while this new path compares CRC only. CRC-only is actually correct here — CRC checksums the uncompressed data, so matching CRCs mean identical content regardless of compression settings. A brief inline comment explaining the intentional difference would help future readers who might wonder about the inconsistency.

Suggested change
if (apk.GetEntry (apk_path).CRC == jar_item.CRC) {
if (apk.GetEntry (apk_path).CRC == jar_item.CRC) { // CRC is computed on uncompressed data — matching CRC means identical content

Rule: Comments explain "why", not "what"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BuildArchive incremental build bug: Java resources alternate between present and missing in APK on consecutive publishes

3 participants