2024-07-21 04:38:31 +02:00
using System.Diagnostics ;
using System.Reflection ;
using System.Runtime.InteropServices ;
using System.Text ;
using System.Text.Json ;
namespace Nerfed.Compiler ;
public static class Compiler
{
public const string CSProjectFileName = ".csproject" ;
public const string CSProjFileName = ".csproj" ;
public static bool Compile ( string projectFilePath , string configuration = "Debug" )
{
string projectPath = Path . GetDirectoryName ( projectFilePath ) ;
if ( ! File . Exists ( projectFilePath ) )
{
Console . WriteLine ( $"ERROR: Project file not found at {projectPath}." ) ;
return false ;
}
if ( ! Project . Open ( projectFilePath , out Project project ) )
{
return false ;
}
// TODO: Check project version, to make sure we can compile it or something...
// Generate solution.
GenerateSolution ( projectPath , project , out string solutionFilePath ) ;
// Compile solution.
ProcessStartInfo processInfo = new ( )
{
WorkingDirectory = Path . GetDirectoryName ( solutionFilePath ) ,
CreateNoWindow = true ,
UseShellExecute = false ,
RedirectStandardError = true ,
RedirectStandardOutput = true ,
} ;
if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) )
{
processInfo . FileName = "/bin/bash" ;
processInfo . Arguments = $"-c \" dotnet build ' { Path . GetFileName ( solutionFilePath ) } ' \ "" + ( string . IsNullOrWhiteSpace ( configuration ) ? $" --configuration {configuration}" : "" ) ;
}
else if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
{
processInfo . FileName = "cmd.exe" ;
processInfo . Arguments = $"/c dotnet build \" { Path . GetFileName ( solutionFilePath ) } \ "" + ( string . IsNullOrWhiteSpace ( configuration ) ? $" --configuration {configuration}" : "" ) ;
}
else
{
2024-07-21 14:03:40 +02:00
Console . WriteLine ( $"ERROR: Platform not supported!" ) ;
2024-07-21 04:38:31 +02:00
return false ;
}
Process process = Process . Start ( processInfo ) ? ? throw new Exception ( ) ;
process . OutputDataReceived + = ( sender , dataArgs ) = > {
string data = dataArgs . Data ;
if ( data is null )
{
return ;
}
Console . WriteLine ( data ) ;
} ;
process . BeginOutputReadLine ( ) ;
process . BeginErrorReadLine ( ) ;
process . ErrorDataReceived + = ( sender , dataArgs ) = > {
if ( dataArgs . Data is not null )
{
Console . WriteLine ( dataArgs . Data ) ;
}
} ;
process . WaitForExit ( ) ;
int exitCode = process . ExitCode ;
process . Close ( ) ;
return true ;
}
public static void GenerateSolution ( string projectPath , Project project , out string solutionPath )
{
// Clear files.
2024-07-21 22:31:04 +02:00
string [ ] csProjectFiles = Directory . GetFiles ( projectPath , $"*{CSProjFileName}" , SearchOption . TopDirectoryOnly ) ;
2024-07-21 04:38:31 +02:00
foreach ( string csProjFile in csProjectFiles )
{
File . Delete ( csProjFile ) ;
}
// Generate projects.
string [ ] csProjectFilePaths = Directory . GetFiles ( projectPath , CSProjectFileName , SearchOption . AllDirectories ) ;
foreach ( string csProjectFilePath in csProjectFilePaths )
{
GenerateCSProject ( csProjectFilePath , projectPath ) ;
}
// Generate solution.
2024-07-21 22:31:04 +02:00
string [ ] csProjPaths = Directory . GetFiles ( projectPath , $"*{CSProjFileName}" , SearchOption . TopDirectoryOnly ) ;
2024-07-21 04:38:31 +02:00
string [ ] csProjGuids = new string [ csProjPaths . Length ] ;
for ( int i = 0 ; i < csProjPaths . Length ; i + + )
{
csProjGuids [ i ] = Guid . NewGuid ( ) . ToString ( "B" ) . ToUpper ( ) ;
}
StringBuilder solutionContent = new StringBuilder ( ) ;
// Write the solution file header
solutionContent . AppendLine ( "Microsoft Visual Studio Solution File, Format Version 12.00" ) ;
solutionContent . AppendLine ( "# Visual Studio Version 17" ) ;
solutionContent . AppendLine ( "VisualStudioVersion = 17.10.35013.160" ) ;
solutionContent . AppendLine ( "MinimumVisualStudioVersion = 10.0.40219.1" ) ;
// Add each project to the solution file
for ( int i = 0 ; i < csProjPaths . Length ; i + + )
{
string csProjPath = csProjPaths [ i ] ;
string projectGuid = csProjGuids [ i ] ;
string projectName = Path . GetFileNameWithoutExtension ( csProjPath ) ;
2024-07-21 22:31:04 +02:00
string projectRelativePath = Path . GetRelativePath ( projectPath , csProjPath ) ;
2024-07-21 04:38:31 +02:00
// FAE04EC0-301F-11D3-BF4B-00C04F79EFBC for C# projects.
2024-07-21 22:31:04 +02:00
solutionContent . AppendLine ( $"Project(\" { { FAE04EC0 - 301F - 11D 3 - BF4B - 00 C04F79EFBC } } \ ") = \"{projectName}\", \"{projectRelativePath}\", \"{projectGuid}\"" ) ;
2024-07-21 04:38:31 +02:00
solutionContent . AppendLine ( "EndProject" ) ;
}
// Add global sections (these can be extended as needed)
solutionContent . AppendLine ( "Global" ) ;
solutionContent . AppendLine ( " GlobalSection(SolutionConfigurationPlatforms) = preSolution" ) ;
solutionContent . AppendLine ( " Test|x64 = Test|x64" ) ;
solutionContent . AppendLine ( " Release|x64 = Release|x64" ) ;
solutionContent . AppendLine ( " Debug|x64 = Debug|x64" ) ;
solutionContent . AppendLine ( " EndGlobalSection" ) ;
solutionContent . AppendLine ( " GlobalSection(ProjectConfigurationPlatforms) = postSolution" ) ;
for ( int i = 0 ; i < csProjPaths . Length ; i + + )
{
string projectGuid = csProjGuids [ i ] ;
solutionContent . AppendLine ( $" {projectGuid}.Test|x64.ActiveCfg = Test|x64" ) ;
solutionContent . AppendLine ( $" {projectGuid}.Test|x64.Build.0 = Test|x64" ) ;
solutionContent . AppendLine ( $" {projectGuid}.Release|x64.ActiveCfg = Release|x64" ) ;
solutionContent . AppendLine ( $" {projectGuid}.Release|x64.Build.0 = Release|x64" ) ;
solutionContent . AppendLine ( $" {projectGuid}.Debug|x64.ActiveCfg = Debug|x64" ) ;
solutionContent . AppendLine ( $" {projectGuid}.Debug|x64.Build.0 = Debug|x64" ) ;
}
solutionContent . AppendLine ( " EndGlobalSection" ) ;
solutionContent . AppendLine ( " GlobalSection(SolutionProperties) = preSolution" ) ;
solutionContent . AppendLine ( " HideSolutionNode = FALSE" ) ;
solutionContent . AppendLine ( " EndGlobalSection" ) ;
solutionContent . AppendLine ( "EndGlobal" ) ;
// Write the solution file content to disk
string solutionName = project . Name + ".sln" ;
string filePath = Path . Combine ( projectPath , solutionName ) ;
File . WriteAllText ( filePath , solutionContent . ToString ( ) ) ;
solutionPath = filePath ;
}
private static void GenerateCSProject ( string csProjectFilePath , string projectPath )
{
if ( ! File . Exists ( csProjectFilePath ) )
{
return ;
}
string jsonString = File . ReadAllText ( csProjectFilePath ) ;
CSProject csProject = JsonSerializer . Deserialize ( jsonString , CSProjectContext . Default . CSProject ) ;
Assembly [ ] loadedAssemblies = AppDomain . CurrentDomain . GetAssemblies ( ) ;
Assembly runtimeAssembly = loadedAssemblies . FirstOrDefault ( assembly = > assembly . GetName ( ) . Name = = "Nerfed.Runtime" ) ? ? throw new Exception ( "Failed to find Runtime Assembly!" ) ;
// TODO: get all dependencies.
// TODO: properly get assemblies.
StringBuilder projectContent = new StringBuilder ( ) ;
projectContent . AppendLine ( "<Project Sdk=\"Microsoft.NET.Sdk\">" ) ;
projectContent . AppendLine ( " <PropertyGroup>" ) ;
projectContent . AppendLine ( " <TargetFramework>net8.0</TargetFramework>" ) ;
projectContent . AppendLine ( " <ImplicitUsings>enable</ImplicitUsings>" ) ;
projectContent . AppendLine ( " <Nullable>disable</Nullable>" ) ;
projectContent . AppendLine ( " <PublishAot>true</PublishAot>" ) ;
projectContent . AppendLine ( " <InvariantGlobalization>true</InvariantGlobalization>" ) ;
projectContent . AppendLine ( " <AllowUnsafeBlocks>true</AllowUnsafeBlocks>" ) ;
projectContent . AppendLine ( " <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>" ) ;
projectContent . AppendLine ( " <IsPackable>false</IsPackable>" ) ;
projectContent . AppendLine ( " <Configurations>Debug;Test;Release</Configurations>" ) ;
projectContent . AppendLine ( " <Platforms>x64</Platforms>" ) ;
projectContent . AppendLine ( " </PropertyGroup>" ) ;
projectContent . AppendLine ( " <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Debug|x64' \">" ) ;
projectContent . AppendLine ( " <DefineConstants>TRACE;LOG_INFO;PROFILING</DefineConstants>" ) ;
projectContent . AppendLine ( " </PropertyGroup>" ) ;
projectContent . AppendLine ( " <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Test|x64' \">" ) ;
projectContent . AppendLine ( " <DefineConstants>TRACE;LOG_ERROR;PROFILING</DefineConstants>" ) ;
projectContent . AppendLine ( " <Optimize>true</Optimize>" ) ;
projectContent . AppendLine ( " </PropertyGroup>" ) ;
projectContent . AppendLine ( " <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Release|x64' \">" ) ;
projectContent . AppendLine ( " <DefineConstants>TRACE;LOG_ERROR</DefineConstants>" ) ;
projectContent . AppendLine ( " <Optimize>true</Optimize>" ) ;
projectContent . AppendLine ( " </PropertyGroup>" ) ;
projectContent . AppendLine ( " <ItemGroup>" ) ;
projectContent . AppendLine ( $" <Compile Include=\" { csProjectFilePath } /**/ * . cs \ "/>" ) ;
projectContent . AppendLine ( " </ItemGroup>" ) ;
projectContent . AppendLine ( " <ItemGroup>" ) ;
projectContent . AppendLine ( " <Reference Include=\"Nerfed.Runtime\">" ) ;
projectContent . AppendLine ( $" <HintPath>{runtimeAssembly.Location}</HintPath>" ) ;
projectContent . AppendLine ( " <Private>false</Private>" ) ;
projectContent . AppendLine ( " </Reference>" ) ;
projectContent . AppendLine ( " </ItemGroup>" ) ;
projectContent . AppendLine ( "</Project>" ) ;
2024-07-21 22:31:04 +02:00
string projectName = csProject . Name + ".csproj" ;
2024-07-21 04:38:31 +02:00
string filePath = Path . Combine ( projectPath , projectName ) ;
File . WriteAllText ( filePath , projectContent . ToString ( ) ) ;
}
}