using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Sandbox.Utility; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; namespace Facepunch; class Program { [DllImport( "user32.dll", CharSet = CharSet.Auto )] private static extern int MessageBox( IntPtr hWnd, string text, string caption, uint type ); static void Main( string[] args ) { //try //{ Run( args ); //} //catch ( System.Exception e ) //{ // MessageBox( default, e.Message, e.StackTrace, 0 ); //} } static void Run( string[] args ) { var sw = Stopwatch.StartNew(); System.IO.Directory.CreateDirectory( "obj/.generated" ); // Get a list of files that already exist in the directory var looseFiles = System.IO.Directory.EnumerateFiles( $"{Environment.CurrentDirectory}/obj/.generated/", "*.cs", System.IO.SearchOption.AllDirectories ).ToList(); // Get a list of files we need to copy over var files = System.IO.Directory.EnumerateFiles( Environment.CurrentDirectory, "*.cs", System.IO.SearchOption.AllDirectories ).Select( x => x.Replace( "\\", "/" ) ).ToArray(); Console.WriteLine( $"[{sw.Elapsed.TotalSeconds:0.00}] Got Files" ); List SyntaxTree = new List(); var targetPaths = new HashSet(); // // Convert each file into a syntaxtree. Unless we have the destination file, and it's newer. // Parallel.ForEach( files, file => { // skip bs if ( file.Contains( "/obj/.generated/" ) ) return; if ( file.Contains( "/obj/" ) ) return; AddTree( SyntaxTree, file, file ); } ); Console.WriteLine( $"[{sw.Elapsed.TotalSeconds:0.00}] Built Syntax Trees" ); var optn = new CSharpCompilationOptions( OutputKind.DynamicallyLinkedLibrary ) .WithConcurrentBuild( true ) .WithOptimizationLevel( OptimizationLevel.Debug ) .WithGeneralDiagnosticOption( ReportDiagnostic.Info ) .WithPlatform( Microsoft.CodeAnalysis.Platform.AnyCpu ) .WithAllowUnsafe( false ); var refs = new List(); var path = System.IO.Path.GetDirectoryName( typeof( System.Object ).Assembly.Location ); refs.Add( MetadataReference.CreateFromFile( typeof( System.Object ).Assembly.Location ) ); refs.Add( MetadataReference.CreateFromFile( Path.Combine( path, "System.Runtime.dll" ) ) ); refs.Add( MetadataReference.CreateFromFile( typeof( Sandbox.CodeGeneratorAttribute ).Assembly.Location ) ); CSharpCompilation compiler = CSharpCompilation.Create( $"CodeGen", SyntaxTree, refs, optn ); // // Process each file // { Console.WriteLine( $"[{sw.Elapsed.TotalSeconds:0.00}] Processsing {SyntaxTree.Count:n0} files ({targetPaths.Count:n0} skipped)" ); var processor = new Sandbox.Generator.Processor(); processor.ILHotloadSupported = false; processor.AddonName = "codegen"; processor.PackageAssetResolver = ( p ) => $"/{p}/model_mock.mdl"; processor.AddonFileMap = files.ToDictionary( x => x, x => GetRelativePath( x, Environment.CurrentDirectory ) ); processor.Run( compiler ); Console.WriteLine( $"[{sw.Elapsed.TotalSeconds:0.00}] Processed {SyntaxTree.Count:n0} files" ); compiler = processor.Compilation; } Parallel.ForEach( compiler.SyntaxTrees, tree => { var writtenPath = WriteFileToOutputFolder( tree.FilePath, tree ); lock ( targetPaths ) { targetPaths.Add( writtenPath ); } } ); Console.WriteLine( $"[{sw.Elapsed.TotalSeconds:0.00}] Wrote files" ); foreach ( var file in looseFiles ) { if ( targetPaths.Contains( file ) ) { continue; } System.IO.File.Delete( file ); Console.WriteLine( $"Deleting unused file - {file}" ); } Console.WriteLine( $"[{sw.Elapsed.TotalSeconds:0.00}] Done" ); } static string GetRelativePath( string filespec, string folder ) { Uri pathUri = new Uri( filespec ); // Folders must end in a slash if ( !folder.EndsWith( Path.DirectorySeparatorChar.ToString() ) ) { folder += Path.DirectorySeparatorChar; } Uri folderUri = new Uri( folder ); return Uri.UnescapeDataString( folderUri.MakeRelativeUri( pathUri ).ToString().Replace( '/', Path.DirectorySeparatorChar ) ); } static string ReadText( string path ) { for ( int i = 0; i < 20; i++ ) { try { return System.IO.File.ReadAllText( path ); } catch { Thread.Sleep( 100 ); // HUH } } throw new System.Exception( $"Couldn't read {path}" ); } static void WriteText( string path, string value ) { Exception _e = default; for ( int i = 0; i < 20; i++ ) { try { System.IO.File.WriteAllText( path, value ); return; } catch ( Exception e ) { Thread.Sleep( 100 ); _e = e; // HUH } } throw new System.Exception( $"Couldn't write {path} ({_e?.Message})" ); } static string GetTargetPath( string file ) { var relativePath = GetRelativePath( file, Environment.CurrentDirectory ); return $"{Environment.CurrentDirectory}/obj/.generated/{relativePath}"; } private static void AddTree( List syntaxTree, string path, string relativePath ) { var targetPath = GetTargetPath( path ); var code = ReadText( path ); var parseOptions = CSharpParseOptions.Default.WithLanguageVersion( LanguageVersion.CSharp11 ); var tree = CSharpSyntaxTree.ParseText( text: code, options: parseOptions, path: relativePath, encoding: System.Text.Encoding.UTF8 ); lock ( syntaxTree ) { syntaxTree.Add( tree ); } } private static string WriteFileToOutputFolder( string file, SyntaxTree tree ) { var targetPath = GetTargetPath( file ); var sourceContent = tree.ToString(); var originalContent = ReadText( file ); var crc = Crc64.FromString( originalContent ); sourceContent = $"// {crc}\n{sourceContent}"; System.IO.Directory.CreateDirectory( System.IO.Path.GetDirectoryName( targetPath ) ); WriteText( targetPath, sourceContent ); return targetPath; } }