forked from SubnauticaNitrox/Nitrox
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improved Nitrox build process (SubnauticaNitrox#1469)
* Improved Nitrox build process - Automatically searches for Subnautica installation on build and caches the result. - Generates publicized assemblies from game files and references them automatically. * Added default logger if no Log.Setup is done
- Loading branch information
Showing
20 changed files
with
487 additions
and
145 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> | ||
<PropertyGroup> | ||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> | ||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> | ||
<ProjectGuid>{15C4C9C4-683C-4EF0-9E0F-0664A3BDA0CE}</ProjectGuid> | ||
<OutputType>Exe</OutputType> | ||
<AppDesignerFolder>Properties</AppDesignerFolder> | ||
<RootNamespace>BuildTool</RootNamespace> | ||
<AssemblyName>Nitrox.BuildTool</AssemblyName> | ||
<FileAlignment>512</FileAlignment> | ||
<!-- BuildTool always builds for debugging. --> | ||
<OutputPath>bin\</OutputPath> | ||
<DebugSymbols>true</DebugSymbols> | ||
<DebugType>full</DebugType> | ||
<Optimize>false</Optimize> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<Reference Include="System" /> | ||
<Reference Include="System.Core" /> | ||
<Reference Include="System.Data" /> | ||
<Reference Include="System.Xml" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<Compile Include="Program.cs" /> | ||
<Compile Include="Publicizer.cs" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<PackageReference Include="Mono.Cecil"> | ||
<Version>0.11.3</Version> | ||
</PackageReference> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<ProjectReference Include="..\NitroxModel\NitroxModel.csproj" /> | ||
</ItemGroup> | ||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
using System; | ||
using System.IO; | ||
using System.Reflection; | ||
using System.Threading.Tasks; | ||
using NitroxModel.Discovery; | ||
|
||
namespace BuildTool | ||
{ | ||
/// <summary> | ||
/// Entry point of the build automation project. | ||
/// 1. Search for Subnautica install. | ||
/// 2. Publicize the .NET dlls and persist for subsequent Nitrox builds. | ||
/// </summary> | ||
public static class Program | ||
{ | ||
private static readonly Lazy<string> processDir = | ||
new(() => Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location ?? Directory.GetCurrentDirectory())); | ||
|
||
public static string ProcessDir => processDir.Value; | ||
|
||
public static string GeneratedOutputDir => Path.Combine(ProcessDir, "generated_files"); | ||
|
||
public static async Task Main(string[] args) | ||
{ | ||
AppDomain.CurrentDomain.UnhandledException += (sender, eventArgs) => | ||
{ | ||
Console.ForegroundColor = ConsoleColor.Red; | ||
Console.WriteLine(eventArgs.ExceptionObject); | ||
Console.ResetColor(); | ||
|
||
Exit((eventArgs.ExceptionObject as Exception)?.HResult ?? 1); | ||
}; | ||
|
||
GameInstallData game = await Task.Factory.StartNew(EnsureGame).ConfigureAwait(false); | ||
Console.WriteLine($"Found game at {game.InstallDir}"); | ||
await Task.Factory.StartNew(() => EnsurePublicizedAssemblies(game)).ConfigureAwait(false); | ||
|
||
Exit(); | ||
} | ||
|
||
private static void Exit(int exitCode = 0) | ||
{ | ||
Console.WriteLine(); | ||
Console.WriteLine("Press any key to continue . . ."); | ||
Console.ReadKey(true); | ||
Environment.Exit(exitCode); | ||
} | ||
|
||
private static GameInstallData EnsureGame() | ||
{ | ||
static bool ValidateUnityGame(GameInstallData game, out string error) | ||
{ | ||
if (!File.Exists(Path.Combine(game.InstallDir, "UnityPlayer.dll"))) | ||
{ | ||
error = $"Game at: '{game.InstallDir}' is not a Unity game"; | ||
return false; | ||
} | ||
if (!Directory.Exists(game.ManagedDllsDir)) | ||
{ | ||
error = $"Invalid Unity managed DLLs directory: {game.ManagedDllsDir}"; | ||
return false; | ||
} | ||
|
||
error = null; | ||
return true; | ||
} | ||
|
||
string cacheFile = Path.Combine(GeneratedOutputDir, "game.props"); | ||
if (GameInstallData.TryFrom(cacheFile, out GameInstallData game) && !ValidateUnityGame(game, out string error)) | ||
{ | ||
throw new Exception(error); | ||
} | ||
|
||
game ??= new GameInstallData(GameInstallationFinder.Instance.FindGame()); | ||
game.TrySave(cacheFile); | ||
return game; | ||
} | ||
|
||
private static void EnsurePublicizedAssemblies(GameInstallData game) | ||
{ | ||
if (Directory.Exists(Path.Combine(GeneratedOutputDir, "publicized_assemblies"))) | ||
{ | ||
Console.WriteLine("Assemblies are already publicized."); | ||
return; | ||
} | ||
|
||
string[] dllsToPublicize = Directory.GetFiles(game.ManagedDllsDir, "Assembly-*.dll"); | ||
foreach (string publicizedDll in Publicizer.Execute(dllsToPublicize, | ||
"", | ||
Path.Combine(GeneratedOutputDir, "publicized_assemblies"))) | ||
{ | ||
Console.WriteLine($"Publicized dll: {publicizedDll}"); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Reflection; | ||
using Mono.Cecil; | ||
|
||
namespace BuildTool | ||
{ | ||
public static class Publicizer | ||
{ | ||
public static IEnumerable<string> Execute(IEnumerable<string> files, string outputSuffix = "", string outputPath = null) | ||
{ | ||
// Ensure target directory exists. | ||
if (string.IsNullOrWhiteSpace(outputPath)) | ||
{ | ||
outputPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); | ||
} | ||
if (!string.IsNullOrWhiteSpace(outputPath)) | ||
{ | ||
Directory.CreateDirectory(outputPath); | ||
} | ||
|
||
// Create dependency resolve for cecil (needed to write dlls that have other dependencies). | ||
DefaultAssemblyResolver resolver = new(); | ||
|
||
foreach (string file in files) | ||
{ | ||
if (!File.Exists(file)) | ||
{ | ||
throw new FileNotFoundException("Dll to publicize not found", file); | ||
} | ||
resolver.AddSearchDirectory(Path.GetDirectoryName(file)); | ||
|
||
string outputName = $"{Path.GetFileNameWithoutExtension(file)}{outputSuffix}{Path.GetExtension(file)}"; | ||
string outputFile = Path.Combine(outputPath, outputName); | ||
Publicize(file, resolver).Write(outputFile); | ||
yield return outputFile; | ||
} | ||
} | ||
|
||
public static IEnumerable<FieldDefinition> FilterBackingEventFields(List<TypeDefinition> allTypes) | ||
{ | ||
List<string> eventNames = allTypes.SelectMany(t => t.Events) | ||
.Select(eventDefinition => eventDefinition.Name) | ||
.ToList(); | ||
|
||
return allTypes.SelectMany(x => x.Fields) | ||
.Where(fieldDefinition => !eventNames.Contains(fieldDefinition.Name)); | ||
} | ||
|
||
/// <summary> | ||
/// Method which returns all Types of the given module, including nested ones (recursively). | ||
/// </summary> | ||
/// <param name="moduleDefinition">.NET module to search through for types.</param> | ||
/// <returns>Types found in module.</returns> | ||
public static IEnumerable<TypeDefinition> GetAllTypes(ModuleDefinition moduleDefinition) => GetAllNestedTypes(moduleDefinition.Types); | ||
|
||
private static AssemblyDefinition Publicize(string file, BaseAssemblyResolver dllResolver) | ||
{ | ||
AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(file, | ||
new ReaderParameters | ||
{ | ||
AssemblyResolver = dllResolver | ||
}); | ||
List<TypeDefinition> allTypes = GetAllTypes(assembly.MainModule).ToList(); | ||
foreach (TypeDefinition type in allTypes) | ||
{ | ||
if (type == null) | ||
{ | ||
continue; | ||
} | ||
|
||
// Publicize type and nested types. | ||
if (!type.IsPublic || !type.IsNestedPublic) | ||
{ | ||
if (type.IsNested) | ||
{ | ||
type.IsNestedPublic = true; | ||
} | ||
else | ||
{ | ||
type.IsPublic = true; | ||
} | ||
} | ||
// Publicize methods on type. | ||
foreach (MethodDefinition method in type.Methods) | ||
{ | ||
if (!method?.IsPublic ?? false) | ||
{ | ||
method.IsPublic = true; | ||
} | ||
} | ||
} | ||
|
||
// Publicize all fields (excludes fields if they would cause name conflicts on a type). | ||
foreach (FieldDefinition field in FilterBackingEventFields(allTypes)) | ||
{ | ||
if (!field?.IsPublic ?? false) | ||
{ | ||
field.IsPublic = true; | ||
} | ||
} | ||
|
||
return assembly; | ||
} | ||
|
||
/// <summary> | ||
/// Recursive method to get all nested types. Use <see cref="GetAllTypes(ModuleDefinition)" /> | ||
/// </summary> | ||
/// <param name="typeDefinitions"></param> | ||
/// <returns></returns> | ||
private static IEnumerable<TypeDefinition> GetAllNestedTypes(IEnumerable<TypeDefinition> typeDefinitions) | ||
{ | ||
IEnumerable<TypeDefinition> defs = typeDefinitions as TypeDefinition[] ?? typeDefinitions.ToArray(); | ||
if (!defs.Any()) | ||
{ | ||
return Array.Empty<TypeDefinition>(); | ||
} | ||
|
||
return defs.Concat(GetAllNestedTypes(defs.SelectMany(t => t.NestedTypes))); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.