mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-15 17:59:22 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
217 lines
8.2 KiB
C#
217 lines
8.2 KiB
C#
using System;
|
|
using System.Reflection;
|
|
|
|
namespace Editor;
|
|
|
|
public static partial class EditorUtility
|
|
{
|
|
public static partial class Projects
|
|
{
|
|
/// <inheritdoc cref="Compile(Project, Compiler.Configuration, Action{string})"/>
|
|
public static async Task<CompilerOutput[]> Compile( Project project, Action<string> logOutput )
|
|
=> await Compile( project, project.Config.GetCompileSettings(), logOutput );
|
|
|
|
/// <summary>
|
|
/// Compiled a project ready to be published. Will return the compiled result and any CodeArchives
|
|
/// </summary>
|
|
internal static async Task<CompilerOutput[]> Compile( Project project, Compiler.Configuration compilerSettings, Action<string> logOutput )
|
|
{
|
|
bool isGame = project.Config.Type == "game";
|
|
var codePath = project.GetCodePath();
|
|
|
|
if ( !System.IO.Directory.Exists( codePath ) )
|
|
return default;
|
|
|
|
using CompileGroup compileGroup = new CompileGroup( "Publish" );
|
|
compileGroup.AccessControl = PackageManager.AccessControl;
|
|
|
|
compilerSettings.IgnoreFolders.Add( "editor" );
|
|
compilerSettings.IgnoreFolders.Add( "unittest" );
|
|
compilerSettings.ReleaseMode = Compiler.ReleaseMode.Release;
|
|
compilerSettings.StripDisabledTextTrivia = true;
|
|
|
|
var compiler = compileGroup.CreateCompiler( $"{project.Config.Org}.{project.Config.Ident}", codePath, compilerSettings );
|
|
compiler.UseAbsoluteSourcePaths = false;
|
|
|
|
//Log.Info( $"Code Path: {addon.GetCodePath()}" );
|
|
|
|
bool hasBase = false;
|
|
|
|
//
|
|
// Install any libraries (unless we are a library)
|
|
//
|
|
if ( project.Config.Type != "library" )
|
|
{
|
|
foreach ( var (library, references) in SortLibrariesForCompilation( Project.Libraries, logOutput ) )
|
|
{
|
|
await PackageManager.InstallAsync( new PackageLoadOptions( library.Package.FullIdent, "publish" ) );
|
|
|
|
var compilerName = GetLibraryCompilerName( library );
|
|
|
|
// Compile library
|
|
{
|
|
var compileSettings = new Compiler.Configuration();
|
|
compileSettings.Clean();
|
|
compileSettings.ReleaseMode = Compiler.ReleaseMode.Release;
|
|
|
|
var libCompiler = compileGroup.CreateCompiler( compilerName, library.GetCodePath(), compileSettings );
|
|
libCompiler.UseAbsoluteSourcePaths = false;
|
|
libCompiler.GeneratedCode.AppendLine( "global using static Sandbox.Internal.GlobalGameNamespace;" );
|
|
|
|
// Required by razor
|
|
libCompiler.GeneratedCode.AppendLine( "global using Microsoft.AspNetCore.Components;" );
|
|
libCompiler.GeneratedCode.AppendLine( "global using Microsoft.AspNetCore.Components.Rendering;" );
|
|
|
|
libCompiler.AddReference( "package.base" );
|
|
|
|
foreach ( var reference in references )
|
|
{
|
|
libCompiler.AddReference( "package." + GetLibraryCompilerName( reference ) );
|
|
}
|
|
}
|
|
|
|
// add a reference to it from the main compiler
|
|
{
|
|
compiler.AddReference( "package." + compilerName );
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// if we're a game or an addon then put the base code in the base
|
|
//
|
|
if ( !hasBase )
|
|
{
|
|
var baseSettings = new Compiler.Configuration();
|
|
baseSettings.Clean();
|
|
baseSettings.ReleaseMode = Compiler.ReleaseMode.Release;
|
|
|
|
logOutput?.Invoke( "Adding package.base to compiler" );
|
|
|
|
var baseCompiler = compileGroup.CreateCompiler( "base", EngineFileSystem.Root.GetFullPath( "/addons/base/code/" ), baseSettings );
|
|
baseCompiler.UseAbsoluteSourcePaths = false;
|
|
baseCompiler.GeneratedCode.AppendLine( "global using static Sandbox.Internal.GlobalGameNamespace;" );
|
|
|
|
// Required by razor
|
|
baseCompiler.GeneratedCode.AppendLine( "global using Microsoft.AspNetCore.Components;" );
|
|
baseCompiler.GeneratedCode.AppendLine( "global using Microsoft.AspNetCore.Components.Rendering;" );
|
|
|
|
// reference this from the main compiler
|
|
compiler.AddReference( "package.base" );
|
|
}
|
|
|
|
logOutput?.Invoke( $"Creating package.{project.Config.Org}.{project.Config.Ident} compiler" );
|
|
logOutput?.Invoke( $"Compile path is {project.GetCodePath()}" );
|
|
|
|
logOutput?.Invoke( $"Generating code.." );
|
|
compiler.GeneratedCode.AppendLine( "global using static Sandbox.Internal.GlobalGameNamespace;" );
|
|
|
|
// Required by razor
|
|
compiler.GeneratedCode.AppendLine( "global using Microsoft.AspNetCore.Components;" );
|
|
compiler.GeneratedCode.AppendLine( "global using Microsoft.AspNetCore.Components.Rendering;" );
|
|
|
|
foreach ( var c in compileGroup.Compilers )
|
|
{
|
|
// important - this is what is used by the error system to determine which addon it came from
|
|
// we really only want to set this metadata on assemblies that are being published, that way
|
|
// we can skip reporting errors in addons that are being developed locally, and only get the
|
|
// count of "in the wild" errors.
|
|
|
|
c.GeneratedCode.AppendLine( $"[assembly: global::System.Reflection.AssemblyMetadata( \"AddonTitle\", {project.Config.Title.QuoteSafe()} )]" );
|
|
c.GeneratedCode.AppendLine( $"[assembly: global::System.Reflection.AssemblyMetadata( \"AddonIdent\", {project.Config.Ident.QuoteSafe()} )]" );
|
|
c.GeneratedCode.AppendLine( $"[assembly: global::System.Reflection.AssemblyMetadata( \"OrgIdent\", {project.Config.Org.QuoteSafe()} )]" );
|
|
c.GeneratedCode.AppendLine( $"[assembly: global::System.Reflection.AssemblyMetadata( \"Ident\", {project.Config.FullIdent.QuoteSafe()} )]" );
|
|
c.GeneratedCode.AppendLine( $"[assembly: global::System.Reflection.AssemblyMetadata( \"CompileTime\", {System.DateTime.UtcNow.ToString().QuoteSafe()} )]" );
|
|
c.GeneratedCode.AppendLine( $"[assembly: global::System.Reflection.AssemblyMetadata( \"EngineVersion\", {Sandbox.Engine.Protocol.Api.ToString().QuoteSafe()} )]" );
|
|
c.GeneratedCode.AppendLine( $"[assembly: global::System.Reflection.AssemblyMetadata( \"EngineMinorVersion\", {1.ToString().QuoteSafe()} )]" );
|
|
}
|
|
|
|
logOutput?.Invoke( $"Building.." );
|
|
await compileGroup.BuildAsync();
|
|
|
|
PackageManager.UnmountTagged( "publish" );
|
|
|
|
logOutput?.Invoke( $"Done.." );
|
|
|
|
if ( !compiler.BuildSuccess )
|
|
{
|
|
logOutput?.Invoke( $"COMPILE FAILED!!" );
|
|
logOutput?.Invoke( $"" );
|
|
logOutput?.Invoke( compileGroup.BuildResult.BuildDiagnosticsString() );
|
|
logOutput?.Invoke( $"" );
|
|
|
|
throw new System.InvalidOperationException( "Compiling failed. Can't continue." );
|
|
}
|
|
|
|
logOutput?.Invoke( $"Success!" );
|
|
|
|
return compileGroup.BuildResult.Output.ToArray();
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resolve a compiler from an assembly, using the assembly name
|
|
/// </summary>
|
|
public static Compiler ResolveCompiler( Assembly assembly )
|
|
{
|
|
return Project.ResolveCompiler( assembly );
|
|
}
|
|
|
|
private static string GetLibraryCompilerName( Project library )
|
|
{
|
|
return "library." + library.Package.GetIdent( false, false );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds the compilation order for any of the given libraries containing code,
|
|
/// based on which libraries reference others.
|
|
/// </summary>
|
|
private static IReadOnlyList<(Project Project, IReadOnlyList<Project> References)> SortLibrariesForCompilation( IEnumerable<Project> libraries, Action<string> logOutput )
|
|
{
|
|
libraries = libraries.Where( x => x.HasCodePath() );
|
|
|
|
var identMap = libraries
|
|
.DistinctBy( x => x.Package.GetIdent( false, false ) )
|
|
.ToDictionary( x => x.Package.GetIdent( false, false ), x => x,
|
|
StringComparer.OrdinalIgnoreCase );
|
|
|
|
IReadOnlyList<Project> FindReferences( Project project )
|
|
{
|
|
return project.Package.PackageReferences
|
|
.Select( refName => identMap.TryGetValue( refName, out var reference ) ? reference : null )
|
|
.Where( x => x is not null )
|
|
.ToArray();
|
|
}
|
|
|
|
var remaining = identMap.Values
|
|
.Select( project => (Project: project, References: FindReferences( project )) )
|
|
.ToList();
|
|
|
|
var sorted = new List<(Project Project, IReadOnlyList<Project> References)>();
|
|
var added = new HashSet<Project>();
|
|
|
|
while ( remaining.Any() )
|
|
{
|
|
var next = remaining.MinBy(
|
|
x => x.References.Count( y => !added.Contains( y ) ) );
|
|
|
|
var cyclicReferences = next.References
|
|
.Where( y => !added.Contains( y ) )
|
|
.ToArray();
|
|
|
|
if ( cyclicReferences.Any() )
|
|
{
|
|
logOutput?.Invoke( $"Cyclic library dependency: {next.Project.Package.FullIdent}, " +
|
|
$"{string.Join( ", ", cyclicReferences.Select( x => x.Package.FullIdent ) )}" );
|
|
}
|
|
|
|
sorted.Add( next );
|
|
remaining.Remove( next );
|
|
added.Add( next.Project );
|
|
}
|
|
|
|
return sorted;
|
|
}
|
|
}
|
|
}
|