tinymux/dowin32.sh
Stephen Dennis 44c374ee5a Prepare 2.14.0.11 ALPHA release
Version bump across the three files that carry it: mux/include/_build.h
(MUX_VERSION, MUX_RELEASE_DATE) and OLD_BUILD/NEW_BUILD in dounix.sh and
dowin32.sh, which move 9->10 and 10->11 respectively.

CHANGES.md: close the 2.14.0.11 section that has been maintained through
the cycle, and add the client TCP_NODELAY work (#2204, #2205, #2206,
#2207) to Clients, which had #2196 for tf but not the other four native
clients.  Recorded with the wire measurement rather than by inspection --
two segments before the fix, five after, and no observable difference on
loopback at all.

Build-only changes are left out of the changelog as they have been all
cycle (#2177, #2180, #2185, #2211).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 15:31:13 -06:00

513 lines
18 KiB
Bash

#!/bin/bash
#
# REQUIRED: ReferenceDir must already exist. It may be created by untaring a
# previous distribution.
#
# Error handling
set -e # Exit on error
set -o pipefail
# Version information
OLD_BUILD=10
OLD_VERSION="2.14.0.$OLD_BUILD"
NEW_BUILD=11
NEW_VERSION="2.14.0.$NEW_BUILD"
# Directory structure
CHANGES_DIR=mux
REFERENCE_DIR=mux2.14_$OLD_BUILD
DISTRO_DIR=mux2.14
NEW_DIR=mux2.14_$NEW_BUILD
echo "Checking for required tools..."
if ! command -v xdelta3 &> /dev/null; then
echo "xdelta3 is required but not found. Please install using apt-cyg install xdelta3"
exit 1
fi
# Validate the table-of-contents lists before touching anything.
#
# The lists are the whole packaging model: a distribution is the PREVIOUS
# release plus whatever these name. A list that contradicts itself or names
# a file that no longer exists produces a wrong tarball SILENTLY -- every
# checksum and every signature over the result still verifies, because the
# archive is intact. It is just incomplete. That is how 2.14.0.9 and
# 2.14.0.10 shipped a win32 source zip missing six sources netmux.vcxproj
# requires: they were on the copy list AND the delete list, and the delete
# runs second.
#
# Two invariants, both cheap, both failing loudly:
# 1. removed n (patchable u unpatched) = 0 -- a file cannot both ship and
# be deleted; whichever the script does last wins by accident.
# 2. everything listed to ship exists in the tree -- catches paths left
# behind by a move.
#
# The stronger check (every source the BUILD SYSTEM names is present in the
# output) cannot run here: it needs the packaged tree, and it is
# platform-specific -- Makefile.am on Unix, netmux.vcxproj on Windows. That
# seam is exactly where the six hid, so run it against the built artifact.
#
validate_toc_lists() {
local dist_type=$1
local errors=0
local ship_list conflict f
ship_list=$(cat "win32/TOC.$dist_type.patchable" "win32/TOC.$dist_type.unpatched" \
| sed 's/\r$//' | grep -v '^[[:space:]]*$' | sort -u)
conflict=$(comm -12 <(echo "$ship_list") \
<(sed 's/\r$//' "win32/TOC.$dist_type.removed" \
| grep -v '^[[:space:]]*$' | sort -u))
if [ -n "$conflict" ]; then
echo "ERROR: win32/TOC.$dist_type.* lists both ship and delete these files:"
echo "$conflict" | sed 's/^/ /'
echo " A file on the removed list is deleted after it is copied, so it"
echo " never ships. Remove it from one list or the other."
errors=$((errors + 1))
fi
while IFS= read -r f; do
[ -z "$f" ] && continue
if [ ! -e "$CHANGES_DIR/$f" ]; then
echo "ERROR: win32/TOC.$dist_type.* lists '$f', which is not in $CHANGES_DIR/."
errors=$((errors + 1))
fi
done <<< "$ship_list"
return $errors
}
echo "Validating table-of-contents lists..."
toc_errors=0
for dist_type in src bin; do
validate_toc_lists "$dist_type" || toc_errors=$((toc_errors + $?))
done
if [ "$toc_errors" -ne 0 ]; then
echo "Refusing to build a distribution from inconsistent file lists."
exit 1
fi
echo " TOC lists consistent."
# Function to clean files
clean_files() {
local dir=$1
shift
for file in "$@"; do
# A blank line in a TOC.removed list arrives here as an empty
# element (mapfile -t preserves it; dounix.sh's unquoted word
# splitting silently drops it, which is why the same blank line
# is harmless there and fatal here). "$dir/" exists, so without
# this guard the loop runs rm on the distribution root itself:
# plain rm refuses with "Is a directory" and set -e aborts the
# release mid-build. The rm -rf variant of this would not refuse.
[ -z "$file" ] && continue
if [ -e "$dir/$file" ]; then
echo "Removing: $dir/$file"
rm "$dir/$file"
fi
done
}
# Function to check if a file is binary
is_binary() {
if file --mime "$1" | grep -q "charset=binary"; then
return 0 # True, it's binary
else
return 1 # False, it's not binary
fi
}
# Write a sorted newline-delimited file list without adding a blank line for empty arrays.
write_sorted_list() {
local output_file=$1
shift
if [ "$#" -eq 0 ]; then
: > "$output_file"
else
printf '%s\n' "$@" | sort > "$output_file"
fi
}
# Function to process source and create patches
process_distribution() {
local dist_type=$1 # "src" or "bin"
echo "Building Windows $dist_type distribution..."
# Read file lists
local -a patchable_files=()
local -a unpatched_files=()
local -a remove_files=()
mapfile -t patchable_files < "win32/TOC.$dist_type.patchable"
mapfile -t unpatched_files < "win32/TOC.$dist_type.unpatched"
mapfile -t remove_files < "win32/TOC.$dist_type.removed"
# Prepare directories
rm -rf "${NEW_DIR}_$dist_type" "$DISTRO_DIR" "patches_$dist_type"
cp -r "${REFERENCE_DIR}_$dist_type" "$DISTRO_DIR"
cp -r "${REFERENCE_DIR}_$dist_type" "${NEW_DIR}_$dist_type"
mkdir -p "patches_$dist_type/bin" "patches_$dist_type/text"
# Copy patchable files
echo "Copying patchable files for Windows $dist_type..."
for file in "${patchable_files[@]}"; do
mkdir -p "$(dirname "${NEW_DIR}_$dist_type/$file")"
cp "$CHANGES_DIR/$file" "${NEW_DIR}_$dist_type/$file"
done
# Special Windows configurations for source
if [ "$dist_type" = "src" ]; then
cp "$CHANGES_DIR/include/autoconf-win32.h" "${NEW_DIR}_$dist_type/include/autoconf.h"
cp "$CHANGES_DIR/modules/autoconf-win32.h" "${NEW_DIR}_$dist_type/modules/autoconf.h"
fi
# Remove files
echo "Cleaning up removed files for Windows $dist_type..."
clean_files "${NEW_DIR}_$dist_type" "${remove_files[@]}"
chmod -R u+rw "$DISTRO_DIR" "${NEW_DIR}_$dist_type"
# Create a master patch manifest
echo "# Windows $dist_type patches for MUX $OLD_VERSION to $NEW_VERSION" > "patches_$dist_type/MANIFEST.txt"
echo "# Generated on $(date)" >> "patches_$dist_type/MANIFEST.txt"
echo "" >> "patches_$dist_type/MANIFEST.txt"
echo "# Binary files (xdelta3 format):" >> "patches_$dist_type/MANIFEST.txt"
# Process all files to create appropriate patches
echo "Generating patches for Windows $dist_type..."
# Track files for manifest
local -a binary_files=()
local -a text_files=()
local -a new_files=()
local -a removed_files=()
# Create patches for changed files and track new/removed files
while IFS= read -r src_file; do
rel_path=${src_file#$DISTRO_DIR/}
new_file="${NEW_DIR}_$dist_type/$rel_path"
if [ -f "$new_file" ]; then
# Check if files differ
if ! cmp -s "$src_file" "$new_file"; then
if is_binary "$src_file" || is_binary "$new_file"; then
# Binary file - use xdelta3
mkdir -p "patches_$dist_type/bin/$(dirname "$rel_path")"
xdelta3 -e -s "$src_file" "$new_file" "patches_$dist_type/bin/$rel_path.vcdiff"
binary_files+=("$rel_path")
else
# Text file - use standard diff
mkdir -p "patches_$dist_type/text/$(dirname "$rel_path")"
diff -u "$src_file" "$new_file" > "patches_$dist_type/text/$rel_path.patch" 2>/dev/null || diff_rc=$?
if [ "${diff_rc:-0}" -ge 2 ]; then
echo "Error: diff failed for $rel_path (exit $diff_rc)" >&2
exit 1
fi
text_files+=("$rel_path")
fi
fi
else
# File was removed
removed_files+=("$rel_path")
fi
done < <(find "$DISTRO_DIR" -type f)
# Detect new files
while IFS= read -r new_file; do
rel_path=${new_file#${NEW_DIR}_$dist_type/}
src_file="$DISTRO_DIR/$rel_path"
if [ ! -f "$src_file" ]; then
# File was added - copy directly
if is_binary "$new_file"; then
mkdir -p "patches_$dist_type/bin/$(dirname "$rel_path")"
cp "$new_file" "patches_$dist_type/bin/$rel_path.new"
else
mkdir -p "patches_$dist_type/text/$(dirname "$rel_path")"
cp "$new_file" "patches_$dist_type/text/$rel_path.new"
fi
new_files+=("$rel_path")
fi
done < <(find "${NEW_DIR}_$dist_type" -type f)
# Create metadata files for patch application
write_sorted_list "patches_$dist_type/binary_files.txt" "${binary_files[@]}"
write_sorted_list "patches_$dist_type/text_files.txt" "${text_files[@]}"
write_sorted_list "patches_$dist_type/new_files.txt" "${new_files[@]}"
write_sorted_list "patches_$dist_type/removed_files.txt" "${removed_files[@]}"
# Complete the manifest
while IFS= read -r file; do
[ -n "$file" ] || continue
echo "bin/$file.vcdiff" >> "patches_$dist_type/MANIFEST.txt"
done < "patches_$dist_type/binary_files.txt"
echo "" >> "patches_$dist_type/MANIFEST.txt"
echo "# Text files (unified diff format):" >> "patches_$dist_type/MANIFEST.txt"
while IFS= read -r file; do
[ -n "$file" ] || continue
echo "text/$file.patch" >> "patches_$dist_type/MANIFEST.txt"
done < "patches_$dist_type/text_files.txt"
echo "" >> "patches_$dist_type/MANIFEST.txt"
echo "# New files:" >> "patches_$dist_type/MANIFEST.txt"
while IFS= read -r file; do
[ -n "$file" ] || continue
if is_binary "${NEW_DIR}_$dist_type/$file"; then
echo "bin/$file.new" >> "patches_$dist_type/MANIFEST.txt"
else
echo "text/$file.new" >> "patches_$dist_type/MANIFEST.txt"
fi
done < "patches_$dist_type/new_files.txt"
echo "" >> "patches_$dist_type/MANIFEST.txt"
echo "# Removed files:" >> "patches_$dist_type/MANIFEST.txt"
while IFS= read -r file; do
[ -n "$file" ] || continue
echo "$file" >> "patches_$dist_type/MANIFEST.txt"
done < "patches_$dist_type/removed_files.txt"
# Create patch application script
cat > "patches_$dist_type/apply_patch.bat" <<EOF
@echo off
echo MUX $OLD_VERSION to $NEW_VERSION Patch Applier
echo =====================================
echo.
if not exist xdelta3.exe (
echo Error: xdelta3.exe not found in current directory
echo Please download xdelta3.exe and place it in this directory
exit /b 1
)
if not exist "%1" (
echo Usage: apply_patch.bat [MUX_DIRECTORY]
echo.
echo Please provide the path to your MUX $OLD_VERSION installation
exit /b 1
)
set MUX_DIR=%1
echo Using MUX directory: %MUX_DIR%
echo.
echo Applying binary patches...
for /F "tokens=*" %%f in (binary_files.txt) do (
echo Patching: %%f
xdelta3 -d -s "%MUX_DIR%\%%f" "bin\%%f.vcdiff" "%MUX_DIR%\%%f.new"
move /Y "%MUX_DIR%\%%f.new" "%MUX_DIR%\%%f"
)
echo.
echo Applying text patches...
for /F "tokens=*" %%f in (text_files.txt) do (
echo Patching: %%f
patch "%MUX_DIR%\%%f" < "text\%%f.patch"
)
echo.
echo Copying new files...
for /F "tokens=*" %%f in (new_files.txt) do (
if exist "bin\%%f.new" (
echo Adding: %%f
copy /Y "bin\%%f.new" "%MUX_DIR%\%%f"
) else if exist "text\%%f.new" (
echo Adding: %%f
copy /Y "text\%%f.new" "%MUX_DIR%\%%f"
)
)
echo.
echo Removing deleted files...
for /F "tokens=*" %%f in (removed_files.txt) do (
echo Removing: %%f
if exist "%MUX_DIR%\%%f" del "%MUX_DIR%\%%f"
)
echo.
echo Patch application complete!
echo Your MUX has been updated to version $NEW_VERSION
EOF
# Create a combined patch archive in both formats
echo "Creating Windows $dist_type patch archives..."
if [ -e "mux-$OLD_VERSION-$NEW_VERSION.win32.$dist_type.patch.zip" ]; then
rm "mux-$OLD_VERSION-$NEW_VERSION.win32.$dist_type.patch.zip"
fi
zip -r "mux-$OLD_VERSION-$NEW_VERSION.win32.$dist_type.patch.zip" "patches_$dist_type/"
# Also create .7z format
if command -v 7z &> /dev/null; then
if [ -e "mux-$OLD_VERSION-$NEW_VERSION.win32.$dist_type.patch.7z" ]; then
rm "mux-$OLD_VERSION-$NEW_VERSION.win32.$dist_type.patch.7z"
fi
7z a "mux-$OLD_VERSION-$NEW_VERSION.win32.$dist_type.patch.7z" "patches_$dist_type/"
else
echo "7z command not found, skipping .7z archive creation"
fi
# Build complete distribution
rm -rf "$DISTRO_DIR"
# Copy unpatched files
echo "Copying unpatched files for Windows $dist_type..."
for file in "${unpatched_files[@]}"; do
mkdir -p "$(dirname "${NEW_DIR}_$dist_type/$file")"
cp "$CHANGES_DIR/$file" "${NEW_DIR}_$dist_type/$file"
done
# Final cleanup
echo "Final cleanup of removed files for Windows $dist_type..."
clean_files "${NEW_DIR}_$dist_type" "${remove_files[@]}"
cp -r "${NEW_DIR}_$dist_type" "$DISTRO_DIR"
# Create full distribution archives
echo "Creating Windows $dist_type full distribution archives..."
# ZIP format (primary Windows format)
if [ -e "mux-$NEW_VERSION.win32.$dist_type.zip" ]; then
rm "mux-$NEW_VERSION.win32.$dist_type.zip"
fi
zip -r "mux-$NEW_VERSION.win32.$dist_type.zip" "$DISTRO_DIR"
# 7z format (also common on Windows)
if command -v 7z &> /dev/null; then
if [ -e "mux-$NEW_VERSION.win32.$dist_type.7z" ]; then
rm "mux-$NEW_VERSION.win32.$dist_type.7z"
fi
7z a "mux-$NEW_VERSION.win32.$dist_type.7z" "$DISTRO_DIR"
else
echo "7z command not found, skipping .7z archive creation"
fi
# Generate checksums
echo "Generating checksums for Windows $dist_type..."
sha256sum "mux-$OLD_VERSION-$NEW_VERSION.win32.$dist_type.patch.zip" > "mux-$OLD_VERSION-$NEW_VERSION.win32.$dist_type.patch.zip.sha256"
sha256sum "mux-$NEW_VERSION.win32.$dist_type.zip" > "mux-$NEW_VERSION.win32.$dist_type.zip.sha256"
if command -v 7z &> /dev/null; then
sha256sum "mux-$OLD_VERSION-$NEW_VERSION.win32.$dist_type.patch.7z" > "mux-$OLD_VERSION-$NEW_VERSION.win32.$dist_type.patch.7z.sha256"
sha256sum "mux-$NEW_VERSION.win32.$dist_type.7z" > "mux-$NEW_VERSION.win32.$dist_type.7z.sha256"
fi
# Generate a simple README with instructions
cat > "patches_$dist_type/README.txt" <<EOF
MUX $OLD_VERSION to $NEW_VERSION Patch Instructions
=================================================
This patch package will update your MUX $OLD_VERSION installation to MUX $NEW_VERSION.
Requirements:
- xdelta3.exe (for binary patches)
- patch.exe (for text patches)
- A working MUX $OLD_VERSION installation
Instructions:
1. Download xdelta3.exe from https://github.com/jmacd/xdelta/releases
and patch.exe from http://gnuwin32.sourceforge.net/packages/patch.htm
2. Extract this patch package to a folder
3. Copy xdelta3.exe and patch.exe to the same folder
4. Run apply_patch.bat and provide the path to your MUX installation:
apply_patch.bat C:\Path\to\your\mux\installation
For Unix/Linux users:
If you're applying this patch on Unix/Linux, you can use the standard xdelta3
and patch commands instead of the .bat file:
1. Apply binary patches:
cat binary_files.txt | while read f; do
xdelta3 -d -s "/path/to/mux/\$f" "bin/\$f.vcdiff" "/path/to/mux/\$f.new" &&
mv "/path/to/mux/\$f.new" "/path/to/mux/\$f";
done
2. Apply text patches:
cat text_files.txt | while read f; do
patch "/path/to/mux/\$f" < "text/\$f.patch";
done
3. Copy new files and remove deleted files as needed
For any assistance, please visit: https://tinymux.org
EOF
echo "Process complete for $dist_type distribution!"
}
# Main execution
process_distribution "src"
process_distribution "bin"
# Expected SHA256 hashes for downloaded tools.
# Update these when changing the download URLs or tool versions.
XDELTA3_SHA256="0053909B19CA093B1B8F862F3B48B3B3A9B5AAA0899A8078A66D8E72E733A950"
PATCH_SHA256="0B25FDB8E8F82B27A53CCDE22F70BCD3D9D8C16B68E6E3B3C8D045E2E82B72B8"
# Create installer script for xdelta3
cat > get_xdelta3.bat <<EOF
@echo off
set EXPECTED_HASH=$XDELTA3_SHA256
echo Downloading xdelta3.exe for patch application...
powershell -Command "Invoke-WebRequest -Uri 'https://github.com/jmacd/xdelta/releases/download/v3.1.0/xdelta3-3.1.0-x86_64-win64.exe.zip' -OutFile 'xdelta3.zip'"
echo Verifying download integrity...
for /f %%h in ('powershell -Command "(Get-FileHash 'xdelta3.zip' -Algorithm SHA256).Hash"') do set ACTUAL_HASH=%%h
if /i not "%ACTUAL_HASH%"=="%EXPECTED_HASH%" (
echo ERROR: SHA256 hash mismatch - download may be corrupted or tampered with.
echo Expected: %EXPECTED_HASH%
echo Got: %ACTUAL_HASH%
del xdelta3.zip
exit /b 1
)
echo Hash verified OK.
powershell -Command "Expand-Archive -Path 'xdelta3.zip' -DestinationPath '.'"
move xdelta3-3.1.0-x86_64-win64.exe xdelta3.exe
del xdelta3.zip
echo xdelta3.exe is now ready to use
EOF
# Create installer script for patch.exe
cat > get_patch.bat <<EOF
@echo off
set EXPECTED_HASH=$PATCH_SHA256
echo Downloading patch.exe for patch application...
powershell -Command "Invoke-WebRequest -Uri 'https://downloads.sourceforge.net/project/gnuwin32/patch/2.5.9-7/patch-2.5.9-7-bin.zip' -OutFile 'patch.zip'"
echo Verifying download integrity...
for /f %%h in ('powershell -Command "(Get-FileHash 'patch.zip' -Algorithm SHA256).Hash"') do set ACTUAL_HASH=%%h
if /i not "%ACTUAL_HASH%"=="%EXPECTED_HASH%" (
echo ERROR: SHA256 hash mismatch - download may be corrupted or tampered with.
echo Expected: %EXPECTED_HASH%
echo Got: %ACTUAL_HASH%
del patch.zip
exit /b 1
)
echo Hash verified OK.
powershell -Command "Expand-Archive -Path 'patch.zip' -DestinationPath 'patch_temp'"
copy patch_temp\\bin\\patch.exe .
rmdir /s /q patch_temp
del patch.zip
echo patch.exe is now ready to use
EOF
# Copy these scripts to both patch directories
cp get_xdelta3.bat patches_src/
cp get_patch.bat patches_src/
cp get_xdelta3.bat patches_bin/
cp get_patch.bat patches_bin/
echo "Windows build process completed successfully!"
echo "---------------------------------------------"
echo "Source distribution files:"
ls -la "mux-$OLD_VERSION-$NEW_VERSION.win32.src.patch."*
ls -la "mux-$NEW_VERSION.win32.src."*
echo "Binary distribution files:"
ls -la "mux-$OLD_VERSION-$NEW_VERSION.win32.bin.patch."*
ls -la "mux-$NEW_VERSION.win32.bin."*
echo "All files have been created successfully!"