diff --git a/README.fork.md b/README.fork.md index 65d1a02031f..957a9ae1f34 100644 --- a/README.fork.md +++ b/README.fork.md @@ -16,6 +16,7 @@ If no version is specified, it defaults to `11.9.1`. You can also pass a build c The build produces the following NuGet packages in `forked_nugets/`: +- `Avalonia..nupkg` (repackaged official meta-package with patched Avalonia.Base.dll) - `Avalonia.Android..nupkg` - `Avalonia.iOS..nupkg` - `Avalonia.OpenGL..nupkg` diff --git a/build-fork-nugets.sh b/build-fork-nugets.sh index 3071b51dd8d..93ef2c25c80 100755 --- a/build-fork-nugets.sh +++ b/build-fork-nugets.sh @@ -16,7 +16,135 @@ echo "============================================" rm -rf "$OUTPUT_DIR" mkdir -p "$OUTPUT_DIR" -# Pack order: OpenGL/Skia first (no fork-to-fork deps), then Android/iOS +# Step 1: Build Avalonia.Base first (we need the patched DLLs) +echo ">> Building Avalonia.Base ..." +dotnet pack "$SCRIPT_DIR/src/Avalonia.Base/Avalonia.Base.csproj" \ + -c "$CONFIGURATION" \ + -p:ForkVersion="$FORK_VERSION" \ + -o "$OUTPUT_DIR" +echo "" + +# Step 2: Repackage the official Avalonia meta-package with our patched Avalonia.Base.dll. +# +# The official Avalonia nupkg bundles Avalonia.Base.dll (via Numerge). +# We replace those DLLs with our patched build and re-version the package +# so consumers get the fix without conflicting copies. +NUGET_CACHE="$HOME/.nuget/packages" +OFFICIAL_NUPKG="$NUGET_CACHE/avalonia/$BASE_VERSION/avalonia.$BASE_VERSION.nupkg" + +if [ ! -f "$OFFICIAL_NUPKG" ]; then + echo ">> Official Avalonia $BASE_VERSION not found in NuGet cache. Downloading..." + mkdir -p "$NUGET_CACHE/avalonia/$BASE_VERSION" + curl -sSL -o "$OFFICIAL_NUPKG" \ + "https://api.nuget.org/v3-flatcontainer/avalonia/$BASE_VERSION/avalonia.$BASE_VERSION.nupkg" + if [ ! -f "$OFFICIAL_NUPKG" ]; then + echo " ERROR: Failed to download Avalonia $BASE_VERSION from nuget.org" + exit 1 + fi + echo " Downloaded to $OFFICIAL_NUPKG" +fi + +echo ">> Repackaging official Avalonia $BASE_VERSION with patched Avalonia.Base DLLs..." + +# Built DLLs from step 1 +BASE_BIN="$SCRIPT_DIR/src/Avalonia.Base/bin/$CONFIGURATION" + +python3 - "$OFFICIAL_NUPKG" "$BASE_BIN" "$OUTPUT_DIR" "$BASE_VERSION" "$FORK_VERSION" <<'PYEOF' +import sys, os, zipfile, tempfile, shutil +import xml.etree.ElementTree as ET + +official_nupkg = sys.argv[1] +base_bin = sys.argv[2] +output_dir = sys.argv[3] +base_version = sys.argv[4] +fork_version = sys.argv[5] + +# Map TFM folders in the nupkg to our build output directories +TFM_MAP = { + "net8.0": "net8.0", + "net6.0": "net6.0", + "netstandard2.0": "netstandard2.0", +} + +with tempfile.TemporaryDirectory() as tmpdir: + # Extract official package + with zipfile.ZipFile(official_nupkg, 'r') as z: + z.extractall(tmpdir) + + # Replace Avalonia.Base.dll in lib/ and ref/ for each TFM + for nupkg_tfm, build_tfm in TFM_MAP.items(): + built_dll = os.path.join(base_bin, build_tfm, "Avalonia.Base.dll") + built_xml = os.path.join(base_bin, build_tfm, "Avalonia.Base.xml") + + if not os.path.exists(built_dll): + print(f" WARNING: {built_dll} not found, skipping {nupkg_tfm}") + continue + + for prefix in ["lib", "ref"]: + target_dll = os.path.join(tmpdir, prefix, nupkg_tfm, "Avalonia.Base.dll") + target_xml = os.path.join(tmpdir, prefix, nupkg_tfm, "Avalonia.Base.xml") + + if os.path.exists(target_dll): + shutil.copy2(built_dll, target_dll) + print(f" Replaced {prefix}/{nupkg_tfm}/Avalonia.Base.dll") + + if os.path.exists(target_xml) and os.path.exists(built_xml): + shutil.copy2(built_xml, target_xml) + print(f" Replaced {prefix}/{nupkg_tfm}/Avalonia.Base.xml") + + # Update version in nuspec + nuspec_files = [f for f in os.listdir(tmpdir) if f.endswith(".nuspec")] + if nuspec_files: + nuspec_path = os.path.join(tmpdir, nuspec_files[0]) + tree = ET.parse(nuspec_path) + root = tree.getroot() + + # Find namespace + ns = "" + for uri in [ + "http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd", + "http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd", + "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd", + ]: + if root.find(f"{{{uri}}}metadata") is not None: + ns = uri + break + + if ns: + ET.register_namespace("", ns) + metadata = root.find(f"{{{ns}}}metadata") + version_el = metadata.find(f"{{{ns}}}version") + if version_el is not None: + version_el.text = fork_version + print(f" Version: {base_version} -> {fork_version}") + + tree.write(nuspec_path, xml_declaration=True, encoding="utf-8") + + # Remove [Content_Types].xml signature files that NuGet will regenerate + for f in [".signature.p7s"]: + p = os.path.join(tmpdir, "package", f) + if os.path.exists(p): + os.remove(p) + + # Repackage + out_nupkg = os.path.join(output_dir, f"Avalonia.{fork_version}.nupkg") + with zipfile.ZipFile(out_nupkg, 'w', zipfile.ZIP_DEFLATED) as zout: + for dirpath, dirnames, filenames in os.walk(tmpdir): + for fn in filenames: + full = os.path.join(dirpath, fn) + arcname = os.path.relpath(full, tmpdir) + zout.write(full, arcname) + print(f" Created {os.path.basename(out_nupkg)}") +PYEOF + +echo "" + +# Remove the standalone Avalonia.Base nupkg — it's now inside the Avalonia meta-package +rm -f "$OUTPUT_DIR"/Avalonia.Base.*.nupkg +echo ">> Removed standalone Avalonia.Base nupkg (now bundled in Avalonia meta-package)" +echo "" + +# Step 3: Pack the remaining forked projects PROJECTS=( "src/Avalonia.OpenGL/Avalonia.OpenGL.csproj" "src/Skia/Avalonia.Skia/Avalonia.Skia.csproj" @@ -37,14 +165,14 @@ done # # The official Avalonia build uses Numerge to merge sub-packages into the # Avalonia meta-package. These sub-packages don't exist on nuget.org: -# Avalonia.Base, Avalonia.Controls, Avalonia.DesignerSupport, Avalonia.OpenGL, +# Avalonia.Controls, Avalonia.DesignerSupport, Avalonia.OpenGL, # Avalonia.Metal, Avalonia.Vulkan, Avalonia.Dialogs, Avalonia.Markup, # Avalonia.Markup.Xaml, Avalonia.MicroCom # # Our dotnet pack creates deps on these non-existent packages. We fix this by: -# 1. Removing deps on merged/bundled packages -# 2. Ensuring a dep on Avalonia >= base_version exists (it provides those DLLs) -# 3. Pinning Avalonia dep to base_version (not fork_version) +# 1. Removing deps on merged/bundled packages (they're inside the Avalonia meta-package) +# 2. Rewriting Avalonia deps to point to our fork version (since we now ship a forked Avalonia nupkg) +# 3. Removing deps on Avalonia.Base (now bundled in our forked Avalonia meta-package) echo ">> Post-processing nupkgs..." @@ -82,6 +210,11 @@ def find_ns(root): for fname in sorted(os.listdir(output_dir)): if not fname.endswith(".nupkg"): continue + # Skip the repackaged Avalonia meta-package — already handled + # Its filename is "Avalonia..nupkg" (no sub-name like .Android, .Skia, etc.) + pkg_name = fname.rsplit("." + fork_version, 1)[0] if ("." + fork_version) in fname else fname + if pkg_name == "Avalonia": + continue nupkg_path = os.path.join(output_dir, fname) with tempfile.TemporaryDirectory() as tmpdir: @@ -118,11 +251,11 @@ for fname in sorted(os.listdir(output_dir)): if dep_id == "Avalonia": has_avalonia_dep = True - # Pin to base version (official meta-package on nuget.org) - if dep_ver == fork_version: - dep.set("version", base_version) + # Point to our forked Avalonia meta-package + if dep_ver != fork_version: + dep.set("version", fork_version) modified = True - print(f" {pkg_id}: Avalonia dep {fork_version} -> {base_version}") + print(f" {pkg_id}: Avalonia dep {dep_ver} -> {fork_version}") elif dep_id in MERGED: # Remove deps on packages bundled in the Avalonia meta-package @@ -144,10 +277,10 @@ for fname in sorted(os.listdir(output_dir)): if needs_avalonia_dep and not has_avalonia_dep: new_dep = ET.SubElement(group, f"{{{ns}}}dependency") new_dep.set("id", "Avalonia") - new_dep.set("version", base_version) + new_dep.set("version", fork_version) new_dep.set("exclude", "Build,Analyzers") modified = True - print(f" {pkg_id}: added dep on Avalonia {base_version}") + print(f" {pkg_id}: added dep on Avalonia {fork_version}") if modified: tree.write(nuspec_path, xml_declaration=True, encoding="utf-8") @@ -169,4 +302,5 @@ ls -1 "$OUTPUT_DIR"/*.nupkg 2>/dev/null echo "" echo "Copy to your local feed and clean stale cache:" echo " cp $OUTPUT_DIR/*.nupkg /opt/dev/local_nugets/" -echo " rm -rf ~/.nuget/packages/avalonia*/11.9.1" +echo " rm -rf ~/.nuget/packages/avalonia/$FORK_VERSION" +echo " rm -rf ~/.nuget/packages/avalonia.*/$FORK_VERSION" diff --git a/build/ForkOverride.targets b/build/ForkOverride.targets index 3733373dccb..97b42aea5fc 100644 --- a/build/ForkOverride.targets +++ b/build/ForkOverride.targets @@ -1,13 +1,14 @@ + <_IsForkProject Condition="'$(MSBuildProjectName)' == 'Avalonia.Base'">true <_IsForkProject Condition="'$(MSBuildProjectName)' == 'Avalonia.Skia'">true <_IsForkProject Condition="'$(MSBuildProjectName)' == 'Avalonia.OpenGL'">true <_IsForkProject Condition="'$(MSBuildProjectName)' == 'Avalonia.Android'">true