ModernUO/Server/ScriptCompiler.cs
2006-06-15 04:14:30 +00:00

640 lines
No EOL
18 KiB
C#

/***************************************************************************
* ScriptCompiler.cs
* -------------------
* begin : May 1, 2002
* copyright : (C) The RunUO Software Team
* email : info@runuo.com
*
* $Id: ScriptCompiler.cs 163 2006-06-15 00:59:08Z krrios $
*
***************************************************************************/
/***************************************************************************
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
***************************************************************************/
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Reflection;
using Microsoft.CSharp;
using Microsoft.VisualBasic;
namespace Server {
public class ScriptCompiler {
private static Assembly[] m_Assemblies;
public static Assembly[] Assemblies {
get {
return m_Assemblies;
}
set {
m_Assemblies = value;
}
}
private static List<string> m_AdditionalReferences = new List<string>();
public static string[] GetReferenceAssemblies() {
List<string> list = new List<string>();
string path = Path.Combine( Core.BaseDirectory, "Data/Assemblies.cfg" );
if ( File.Exists( path ) ) {
using ( StreamReader ip = new StreamReader( path ) ) {
string line;
while ( ( line = ip.ReadLine() ) != null ) {
if ( line.Length > 0 && !line.StartsWith( "#" ) )
list.Add( line );
}
}
}
list.Add( Core.ExePath );
list.AddRange( m_AdditionalReferences );
return list.ToArray();
}
public static string GetDefines() {
StringBuilder sb = null;
#if MONO
AppendDefine( ref sb, "MONO" );
#endif
//These two defines are legacy, ie, depreciated.
if ( Core.Is64Bit )
AppendDefine( ref sb, "x64" );
AppendDefine( ref sb, "Framework_2_0" );
return ( sb == null ? null : sb.ToString() );
}
public static void AppendDefine( ref StringBuilder sb, string define ) {
if ( sb == null )
sb = new StringBuilder();
else
sb.Append( ';' );
sb.Append( define );
}
private static byte[] GetHashCode( string compiledFile, string[] scriptFiles, bool debug ) {
using ( MemoryStream ms = new MemoryStream() ) {
using ( BinaryWriter bin = new BinaryWriter( ms ) ) {
FileInfo fileInfo = new FileInfo( compiledFile );
bin.Write( fileInfo.LastWriteTimeUtc.Ticks );
foreach ( string scriptFile in scriptFiles ) {
fileInfo = new FileInfo( scriptFile );
bin.Write( fileInfo.LastWriteTimeUtc.Ticks );
}
bin.Write( debug );
ms.Position = 0;
using ( System.Security.Cryptography.SHA1 sha1 = System.Security.Cryptography.SHA1.Create() ) {
return sha1.ComputeHash( ms );
}
}
}
}
public static bool CompileCSScripts( out Assembly assembly ) {
return CompileCSScripts( false, out assembly );
}
public static bool CompileCSScripts( bool debug, out Assembly assembly ) {
Console.Write( "Scripts: Compiling C# scripts..." );
string[] files = GetScripts( "*.cs" );
if ( files.Length == 0 ) {
Console.WriteLine( "no files found." );
assembly = null;
return true;
}
if ( File.Exists( "Scripts/Output/Scripts.CS.dll" ) ) {
if ( File.Exists( "Scripts/Output/Scripts.CS.hash" ) ) {
try {
byte[] hashCode = GetHashCode( "Scripts/Output/Scripts.CS.dll", files, debug );
using ( FileStream fs = new FileStream( "Scripts/Output/Scripts.CS.hash", FileMode.Open, FileAccess.Read, FileShare.Read ) ) {
using ( BinaryReader bin = new BinaryReader( fs ) ) {
byte[] bytes = bin.ReadBytes( hashCode.Length );
if ( bytes.Length == hashCode.Length ) {
bool valid = true;
for ( int i = 0; i < bytes.Length; ++i ) {
if ( bytes[i] != hashCode[i] ) {
valid = false;
break;
}
}
if ( valid ) {
assembly = Assembly.LoadFrom( "Scripts/Output/Scripts.CS.dll" );
if ( !m_AdditionalReferences.Contains( assembly.Location ) ) {
m_AdditionalReferences.Add( assembly.Location );
}
Console.WriteLine( "done (cached)" );
return true;
}
}
}
}
} catch {
}
}
}
DeleteFiles( "Scripts.CS*.dll" );
using ( CSharpCodeProvider provider = new CSharpCodeProvider() ) {
string path = GetUnusedPath( "Scripts.CS" );
CompilerParameters parms = new CompilerParameters( GetReferenceAssemblies(), path, debug );
string defines = GetDefines();
if ( defines != null )
parms.CompilerOptions = string.Format( "/D:{0}", defines );
CompilerResults results = provider.CompileAssemblyFromFile( parms, files );
m_AdditionalReferences.Add( path );
Display( results );
if ( results.Errors.Count > 0 ) {
assembly = null;
return false;
}
if ( Path.GetFileName( path ) == "Scripts.CS.dll" ) {
try {
byte[] hashCode = GetHashCode( path, files, debug );
using ( FileStream fs = new FileStream( "Scripts/Output/Scripts.CS.hash", FileMode.Create, FileAccess.Write, FileShare.None ) ) {
using ( BinaryWriter bin = new BinaryWriter( fs ) ) {
bin.Write( hashCode, 0, hashCode.Length );
}
}
} catch {
}
}
assembly = results.CompiledAssembly;
return true;
}
}
public static bool CompileVBScripts( out Assembly assembly ) {
return CompileVBScripts( false, out assembly );
}
public static bool CompileVBScripts( bool debug, out Assembly assembly ) {
Console.Write( "Scripts: Compiling VB.NET scripts..." );
string[] files = GetScripts( "*.vb" );
if ( files.Length == 0 ) {
Console.WriteLine( "no files found." );
assembly = null;
return true;
}
if ( File.Exists( "Scripts/Output/Scripts.VB.dll" ) ) {
if ( File.Exists( "Scripts/Output/Scripts.VB.hash" ) ) {
byte[] hashCode = GetHashCode( "Scripts/Output/Scripts.VB.dll", files, debug );
try {
using ( FileStream fs = new FileStream( "Scripts/Output/Scripts.VB.hash", FileMode.Open, FileAccess.Read, FileShare.Read ) ) {
using ( BinaryReader bin = new BinaryReader( fs ) ) {
byte[] bytes = bin.ReadBytes( hashCode.Length );
if ( bytes.Length == hashCode.Length ) {
bool valid = true;
for ( int i = 0; i < bytes.Length; ++i ) {
if ( bytes[i] != hashCode[i] ) {
valid = false;
break;
}
}
if ( valid ) {
assembly = Assembly.LoadFrom( "Scripts/Output/Scripts.VB.dll" );
if ( !m_AdditionalReferences.Contains( assembly.Location ) ) {
m_AdditionalReferences.Add( assembly.Location );
}
Console.WriteLine( "done (cached)" );
return true;
}
}
}
}
} catch {
}
}
}
DeleteFiles( "Scripts.VB*.dll" );
using ( VBCodeProvider provider = new VBCodeProvider() ) {
string path = GetUnusedPath( "Scripts.VB" );
CompilerParameters parms = new CompilerParameters( GetReferenceAssemblies(), path, debug );
string defines = GetDefines();
if ( defines != null )
parms.CompilerOptions = String.Format( "/D:{0}", defines );
CompilerResults results = provider.CompileAssemblyFromFile( parms, files );
m_AdditionalReferences.Add( path );
Display( results );
if ( results.Errors.Count > 0 ) {
assembly = null;
return false;
}
if ( Path.GetFileName( path ) == "Scripts.VB.dll" ) {
try {
byte[] hashCode = GetHashCode( path, files, debug );
using ( FileStream fs = new FileStream( "Scripts/Output/Scripts.VB.hash", FileMode.Create, FileAccess.Write, FileShare.None ) ) {
using ( BinaryWriter bin = new BinaryWriter( fs ) ) {
bin.Write( hashCode, 0, hashCode.Length );
}
}
} catch {
}
}
assembly = results.CompiledAssembly;
return true;
}
}
public static void Display( CompilerResults results ) {
if ( results.Errors.Count > 0 ) {
Dictionary<string, List<CompilerError>> errors = new Dictionary<string, List<CompilerError>>( results.Errors.Count, StringComparer.OrdinalIgnoreCase );
Dictionary<string, List<CompilerError>> warnings = new Dictionary<string, List<CompilerError>>( results.Errors.Count, StringComparer.OrdinalIgnoreCase );
foreach ( CompilerError e in results.Errors ) {
Dictionary<string, List<CompilerError>> table = ( e.IsWarning ? warnings : errors );
List<CompilerError> list = null;
table.TryGetValue( e.FileName, out list );
if ( list == null )
table[e.FileName] = list = new List<CompilerError>();
list.Add( e );
}
if ( errors.Count > 0 )
Console.WriteLine( "failed ({0} errors, {1} warnings)", errors.Count, warnings.Count );
else
Console.WriteLine( "done ({0} errors, {1} warnings)", errors.Count, warnings.Count );
string scriptRoot = Path.GetFullPath( Path.Combine( Core.BaseDirectory, "Scripts" + Path.DirectorySeparatorChar ) );
Uri scriptRootUri = new Uri( scriptRoot );
Utility.PushColor( ConsoleColor.Yellow );
if ( warnings.Count > 0 )
Console.WriteLine( "Warnings:" );
foreach ( KeyValuePair<string, List<CompilerError>> kvp in warnings ) {
string fileName = kvp.Key;
List<CompilerError> list = kvp.Value;
string fullPath = Path.GetFullPath( fileName );
string usedPath = Uri.UnescapeDataString( scriptRootUri.MakeRelativeUri( new Uri( fullPath ) ).OriginalString );
Console.WriteLine( " + {0}:", usedPath );
Utility.PushColor( ConsoleColor.DarkYellow );
foreach ( CompilerError e in list )
Console.WriteLine( " {0}: Line {1}: {3}", e.ErrorNumber, e.Line, e.Column, e.ErrorText );
Utility.PopColor();
}
Utility.PopColor();
Utility.PushColor( ConsoleColor.Red );
if ( errors.Count > 0 )
Console.WriteLine( "Errors:" );
foreach ( KeyValuePair<string, List<CompilerError>> kvp in errors ) {
string fileName = kvp.Key;
List<CompilerError> list = kvp.Value;
string fullPath = Path.GetFullPath( fileName );
string usedPath = Uri.UnescapeDataString( scriptRootUri.MakeRelativeUri( new Uri( fullPath ) ).OriginalString );
Console.WriteLine( " + {0}:", usedPath );
Utility.PushColor( ConsoleColor.DarkRed );
foreach ( CompilerError e in list )
Console.WriteLine( " {0}: Line {1}: {3}", e.ErrorNumber, e.Line, e.Column, e.ErrorText );
Utility.PopColor();
}
Utility.PopColor();
} else {
Console.WriteLine( "done (0 errors, 0 warnings)" );
}
}
public static string GetUnusedPath( string name ) {
string path = Path.Combine( Core.BaseDirectory, String.Format( "Scripts/Output/{0}.dll", name ) );
for ( int i = 2; File.Exists( path ) && i <= 1000; ++i )
path = Path.Combine( Core.BaseDirectory, String.Format( "Scripts/Output/{0}.{1}.dll", name, i ) );
return path;
}
public static void DeleteFiles( string mask ) {
try {
string[] files = Directory.GetFiles( Path.Combine( Core.BaseDirectory, "Scripts/Output" ), mask );
foreach ( string file in files ) {
try { File.Delete( file ); } catch { }
}
} catch {
}
}
private delegate CompilerResults Compiler( bool debug );
private void LoadScriptedAssembly( List<Assembly> assemblies, string fileName, Compiler compiler, bool debug ) {
if ( File.Exists( fileName ) ) {
}
}
public static bool Compile() {
return Compile( false );
}
public static bool Compile( bool debug ) {
EnsureDirectory( "Scripts/" );
EnsureDirectory( "Scripts/Output/" );
if ( m_AdditionalReferences.Count > 0 )
m_AdditionalReferences.Clear();
List<Assembly> assemblies = new List<Assembly>();
Assembly assembly;
if ( CompileCSScripts( debug, out assembly ) ) {
if ( assembly != null ) {
assemblies.Add( assembly );
}
} else {
return false;
}
if ( CompileVBScripts( debug, out assembly ) ) {
if ( assembly != null ) {
assemblies.Add( assembly );
}
} else {
return false;
}
if ( assemblies.Count == 0 ) {
return false;
}
m_Assemblies = assemblies.ToArray();
Console.Write( "Scripts: Verifying..." );
Core.VerifySerialization();
Console.WriteLine( "done ({0} items, {1} mobiles)", Core.ScriptItems, Core.ScriptMobiles );
List<MethodInfo> invoke = new List<MethodInfo>();
for ( int a = 0; a < m_Assemblies.Length; ++a ) {
Type[] types = m_Assemblies[a].GetTypes();
for ( int i = 0; i < types.Length; ++i ) {
MethodInfo m = types[i].GetMethod( "Configure", BindingFlags.Static | BindingFlags.Public );
if ( m != null )
invoke.Add( m );
}
}
invoke.Sort( new CallPriorityComparer() );
for ( int i = 0; i < invoke.Count; ++i )
invoke[i].Invoke( null, null );
invoke.Clear();
Region.Load();
World.Load();
for ( int a = 0; a < m_Assemblies.Length; ++a ) {
Type[] types = m_Assemblies[a].GetTypes();
for ( int i = 0; i < types.Length; ++i ) {
MethodInfo m = types[i].GetMethod( "Initialize", BindingFlags.Static | BindingFlags.Public );
if ( m != null )
invoke.Add( m );
}
}
invoke.Sort( new CallPriorityComparer() );
for ( int i = 0; i < invoke.Count; ++i )
invoke[i].Invoke( null, null );
return true;
}
private static Dictionary<Assembly, TypeCache> m_TypeCaches = new Dictionary<Assembly, TypeCache>();
private static TypeCache m_NullCache;
public static TypeCache GetTypeCache( Assembly asm ) {
if ( asm == null ) {
if ( m_NullCache == null )
m_NullCache = new TypeCache( null );
return m_NullCache;
}
TypeCache c = null;
m_TypeCaches.TryGetValue( asm, out c );
if ( c == null )
m_TypeCaches[asm] = c = new TypeCache( asm );
return c;
}
public static Type FindTypeByFullName( string fullName ) {
return FindTypeByFullName( fullName, true );
}
public static Type FindTypeByFullName( string fullName, bool ignoreCase ) {
Type type = null;
for ( int i = 0; type == null && i < m_Assemblies.Length; ++i )
type = GetTypeCache( m_Assemblies[i] ).GetTypeByFullName( fullName, ignoreCase );
if ( type == null )
type = GetTypeCache( Core.Assembly ).GetTypeByFullName( fullName, ignoreCase );
return type;
}
public static Type FindTypeByName( string name ) {
return FindTypeByName( name, true );
}
public static Type FindTypeByName( string name, bool ignoreCase ) {
Type type = null;
for ( int i = 0; type == null && i < m_Assemblies.Length; ++i )
type = GetTypeCache( m_Assemblies[i] ).GetTypeByName( name, ignoreCase );
if ( type == null )
type = GetTypeCache( Core.Assembly ).GetTypeByName( name, ignoreCase );
return type;
}
public static void EnsureDirectory( string dir ) {
string path = Path.Combine( Core.BaseDirectory, dir );
if ( !Directory.Exists( path ) )
Directory.CreateDirectory( path );
}
public static string[] GetScripts( string filter ) {
List<string> list = new List<string>();
GetScripts( list, Path.Combine( Core.BaseDirectory, "Scripts" ), filter );
return list.ToArray();
}
public static void GetScripts( List<string> list, string path, string filter ) {
foreach ( string dir in Directory.GetDirectories( path ) )
GetScripts( list, dir, filter );
list.AddRange( Directory.GetFiles( path, filter ) );
}
}
public class TypeCache {
private Type[] m_Types;
private TypeTable m_Names, m_FullNames;
public Type[] Types { get { return m_Types; } }
public TypeTable Names { get { return m_Names; } }
public TypeTable FullNames { get { return m_FullNames; } }
public Type GetTypeByName( string name, bool ignoreCase ) {
return m_Names.Get( name, ignoreCase );
}
public Type GetTypeByFullName( string fullName, bool ignoreCase ) {
return m_FullNames.Get( fullName, ignoreCase );
}
public TypeCache( Assembly asm ) {
if ( asm == null )
m_Types = Type.EmptyTypes;
else
m_Types = asm.GetTypes();
m_Names = new TypeTable( m_Types.Length );
m_FullNames = new TypeTable( m_Types.Length );
Type typeofTypeAliasAttribute = typeof( TypeAliasAttribute );
for ( int i = 0; i < m_Types.Length; ++i ) {
Type type = m_Types[i];
m_Names.Add( type.Name, type );
m_FullNames.Add( type.FullName, type );
if ( type.IsDefined( typeofTypeAliasAttribute, false ) ) {
object[] attrs = type.GetCustomAttributes( typeofTypeAliasAttribute, false );
if ( attrs != null && attrs.Length > 0 ) {
TypeAliasAttribute attr = attrs[0] as TypeAliasAttribute;
if ( attr != null ) {
for ( int j = 0; j < attr.Aliases.Length; ++j )
m_FullNames.Add( attr.Aliases[j], type );
}
}
}
}
}
}
public class TypeTable {
private Dictionary<string, Type> m_Sensitive, m_Insensitive;
public void Add( string key, Type type ) {
m_Sensitive[key] = type;
m_Insensitive[key] = type;
}
public Type Get( string key, bool ignoreCase ) {
Type t = null;
if ( ignoreCase )
m_Insensitive.TryGetValue( key, out t );
else
m_Sensitive.TryGetValue( key, out t );
return t;
}
public TypeTable( int capacity ) {
m_Sensitive = new Dictionary<string, Type>( capacity );
m_Insensitive = new Dictionary<string, Type>( capacity, StringComparer.OrdinalIgnoreCase );
}
}
}