From 328a10d28d53462571c216868941028d94c25f19 Mon Sep 17 00:00:00 2001
From: Derek Johnston <ross.mcnairn.dev@wordsmith.ai>
Date: Wed, 25 Dec 2024 04:52:55 +0000
Subject: [PATCH 1/3] compress in separate threads

---
 build_differ.py | 117 ++++++++++++++++++++++++++++++------------------
 1 file changed, 74 insertions(+), 43 deletions(-)

diff --git a/build_differ.py b/build_differ.py
index 0c6ab53..8c2f7d1 100644
--- a/build_differ.py
+++ b/build_differ.py
@@ -1,3 +1,4 @@
+import asyncio
 import subprocess
 import os
 import tarfile
@@ -9,34 +10,48 @@ def get_version():
     Extracts the version from the specified __about__.py file.
     """
     about = {}
-    with open('./src/python_redlines/__about__.py') as f:
+    with open("./src/python_redlines/__about__.py") as f:
         exec(f.read(), about)
-    return about['__version__']
+    return about["__version__"]
 
 
 def run_command(command):
     """
     Runs a shell command and prints its output.
     """
-    process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+    process = subprocess.Popen(
+        command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
+    )
     for line in process.stdout:
         print(line.decode().strip())
 
 
-def compress_files(source_dir, target_file):
+def _compress_tar(source_dir, target_file):
+    with tarfile.open(target_file, "w:gz") as tar:
+        tar.add(source_dir, arcname=os.path.basename(source_dir))
+
+
+def _compress_zip(source_dir, target_file):
+    with zipfile.ZipFile(target_file, "w", zipfile.ZIP_DEFLATED) as zipf:
+        for root, dirs, files in os.walk(source_dir):
+            for file in files:
+                zipf.write(
+                    os.path.join(root, file),
+                    os.path.relpath(
+                        os.path.join(root, file), os.path.join(source_dir, "..")
+                    ),
+                )
+
+
+async def compress_files(source_dir, target_file):
     """
     Compresses files in the specified directory into a tar.gz or zip file.
     """
-    if target_file.endswith('.tar.gz'):
-        with tarfile.open(target_file, "w:gz") as tar:
-            tar.add(source_dir, arcname=os.path.basename(source_dir))
-    elif target_file.endswith('.zip'):
-        with zipfile.ZipFile(target_file, 'w', zipfile.ZIP_DEFLATED) as zipf:
-            for root, dirs, files in os.walk(source_dir):
-                for file in files:
-                    zipf.write(os.path.join(root, file),
-                               os.path.relpath(os.path.join(root, file),
-                                               os.path.join(source_dir, '..')))
+    loop = asyncio.get_event_loop()
+    if target_file.endswith(".tar.gz"):
+        loop.run_in_executor(None, _compress_tar, source_dir, target_file)
+    elif target_file.endswith(".zip"):
+        loop.run_in_executor(None, _compress_zip, source_dir, target_file)
 
 
 def cleanup_old_builds(dist_dir, current_version):
@@ -44,13 +59,15 @@ def cleanup_old_builds(dist_dir, current_version):
     Deletes any build files ending in .zip or .tar.gz in the dist_dir with a different version tag.
     """
     for file in os.listdir(dist_dir):
-        if not file.endswith((f'{current_version}.zip', f'{current_version}.tar.gz', '.gitignore')):
+        if not file.endswith(
+            (f"{current_version}.zip", f"{current_version}.tar.gz", ".gitignore")
+        ):
             file_path = os.path.join(dist_dir, file)
             os.remove(file_path)
             print(f"Deleted old build file: {file}")
 
 
-def main():
+async def main():
     version = get_version()
     print(f"Version: {version}")
 
@@ -58,47 +75,61 @@ def main():
 
     # Build for Linux x64
     print("Building for Linux x64...")
-    run_command('dotnet publish ./csproj -c Release -r linux-x64 --self-contained')
+    run_command("dotnet publish ./csproj -c Release -r linux-x64 --self-contained")
 
     # Build for Linux ARM64
     print("Building for Linux ARM64...")
-    run_command('dotnet publish ./csproj -c Release -r linux-arm64 --self-contained')
+    run_command("dotnet publish ./csproj -c Release -r linux-arm64 --self-contained")
 
     # Build for Windows x64
     print("Building for Windows x64...")
-    run_command('dotnet publish ./csproj -c Release -r win-x64 --self-contained')
+    run_command("dotnet publish ./csproj -c Release -r win-x64 --self-contained")
 
     # Build for Windows ARM64
     print("Building for Windows ARM64...")
-    run_command('dotnet publish ./csproj -c Release -r win-arm64 --self-contained')
+    run_command("dotnet publish ./csproj -c Release -r win-arm64 --self-contained")
 
     # Build for macOS x64
     print("Building for macOS x64...")
-    run_command('dotnet publish ./csproj -c Release -r osx-x64 --self-contained')
+    run_command("dotnet publish ./csproj -c Release -r osx-x64 --self-contained")
 
     # Build for macOS ARM64
     print("Building for macOS ARM64...")
-    run_command('dotnet publish ./csproj -c Release -r osx-arm64 --self-contained')
-
-    # Compress the Linux x64 build
-    linux_x64_build_dir = './csproj/bin/Release/net8.0/linux-x64'
-    compress_files(linux_x64_build_dir, f"{dist_dir}/linux-x64-{version}.tar.gz")
-
-    # Compress the Linux ARM64 build
-    linux_arm64_build_dir = './csproj/bin/Release/net8.0/linux-arm64'
-    compress_files(linux_arm64_build_dir, f"{dist_dir}/linux-arm64-{version}.tar.gz")
-
-    # Compress the Windows x64 build
-    windows_build_dir = './csproj/bin/Release/net8.0/win-x64'
-    compress_files(windows_build_dir, f"{dist_dir}/win-x64-{version}.zip")
-
-    # Compress the macOS x64 build
-    macos_x64_build_dir = './csproj/bin/Release/net8.0/osx-x64'
-    compress_files(macos_x64_build_dir, f"{dist_dir}/osx-x64-{version}.tar.gz")
-
-    # Compress the macOS ARM64 build
-    macos_arm64_build_dir = './csproj/bin/Release/net8.0/osx-arm64'
-    compress_files(macos_arm64_build_dir, f"{dist_dir}/osx-arm64-{version}.tar.gz")
+    run_command("dotnet publish ./csproj -c Release -r osx-arm64 --self-contained")
+
+    compression_inputs = [
+        (
+            "./csproj/bin/Release/net8.0/linux-x64",
+            f"{dist_dir}/linux-x64-{version}.tar.gz",
+        ),
+        (
+            "./csproj/bin/Release/net8.0/linux-arm64",
+            f"{dist_dir}/linux-arm64-{version}.tar.gz",
+        ),
+        (
+            "./csproj/bin/Release/net8.0/win-x64",
+            f"{dist_dir}/win-x64-{version}.zip",
+        ),
+        (
+            "./csproj/bin/Release/net8.0/win-arm64",
+            f"{dist_dir}/win-arm64-{version}.zip",
+        ),
+        (
+            "./csproj/bin/Release/net8.0/osx-x64",
+            f"{dist_dir}/osx-x64-{version}.tar.gz",
+        ),
+        (
+            "./csproj/bin/Release/net8.0/osx-arm64",
+            f"{dist_dir}/osx-arm64-{version}.tar.gz",
+        ),
+    ]
+
+    await asyncio.gather(
+        *[
+            compress_files(source_dir, target_file)
+            for source_dir, target_file in compression_inputs
+        ]
+    )
 
     cleanup_old_builds(dist_dir, version)
 
@@ -106,4 +137,4 @@ def main():
 
 
 if __name__ == "__main__":
-    main()
+    asyncio.run(main())

From cb0bbbf232262b0d449be721a3cb8b78f93b3aa8 Mon Sep 17 00:00:00 2001
From: Derek Johnston <ross.mcnairn.dev@wordsmith.ai>
Date: Wed, 25 Dec 2024 16:49:31 +0000
Subject: [PATCH 2/3] publish in parallel procs

---
 build_differ.py        | 45 ++++++++++++++++--------------------------
 csproj/redlines.csproj |  1 +
 2 files changed, 18 insertions(+), 28 deletions(-)

diff --git a/build_differ.py b/build_differ.py
index 8c2f7d1..e837f7b 100644
--- a/build_differ.py
+++ b/build_differ.py
@@ -15,16 +15,18 @@ def get_version():
     return about["__version__"]
 
 
-def run_command(command):
+async def run_command(command):
     """
     Runs a shell command and prints its output.
     """
-    process = subprocess.Popen(
-        command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
+    process = await asyncio.create_subprocess_exec(
+        *command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
     )
-    for line in process.stdout:
+    async for line in process.stdout:
         print(line.decode().strip())
 
+    await process.wait()
+
 
 def _compress_tar(source_dir, target_file):
     with tarfile.open(target_file, "w:gz") as tar:
@@ -49,9 +51,9 @@ async def compress_files(source_dir, target_file):
     """
     loop = asyncio.get_event_loop()
     if target_file.endswith(".tar.gz"):
-        loop.run_in_executor(None, _compress_tar, source_dir, target_file)
+        await loop.run_in_executor(None, _compress_tar, source_dir, target_file)
     elif target_file.endswith(".zip"):
-        loop.run_in_executor(None, _compress_zip, source_dir, target_file)
+        await loop.run_in_executor(None, _compress_zip, source_dir, target_file)
 
 
 def cleanup_old_builds(dist_dir, current_version):
@@ -73,29 +75,16 @@ async def main():
 
     dist_dir = "./src/python_redlines/dist/"
 
-    # Build for Linux x64
-    print("Building for Linux x64...")
-    run_command("dotnet publish ./csproj -c Release -r linux-x64 --self-contained")
-
-    # Build for Linux ARM64
-    print("Building for Linux ARM64...")
-    run_command("dotnet publish ./csproj -c Release -r linux-arm64 --self-contained")
-
-    # Build for Windows x64
-    print("Building for Windows x64...")
-    run_command("dotnet publish ./csproj -c Release -r win-x64 --self-contained")
-
-    # Build for Windows ARM64
-    print("Building for Windows ARM64...")
-    run_command("dotnet publish ./csproj -c Release -r win-arm64 --self-contained")
-
-    # Build for macOS x64
-    print("Building for macOS x64...")
-    run_command("dotnet publish ./csproj -c Release -r osx-x64 --self-contained")
+    run_commands = [
+        ["dotnet", "publish", "./csproj", "-c", "Release", "-r", "linux-x64", "--self-contained"],
+        ["dotnet", "publish", "./csproj", "-c", "Release", "-r", "linux-arm64", "--self-contained"],
+        ["dotnet", "publish", "./csproj", "-c", "Release", "-r", "win-x64", "--self-contained"],
+        ["dotnet", "publish", "./csproj", "-c", "Release", "-r", "win-arm64", "--self-contained"],
+        ["dotnet", "publish", "./csproj", "-c", "Release", "-r", "osx-x64", "--self-contained"],
+        ["dotnet", "publish", "./csproj", "-c", "Release", "-r", "osx-arm64", "--self-contained"],
+    ]
 
-    # Build for macOS ARM64
-    print("Building for macOS ARM64...")
-    run_command("dotnet publish ./csproj -c Release -r osx-arm64 --self-contained")
+    await asyncio.gather(*[run_command(command) for command in run_commands])
 
     compression_inputs = [
         (
diff --git a/csproj/redlines.csproj b/csproj/redlines.csproj
index 8b2abdd..0e354c2 100644
--- a/csproj/redlines.csproj
+++ b/csproj/redlines.csproj
@@ -5,6 +5,7 @@
     <TargetFramework>net8.0</TargetFramework>
     <ImplicitUsings>enable</ImplicitUsings>
     <Nullable>enable</Nullable>
+    <RuntimeIdentifiers>win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
   </PropertyGroup>
 
   <ItemGroup>

From 42d3c82a4c9bba9e6e790a9a0379855fed7bdf96 Mon Sep 17 00:00:00 2001
From: Derek Johnston <ross.mcnairn.dev@wordsmith.ai>
Date: Wed, 25 Dec 2024 19:00:13 +0000
Subject: [PATCH 3/3] manually restore

---
 build_differ.py | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/build_differ.py b/build_differ.py
index e837f7b..c3e4f6d 100644
--- a/build_differ.py
+++ b/build_differ.py
@@ -75,13 +75,15 @@ async def main():
 
     dist_dir = "./src/python_redlines/dist/"
 
+    await run_command(["dotnet", "restore", "./csproj"])
+
     run_commands = [
-        ["dotnet", "publish", "./csproj", "-c", "Release", "-r", "linux-x64", "--self-contained"],
-        ["dotnet", "publish", "./csproj", "-c", "Release", "-r", "linux-arm64", "--self-contained"],
-        ["dotnet", "publish", "./csproj", "-c", "Release", "-r", "win-x64", "--self-contained"],
-        ["dotnet", "publish", "./csproj", "-c", "Release", "-r", "win-arm64", "--self-contained"],
-        ["dotnet", "publish", "./csproj", "-c", "Release", "-r", "osx-x64", "--self-contained"],
-        ["dotnet", "publish", "./csproj", "-c", "Release", "-r", "osx-arm64", "--self-contained"],
+        ["dotnet", "publish", "./csproj", "-c", "Release", "-r", "linux-x64", "--self-contained", "--no-restore"],
+        ["dotnet", "publish", "./csproj", "-c", "Release", "-r", "linux-arm64", "--self-contained", "--no-restore"],
+        ["dotnet", "publish", "./csproj", "-c", "Release", "-r", "win-x64", "--self-contained", "--no-restore"],
+        ["dotnet", "publish", "./csproj", "-c", "Release", "-r", "win-arm64", "--self-contained", "--no-restore"],
+        ["dotnet", "publish", "./csproj", "-c", "Release", "-r", "osx-x64", "--self-contained", "--no-restore"],
+        ["dotnet", "publish", "./csproj", "-c", "Release", "-r", "osx-arm64", "--self-contained", "--no-restore"],
     ]
 
     await asyncio.gather(*[run_command(command) for command in run_commands])