Port Headerer from SabreTools, add build scripts

This commit is contained in:
Matt Nadareski
2024-10-24 21:30:22 -04:00
parent a5e172fa9f
commit c19c734cc2
12 changed files with 876 additions and 0 deletions

50
.github/workflows/build_and_publish.yml vendored Normal file
View File

@@ -0,0 +1,50 @@
name: Build and Publish
on:
push:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
project: [Headerer]
runtime: [win-x86, win-x64, win-arm64, linux-x64, linux-arm64, osx-x64]
framework: [net8.0] #[net20, net35, net40, net452, net472, net48, netcoreapp3.1, net5.0, net6.0, net7.0, net8.0]
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet publish ${{ matrix.project }}/${{ matrix.project }}.csproj -f ${{ matrix.framework }} -r ${{ matrix.runtime }} -c Debug --self-contained true --version-suffix ${{ github.sha }} ${{ (startsWith(matrix.framework, 'net5') || startsWith(matrix.framework, 'net6') || startsWith(matrix.framework, 'net7') || startsWith(matrix.framework, 'net8')) && '-p:PublishSingleFile=true' || ''}}
- name: Archive build
run: zip -r ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_debug.zip ${{ matrix.project }}/bin/Debug/${{ matrix.framework }}/${{ matrix.runtime }}/publish/
- name: Upload build
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_debug
path: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_debug.zip
- name: Upload to rolling
uses: ncipollo/release-action@v1.14.0
with:
allowUpdates: True
artifacts: ${{ matrix.project }}_${{ matrix.framework }}_${{ matrix.runtime }}_debug.zip
body: 'Last built commit: ${{ github.sha }}'
name: 'Rolling Release'
prerelease: True
replacesArtifacts: True
tag: "rolling"
updateOnlyUnreleased: True

129
Headerer/Database.cs Normal file
View File

@@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Data.Sqlite;
using SabreTools.IO;
namespace Headerer
{
internal static class Database
{
#region Constants
/// <summary>
/// Default location for the database
/// </summary>
private static readonly string DbFileName = Path.Combine(PathTool.GetRuntimeDirectory(), "Headerer.sqlite");
/// <summary>
/// Connection string for the database
/// </summary>
private static readonly string DbConnectionString = $"Data Source={DbFileName};";
#endregion
/// <summary>
/// Add a header to the database
/// </summary>
/// <param name="header">String representing the header bytes</param>
/// <param name="SHA1">SHA-1 of the deheadered file</param>
/// <param name="type">Name of the source skipper file</param>
/// <param name="debug">Enable additional log statements for debugging</param>
public static void AddHeader(string header, string SHA1, string source, bool debug)
{
// Ensure the database exists
EnsureDatabase();
// Open the database connection
SqliteConnection dbc = new(DbConnectionString);
dbc.Open();
string query = $"SELECT * FROM data WHERE sha1='{SHA1}' AND header='{header}'";
var slc = new SqliteCommand(query, dbc);
SqliteDataReader sldr = slc.ExecuteReader();
bool exists = sldr.HasRows;
if (!exists)
{
query = $"INSERT INTO data (sha1, header, type) VALUES ('{SHA1}', '{header}', '{source}')";
slc = new SqliteCommand(query, dbc);
if (debug) Console.WriteLine($"Result of inserting header: {slc.ExecuteNonQuery()}");
}
// Dispose of database objects
slc.Dispose();
sldr.Dispose();
dbc.Dispose();
}
/// <summary>
/// Retrieve headers from the database
/// </summary>
/// <param name="SHA1">SHA-1 of the deheadered file</param>
/// <param name="debug">Enable additional log statements for debugging</param>
/// <returns>List of strings representing the headers to add</returns>
public static List<string> RetrieveHeaders(string SHA1, bool debug)
{
// Ensure the database exists
EnsureDatabase();
// Open the database connection
var dbc = new SqliteConnection(DbConnectionString);
dbc.Open();
// Create the output list of headers
List<string> headers = [];
string query = $"SELECT header, type FROM data WHERE sha1='{SHA1}'";
var slc = new SqliteCommand(query, dbc);
SqliteDataReader sldr = slc.ExecuteReader();
if (sldr.HasRows)
{
while (sldr.Read())
{
if (debug) Console.WriteLine($"Found match with rom type '{sldr.GetString(1)}'");
headers.Add(sldr.GetString(0));
}
}
else
{
Console.Error.WriteLine("No matching header could be found!");
}
// Dispose of database objects
slc.Dispose();
sldr.Dispose();
dbc.Dispose();
return headers;
}
/// <summary>
/// Ensure that the database exists and has the proper schema
/// </summary>
private static void EnsureDatabase()
{
// Make sure the file exists
if (!File.Exists(DbFileName))
File.Create(DbFileName);
// Open the database connection
SqliteConnection dbc = new(DbConnectionString);
dbc.Open();
// Make sure the database has the correct schema
string query = @"
CREATE TABLE IF NOT EXISTS data (
'sha1' TEXT NOT NULL,
'header' TEXT NOT NULL,
'type' TEXT NOT NULL,
PRIMARY KEY (sha1, header, type)
)";
SqliteCommand slc = new(query, dbc);
slc.ExecuteNonQuery();
slc.Dispose();
dbc.Dispose();
}
}
}

71
Headerer/Extract.cs Normal file
View File

@@ -0,0 +1,71 @@
using System;
using System.IO;
using SabreTools.Hashing;
using SabreTools.IO.Extensions;
using SabreTools.Skippers;
namespace Headerer
{
internal static class Extract
{
/// <summary>
/// Detect header skipper compliance and create an output file
/// </summary>
/// <param name="file">Name of the file to be parsed</param>
/// <param name="outDir">Output directory to write the file to, empty means the same directory as the input file</param>
/// <param name="nostore">True if headers should not be stored in the database, false otherwise</param>
/// <param name="debug">Enable additional log statements for debugging</param>
/// <returns>True if the output file was created, false otherwise</returns>
public static bool DetectTransformStore(string file, string? outDir, bool nostore, bool debug = false)
{
// Create the output directory if it doesn't exist
if (!string.IsNullOrWhiteSpace(outDir) && !Directory.Exists(outDir))
Directory.CreateDirectory(outDir);
Console.WriteLine($"\nGetting skipper information for '{file}'");
// Get the skipper rule that matches the file, if any
SkipperMatch.Init();
Rule rule = SkipperMatch.GetMatchingRule(file, string.Empty);
// If we have an empty rule, return false
if (rule.Tests == null || rule.Tests.Length == 0 || rule.Operation != HeaderSkipOperation.None)
return false;
Console.WriteLine("File has a valid copier header");
// Get the header bytes from the file first
string hstr;
try
{
// Extract the header as a string for the database
using var fs = File.OpenRead(file);
int startOffset = int.Parse(rule.StartOffset ?? "0");
byte[] hbin = new byte[startOffset];
fs.Read(hbin, 0, startOffset);
hstr = ByteArrayExtensions.ByteArrayToString(hbin)!;
}
catch
{
return false;
}
// Apply the rule to the file
string newfile = string.IsNullOrWhiteSpace(outDir) ? Path.GetFullPath(file) + ".new" : Path.Combine(outDir, Path.GetFileName(file));
rule.TransformFile(file, newfile);
// If the output file doesn't exist, return false
if (!File.Exists(newfile))
return false;
// Now add the information to the database if it's not already there
if (!nostore)
{
string sha1 = HashTool.GetFileHash(newfile, HashType.SHA1) ?? string.Empty;
Database.AddHeader(hstr, sha1, rule.SourceFile!, debug);
}
return true;
}
}
}

10
Headerer/Feature.cs Normal file
View File

@@ -0,0 +1,10 @@
namespace Headerer
{
internal enum Feature
{
NONE = 0,
Extract,
Restore,
}
}

49
Headerer/Headerer.csproj Normal file
View File

@@ -0,0 +1,49 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Assembly Properties -->
<TargetFrameworks>net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
<OutputType>Exe</OutputType>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>1.1.2</Version>
<!-- Package Properties -->
<Authors>Matt Nadareski</Authors>
<Copyright>Copyright (c)2016-2024 Matt Nadareski</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/SabreTools/SabreTools</RepositoryUrl>
</PropertyGroup>
<!-- Support All Frameworks -->
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\SabreTools.Skippers\SabreTools.Skippers.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.10" />
<PackageReference Include="SabreTools.Hashing" Version="1.2.2" />
<PackageReference Include="SabreTools.IO" Version="1.4.13" />
</ItemGroup>
</Project>

140
Headerer/Options.cs Normal file
View File

@@ -0,0 +1,140 @@
using System;
using System.Collections.Generic;
namespace Headerer
{
internal sealed class Options
{
#region Properties
/// <summary>
/// Set of input paths to use for operations
/// </summary>
public List<string> InputPaths { get; private set; } = [];
/// <summary>
/// Represents the feature being called
/// </summary>
public Feature Feature { get; private set; } = Feature.NONE;
/// <summary>
/// Output debug statements to console
/// </summary>
public bool Debug { get; private set; } = false;
/// <summary>
/// Optional output directory
/// </summary>
public string? OutputDir { get; private set; }
#region Extraction
/// <summary>
/// Disable storing copier headers on extract
/// </summary>
public bool NoStoreHeader { get; private set; }
#endregion
#endregion
/// <summary>
/// Parse commandline arguments into an Options object
/// </summary>
public static Options? ParseOptions(string[] args)
{
// If we have invalid arguments
if (args == null || args.Length == 0)
return null;
// Create an Options object
var options = new Options();
// Get the first argument as a feature flag
string featureName = args[0];
switch (featureName)
{
case "ex":
case "extract":
options.Feature = Feature.Extract;
break;
case "re":
case "restore":
options.Feature = Feature.Restore;
break;
default:
Console.WriteLine($"{featureName} is not a recognized feature");
return null;
}
// Parse the options and paths
int index = 1;
for (; index < args.Length; index++)
{
string arg = args[index];
switch (arg)
{
case "-dbg":
case "--debug":
options.Debug = true;
break;
case "-o":
case "--outdir":
options.OutputDir = index + 1 < args.Length ? args[++index] : string.Empty;
break;
#region Extraction
case "-nsh":
case "--no-store-header":
options.NoStoreHeader = true;
break;
#endregion
default:
options.InputPaths.Add(arg);
break;
}
}
// Validate we have any input paths to work on
if (options.InputPaths.Count == 0)
{
Console.WriteLine("At least one path is required!");
return null;
}
return options;
}
/// <summary>
/// Display help text
/// </summary>
/// <param name="err">Additional error text to display, can be null to ignore</param>
public static void DisplayHelp(string? err = null)
{
if (!string.IsNullOrEmpty(err))
Console.WriteLine($"Error: {err}");
Console.WriteLine("Headerer - Remove, store, and restore copier headers");
Console.WriteLine();
Console.WriteLine("Headerer.exe <features> <options> file|directory ...");
Console.WriteLine();
Console.WriteLine("Features:");
Console.WriteLine("ex, extract Extract and remove copier headers");
Console.WriteLine("re, restore Restore header to file based on SHA-1");
Console.WriteLine();
Console.WriteLine("Common options:");
Console.WriteLine("-?, -h, --help Display this help text and quit");
Console.WriteLine("-dbg, --debug Enable debug logging statements");
Console.WriteLine("-o, --outdir [PATH] Set output directory");
Console.WriteLine();
Console.WriteLine("Extraction options:");
Console.WriteLine("-nsh, --no-store-header Don't store the extracted header");
}
}
}

45
Headerer/Program.cs Normal file
View File

@@ -0,0 +1,45 @@
namespace Headerer
{
public class Program
{
/// <summary>
/// Entry point for the SabreTools application
/// </summary>
/// <param name="args">String array representing command line parameters</param>
public static void Main(string[] args)
{
// Validate the arguments
if (args == null || args.Length == 0)
{
Options.DisplayHelp("One input file path required");
return;
}
// Get the options from the arguments
var options = Options.ParseOptions(args);
// If we have an invalid state
if (options == null)
{
Options.DisplayHelp();
return;
}
// Loop through the input paths
foreach (string inputPath in options.InputPaths)
{
// TODO: Do something with the output success flags
switch (options.Feature)
{
case Feature.Extract:
_ = Extract.DetectTransformStore(inputPath, options.OutputDir, options.NoStoreHeader);
break;
case Feature.Restore:
_ = Restore.RestoreHeader(inputPath, options.OutputDir);
break;
}
}
}
}
}

86
Headerer/Restore.cs Normal file
View File

@@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.IO;
using SabreTools.Hashing;
using SabreTools.IO.Extensions;
namespace Headerer
{
internal static class Restore
{
/// <summary>
/// Detect and replace header(s) to the given file
/// </summary>
/// <param name="file">Name of the file to be parsed</param>
/// <param name="outDir">Output directory to write the file to, empty means the same directory as the input file</param>
/// <param name="debug">Enable additional log statements for debugging</param>
/// <returns>True if a header was found and appended, false otherwise</returns>
public static bool RestoreHeader(string file, string? outDir, bool debug = false)
{
// Create the output directory if it doesn't exist
if (!string.IsNullOrWhiteSpace(outDir) && !Directory.Exists(outDir))
Directory.CreateDirectory(outDir);
// First, get the SHA-1 hash of the file
string sha1 = HashTool.GetFileHash(file, HashType.SHA1) ?? string.Empty;
// Retrieve a list of all related headers from the database
List<string> headers = Database.RetrieveHeaders(sha1, debug);
// If we have nothing retrieved, we return false
if (headers.Count == 0)
return false;
// Now loop through and create the reheadered files, if possible
for (int i = 0; i < headers.Count; i++)
{
string outputFile = (string.IsNullOrWhiteSpace(outDir) ? $"{Path.GetFullPath(file)}.new" : Path.Combine(outDir, Path.GetFileName(file))) + i;
Console.WriteLine($"Creating reheadered file: {outputFile}");
AppendBytes(file, outputFile, ByteArrayExtensions.StringToByteArray(headers[i]), null);
Console.WriteLine("Reheadered file created!");
}
return true;
}
/// <summary>
/// Add an aribtrary number of bytes to the inputted file
/// </summary>
/// <param name="input">File to be appended to</param>
/// <param name="output">Outputted file</param>
/// <param name="bytesToAddToHead">Bytes to be added to head of file</param>
/// <param name="bytesToAddToTail">Bytes to be added to tail of file</param>
private static void AppendBytes(string input, string output, byte[]? bytesToAddToHead, byte[]? bytesToAddToTail)
{
// If any of the inputs are invalid, skip
if (!File.Exists(input))
return;
using FileStream fsr = File.OpenRead(input);
using FileStream fsw = File.OpenWrite(output);
AppendBytes(fsr, fsw, bytesToAddToHead, bytesToAddToTail);
}
/// <summary>
/// Add an aribtrary number of bytes to the inputted stream
/// </summary>
/// <param name="input">Stream to be appended to</param>
/// <param name="output">Outputted stream</param>
/// <param name="bytesToAddToHead">Bytes to be added to head of stream</param>
/// <param name="bytesToAddToTail">Bytes to be added to tail of stream</param>
private static void AppendBytes(Stream input, Stream output, byte[]? bytesToAddToHead, byte[]? bytesToAddToTail)
{
// Write out prepended bytes
if (bytesToAddToHead != null && bytesToAddToHead.Length > 0)
output.Write(bytesToAddToHead, 0, bytesToAddToHead.Length);
// Now copy the existing file over
input.CopyTo(output);
// Write out appended bytes
if (bytesToAddToTail != null && bytesToAddToTail.Length > 0)
output.Write(bytesToAddToTail, 0, bytesToAddToTail.Length);
}
}
}

View File

@@ -3,3 +3,16 @@
This library comprises of code to perform copier header operations such as matching, manipulation, and removal. These are used for many older console-based systems and helps define known header skippers for immediate use.
Find the link to the Nuget package [here](https://www.nuget.org/packages/SabreTools.Skippers).
## Headerer
A small tool that allows users to extract, store, and remove copier headers for a variety of systems. Optionally, the stored headers can be replaced on the original files using a separate command. Each of the headers are stored in a Sqlite database file that links each header to the unheadered hash of the original file. The following copier header types are supported:
* Atari 7800
* Atari Lynx
* Commodore 64 PSID music
* NEC PC-Engine / TurboGrafx 16
* Nintendo Famicom Disk System
* Nintendo Entertainment System / Famicom
* Super Nintendo Entertainment System / Super Famicom
* Super Nintendo Entertainment System / Super Famicom SPC music

View File

@@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Skippers", "Sabr
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Skippers.Test", "SabreTools.Skippers.Test\SabreTools.Skippers.Test.csproj", "{3F8CABAD-83CF-4AE7-9902-18116FDAFB3F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Headerer", "Headerer\Headerer.csproj", "{96DC170D-0BD0-47B8-B3D8-457DB0E2BE70}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -24,5 +26,9 @@ Global
{3F8CABAD-83CF-4AE7-9902-18116FDAFB3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3F8CABAD-83CF-4AE7-9902-18116FDAFB3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3F8CABAD-83CF-4AE7-9902-18116FDAFB3F}.Release|Any CPU.Build.0 = Release|Any CPU
{96DC170D-0BD0-47B8-B3D8-457DB0E2BE70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{96DC170D-0BD0-47B8-B3D8-457DB0E2BE70}.Debug|Any CPU.Build.0 = Debug|Any CPU
{96DC170D-0BD0-47B8-B3D8-457DB0E2BE70}.Release|Any CPU.ActiveCfg = Release|Any CPU
{96DC170D-0BD0-47B8-B3D8-457DB0E2BE70}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

146
publish-nix.sh Normal file
View File

@@ -0,0 +1,146 @@
#!/bin/bash
# This batch file assumes the following:
# - .NET 8.0 (or newer) SDK is installed and in PATH
# - zip is installed and in PATH
# - Git is installed and in PATH
#
# If any of these are not satisfied, the operation may fail
# in an unpredictable way and result in an incomplete output.
# Optional parameters
USE_ALL=false
NO_BUILD=false
NO_ARCHIVE=false
while getopts "uba" OPTION; do
case $OPTION in
u)
USE_ALL=true
;;
b)
NO_BUILD=true
;;
a)
NO_ARCHIVE=true
;;
*)
echo "Invalid option provided"
exit 1
;;
esac
done
# Set the current directory as a variable
BUILD_FOLDER=$PWD
# Set the current commit hash
COMMIT=$(git log --pretty=%H -1)
# Output the selected options
echo "Selected Options:"
echo " Use all frameworks (-u) $USE_ALL"
echo " No build (-b) $NO_BUILD"
echo " No archive (-a) $NO_ARCHIVE"
echo " "
# Create the build matrix arrays
FRAMEWORKS=("net8.0")
RUNTIMES=("win-x86" "win-x64" "win-arm64" "linux-x64" "linux-arm64" "osx-x64" "osx-arm64")
# Use expanded lists, if requested
if [ $USE_ALL = true ]; then
FRAMEWORKS=("net48" "netcoreapp3.1" "net5.0" "net6.0" "net7.0" "net8.0") # TODO: Support all frameworks
fi
# Create the filter arrays
SINGLE_FILE_CAPABLE=("net5.0" "net6.0" "net7.0" "net8.0")
VALID_APPLE_FRAMEWORKS=("net6.0" "net7.0" "net8.0")
VALID_CROSS_PLATFORM_FRAMEWORKS=("netcoreapp3.1" "net5.0" "net6.0" "net7.0" "net8.0")
VALID_CROSS_PLATFORM_RUNTIMES=("win-arm64" "linux-x64" "linux-arm64" "osx-x64" "osx-arm64")
# Only build if requested
if [ $NO_BUILD = false ]; then
# Restore Nuget packages for all builds
echo "Restoring Nuget packages"
dotnet restore
# Create Nuget Package
dotnet pack SabreTools.Skippers/SabreTools.Skippers.csproj --output $BUILD_FOLDER
# Build Headerer
for FRAMEWORK in "${FRAMEWORKS[@]}"; do
for RUNTIME in "${RUNTIMES[@]}"; do
# Output the current build
echo "===== Build Headerer - $FRAMEWORK, $RUNTIME ====="
# If we have an invalid combination of framework and runtime
if [[ ! $(echo ${VALID_CROSS_PLATFORM_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then
if [[ $(echo ${VALID_CROSS_PLATFORM_RUNTIMES[@]} | fgrep -w $RUNTIME) ]]; then
echo "Skipped due to invalid combination"
continue
fi
fi
# If we have Apple silicon but an unsupported framework
if [[ ! $(echo ${VALID_APPLE_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then
if [ $RUNTIME = "osx-arm64" ]; then
echo "Skipped due to no Apple Silicon support"
continue
fi
fi
# Only .NET 5 and above can publish to a single file
if [[ $(echo ${SINGLE_FILE_CAPABLE[@]} | fgrep -w $FRAMEWORK) ]]; then
# Only include Debug if building all
if [ $USE_ALL = true ]; then
dotnet publish Headerer/Headerer.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
fi
dotnet publish Headerer/Headerer.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
else
# Only include Debug if building all
if [ $USE_ALL = true ]; then
dotnet publish Headerer/Headerer.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT
fi
dotnet publish Headerer/Headerer.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:DebugType=None -p:DebugSymbols=false
fi
done
done
fi
# Only create archives if requested
if [ $NO_ARCHIVE = false ]; then
# Create Headerer archives
for FRAMEWORK in "${FRAMEWORKS[@]}"; do
for RUNTIME in "${RUNTIMES[@]}"; do
# Output the current build
echo "===== Archive Headerer - $FRAMEWORK, $RUNTIME ====="
# If we have an invalid combination of framework and runtime
if [[ ! $(echo ${VALID_CROSS_PLATFORM_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then
if [[ $(echo ${VALID_CROSS_PLATFORM_RUNTIMES[@]} | fgrep -w $RUNTIME) ]]; then
echo "Skipped due to invalid combination"
continue
fi
fi
# If we have Apple silicon but an unsupported framework
if [[ ! $(echo ${VALID_APPLE_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then
if [ $RUNTIME = "osx-arm64" ]; then
echo "Skipped due to no Apple Silicon support"
continue
fi
fi
# Only include Debug if building all
if [ $USE_ALL = true ]; then
cd $BUILD_FOLDER/Headerer/bin/Debug/${FRAMEWORK}/${RUNTIME}/publish/
zip -r $BUILD_FOLDER/Headerer_${FRAMEWORK}_${RUNTIME}_debug.zip .
fi
cd $BUILD_FOLDER/Headerer/bin/Release/${FRAMEWORK}/${RUNTIME}/publish/
zip -r $BUILD_FOLDER/Headerer_${FRAMEWORK}_${RUNTIME}_release.zip .
done
done
# Reset the directory
cd $BUILD_FOLDER
fi

131
publish-win.ps1 Normal file
View File

@@ -0,0 +1,131 @@
# This batch file assumes the following:
# - .NET 8.0 (or newer) SDK is installed and in PATH
# - 7-zip commandline (7z.exe) is installed and in PATH
# - Git for Windows is installed and in PATH
#
# If any of these are not satisfied, the operation may fail
# in an unpredictable way and result in an incomplete output.
# Optional parameters
param(
[Parameter(Mandatory = $false)]
[Alias("UseAll")]
[switch]$USE_ALL,
[Parameter(Mandatory = $false)]
[Alias("NoBuild")]
[switch]$NO_BUILD,
[Parameter(Mandatory = $false)]
[Alias("NoArchive")]
[switch]$NO_ARCHIVE
)
# Set the current directory as a variable
$BUILD_FOLDER = $PSScriptRoot
# Set the current commit hash
$COMMIT = git log --pretty=format:"%H" -1
# Output the selected options
Write-Host "Selected Options:"
Write-Host " Use all frameworks (-UseAll) $USE_ALL"
Write-Host " No build (-NoBuild) $NO_BUILD"
Write-Host " No archive (-NoArchive) $NO_ARCHIVE"
Write-Host " "
# Create the build matrix arrays
$FRAMEWORKS = @('net8.0')
$RUNTIMES = @('win-x86', 'win-x64', 'win-arm64', 'linux-x64', 'linux-arm64', 'osx-x64', 'osx-arm64')
# Use expanded lists, if requested
if ($USE_ALL.IsPresent) {
$FRAMEWORKS = @('net48', 'netcoreapp3.1', 'net5.0', 'net6.0', 'net7.0', 'net8.0') # TODO: Support all frameworks
}
# Create the filter arrays
$SINGLE_FILE_CAPABLE = @('net5.0', 'net6.0', 'net7.0', 'net8.0')
$VALID_APPLE_FRAMEWORKS = @('net6.0', 'net7.0', 'net8.0')
$VALID_CROSS_PLATFORM_FRAMEWORKS = @('netcoreapp3.1', 'net5.0', 'net6.0', 'net7.0', 'net8.0')
$VALID_CROSS_PLATFORM_RUNTIMES = @('win-arm64', 'linux-x64', 'linux-arm64', 'osx-x64', 'osx-arm64')
# Only build if requested
if (!$NO_BUILD.IsPresent) {
# Restore Nuget packages for all builds
Write-Host "Restoring Nuget packages"
dotnet restore
# Create Nuget Package
dotnet pack SabreTools.Skippers\SabreTools.Skippers.csproj --output $BUILD_FOLDER
# Build Headerer
foreach ($FRAMEWORK in $FRAMEWORKS) {
foreach ($RUNTIME in $RUNTIMES) {
# Output the current build
Write-Host "===== Build Headerer - $FRAMEWORK, $RUNTIME ====="
# If we have an invalid combination of framework and runtime
if ($VALID_CROSS_PLATFORM_FRAMEWORKS -notcontains $FRAMEWORK -and $VALID_CROSS_PLATFORM_RUNTIMES -contains $RUNTIME) {
Write-Host "Skipped due to invalid combination"
continue
}
# If we have Apple silicon but an unsupported framework
if ($VALID_APPLE_FRAMEWORKS -notcontains $FRAMEWORK -and $RUNTIME -eq 'osx-arm64') {
Write-Host "Skipped due to no Apple Silicon support"
continue
}
# Only .NET 5 and above can publish to a single file
if ($SINGLE_FILE_CAPABLE -contains $FRAMEWORK) {
# Only include Debug if building all
if ($USE_ALL.IsPresent) {
dotnet publish Headerer\Headerer.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
}
dotnet publish Headerer\Headerer.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
}
else {
# Only include Debug if building all
if ($USE_ALL.IsPresent) {
dotnet publish Headerer\Headerer.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT
}
dotnet publish Headerer\Headerer.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:DebugType=None -p:DebugSymbols=false
}
}
}
}
# Only create archives if requested
if (!$NO_ARCHIVE.IsPresent) {
# Create Headerer archives
foreach ($FRAMEWORK in $FRAMEWORKS) {
foreach ($RUNTIME in $RUNTIMES) {
# Output the current build
Write-Host "===== Archive Headerer - $FRAMEWORK, $RUNTIME ====="
# If we have an invalid combination of framework and runtime
if ($VALID_CROSS_PLATFORM_FRAMEWORKS -notcontains $FRAMEWORK -and $VALID_CROSS_PLATFORM_RUNTIMES -contains $RUNTIME) {
Write-Host "Skipped due to invalid combination"
continue
}
# If we have Apple silicon but an unsupported framework
if ($VALID_APPLE_FRAMEWORKS -notcontains $FRAMEWORK -and $RUNTIME -eq 'osx-arm64') {
Write-Host "Skipped due to no Apple Silicon support"
continue
}
# Only include Debug if building all
if ($USE_ALL.IsPresent) {
Set-Location -Path $BUILD_FOLDER\Headerer\bin\Debug\${FRAMEWORK}\${RUNTIME}\publish\
7z a -tzip $BUILD_FOLDER\Headerer_${FRAMEWORK}_${RUNTIME}_debug.zip *
}
Set-Location -Path $BUILD_FOLDER\Headerer\bin\Release\${FRAMEWORK}\${RUNTIME}\publish\
7z a -tzip $BUILD_FOLDER\Headerer_${FRAMEWORK}_${RUNTIME}_release.zip *
}
}
# Reset the directory
Set-Location -Path $PSScriptRoot
}