1515// You should have received a copy of the GNU General Public License
1616// along with Open Rails. If not, see <http://www.gnu.org/licenses/>.
1717
18- using Microsoft . CodeDom . Providers . DotNetCompilerPlatform ;
1918using Orts . Simulation ;
2019using ORTS . Common ;
2120using System ;
22- using System . CodeDom . Compiler ;
2321using System . Collections . Generic ;
2422using System . Diagnostics ;
2523using System . IO ;
24+ using System . Linq ;
2625using System . Reflection ;
2726using System . Text ;
2827using System . Threading ;
28+ using Microsoft . CodeAnalysis ;
29+ using Microsoft . CodeAnalysis . CSharp ;
30+ using Microsoft . CodeAnalysis . Emit ;
2931
3032namespace Orts . Common . Scripting
3133{
3234 [ CallOnThread ( "Loader" ) ]
3335 public class ScriptManager
3436 {
35- readonly Simulator Simulator ;
3637 readonly IDictionary < string , Assembly > Scripts = new Dictionary < string , Assembly > ( ) ;
37- static readonly ProviderOptions ProviderOptions = new ProviderOptions ( Path . Combine ( new Uri ( Path . GetDirectoryName ( Assembly . GetExecutingAssembly ( ) . CodeBase ) ) . LocalPath , "roslyn" , "csc.exe" ) , 10 ) ;
38- static readonly CSharpCodeProvider Compiler = new CSharpCodeProvider ( ProviderOptions ) ;
39-
40- static CompilerParameters GetCompilerParameters ( )
38+ static readonly string [ ] ReferenceAssemblies = new [ ]
4139 {
42- var cp = new CompilerParameters ( )
43- {
44- GenerateInMemory = true ,
45- IncludeDebugInformation = Debugger . IsAttached ,
46- } ;
47- cp . ReferencedAssemblies . Add ( "System.dll" ) ;
48- cp . ReferencedAssemblies . Add ( "System.Core.dll" ) ;
49- cp . ReferencedAssemblies . Add ( "ORTS.Common.dll" ) ;
50- cp . ReferencedAssemblies . Add ( "Orts.Simulation.dll" ) ;
51- return cp ;
52- }
40+ typeof ( System . Object ) . Assembly . Location ,
41+ typeof ( System . Diagnostics . Debug ) . Assembly . Location ,
42+ typeof ( ORTS . Common . ElapsedTime ) . Assembly . Location ,
43+ typeof ( ORTS . Scripting . Api . Timer ) . Assembly . Location ,
44+ typeof ( System . Linq . Enumerable ) . Assembly . Location ,
45+ } ;
46+ static MetadataReference [ ] References = ReferenceAssemblies . Select ( r => MetadataReference . CreateFromFile ( r ) ) . ToArray ( ) ;
47+ static CSharpCompilationOptions CompilationOptions = new CSharpCompilationOptions (
48+ OutputKind . DynamicallyLinkedLibrary ,
49+ optimizationLevel : Debugger . IsAttached ? OptimizationLevel . Debug : OptimizationLevel . Release ) ;
5350
5451 [ CallOnThread ( "Loader" ) ]
55- internal ScriptManager ( Simulator simulator )
52+ internal ScriptManager ( )
5653 {
57- Simulator = simulator ;
5854 }
5955
6056 public object Load ( string [ ] pathArray , string name , string nameSpace = "ORTS.Scripting.Script" )
@@ -65,7 +61,7 @@ public object Load(string[] pathArray, string name, string nameSpace = "ORTS.Scr
6561 if ( pathArray == null || pathArray . Length == 0 || name == null || name == "" )
6662 return null ;
6763
68- if ( Path . GetExtension ( name ) != ".cs" )
64+ if ( Path . GetExtension ( name ) . ToLower ( ) != ".cs" )
6965 name += ".cs" ;
7066
7167 var path = ORTSPaths . GetFileFromFolders ( pathArray , name ) ;
@@ -78,30 +74,65 @@ public object Load(string[] pathArray, string name, string nameSpace = "ORTS.Scr
7874 var type = String . Format ( "{0}.{1}" , nameSpace , Path . GetFileNameWithoutExtension ( path ) . Replace ( '-' , '_' ) ) ;
7975
8076 if ( ! Scripts . ContainsKey ( path ) )
81- Scripts [ path ] = CompileScript ( path ) ;
77+ Scripts [ path ] = CompileScript ( new string [ ] { path } ) ;
8278 return Scripts [ path ] ? . CreateInstance ( type , true ) ;
8379 }
8480
85- private static Assembly CompileScript ( string path )
81+ private static Assembly CompileScript ( string [ ] path )
8682 {
83+ var scriptPath = path . Length > 1 ? Path . GetDirectoryName ( path [ 0 ] ) : path [ 0 ] ;
84+ var scriptName = Path . GetFileName ( scriptPath ) ;
85+ var symbolsName = Path . ChangeExtension ( scriptName , "pdb" ) ;
8786 try
8887 {
89- var compilerResults = Compiler . CompileAssemblyFromFile ( GetCompilerParameters ( ) , path ) ;
90- if ( ! compilerResults . Errors . HasErrors )
88+ var syntaxTrees = path . Select ( file => CSharpSyntaxTree . ParseText ( File . ReadAllText ( file ) , null , file , Encoding . UTF8 ) ) ;
89+ var compilation = CSharpCompilation . Create (
90+ scriptName ,
91+ syntaxTrees ,
92+ References ,
93+ CompilationOptions ) ;
94+
95+ var emitOptions = new EmitOptions (
96+ debugInformationFormat : DebugInformationFormat . PortablePdb ,
97+ pdbFilePath : symbolsName ) ;
98+
99+ var assemblyStream = new MemoryStream ( ) ;
100+ var symbolsStream = new MemoryStream ( ) ;
101+
102+ var result = compilation . Emit (
103+ assemblyStream ,
104+ symbolsStream ,
105+ options : emitOptions ) ;
106+
107+ if ( result . Success )
91108 {
92- var script = compilerResults . CompiledAssembly ;
109+ assemblyStream . Seek ( 0 , SeekOrigin . Begin ) ;
110+ symbolsStream . Seek ( 0 , SeekOrigin . Begin ) ;
111+
112+ var script = Assembly . Load ( assemblyStream . ToArray ( ) , symbolsStream . ToArray ( ) ) ;
113+ // in netcore:
114+ //var script = AssemblyLoadContext.Default.LoadFromStream(ms);
93115 if ( script == null )
94- Trace . TraceWarning ( $ "Script file { path } could not be loaded into the process.") ;
116+ Trace . TraceWarning ( $ "Script { scriptPath } could not be loaded into the process.") ;
95117 return script ;
96118 }
97119 else
98120 {
121+ var errors = result . Diagnostics . Where ( diagnostic => diagnostic . IsWarningAsError || diagnostic . Severity == DiagnosticSeverity . Error ) ;
122+
99123 var errorString = new StringBuilder ( ) ;
100- errorString . AppendFormat ( "Skipped script {0} with error:" , path ) ;
124+ errorString . AppendFormat ( "Skipped script {0} with error:" , scriptPath ) ;
101125 errorString . Append ( Environment . NewLine ) ;
102- foreach ( CompilerError error in compilerResults . Errors )
126+ foreach ( var error in errors )
103127 {
104- errorString . AppendFormat ( " {0}, line: {1}, column: {2}" , error . ErrorText , error . Line /*- prefixLines*/ , error . Column ) ;
128+ var textSpan = error . Location . SourceSpan ;
129+ var fileName = Path . GetFileName ( error . Location . SourceTree . FilePath ) ;
130+ var lineSpan = error . Location . SourceTree . GetLineSpan ( textSpan ) ;
131+ var line = lineSpan . StartLinePosition . Line + 1 ;
132+ var column = lineSpan . StartLinePosition . Character + 1 ;
133+ errorString . AppendFormat ( "\t {0}: {1}, " , error . Id , error . GetMessage ( ) ) ;
134+ if ( path . Length > 1 ) errorString . AppendFormat ( "file: {0}, " , fileName ) ;
135+ errorString . AppendFormat ( "line: {0}, column: {1}" , line , column ) ;
105136 errorString . Append ( Environment . NewLine ) ;
106137 }
107138
@@ -111,21 +142,22 @@ private static Assembly CompileScript(string path)
111142 }
112143 catch ( InvalidDataException error )
113144 {
114- Trace . TraceWarning ( "Skipped script {0} with error: {1}" , path , error . Message ) ;
145+ Trace . TraceWarning ( "Skipped script {0} with error: {1}" , scriptPath , error . Message ) ;
115146 return null ;
116147 }
117148 catch ( Exception error )
118149 {
119- if ( File . Exists ( path ) )
120- Trace . WriteLine ( new FileLoadException ( path , error ) ) ;
150+ if ( File . Exists ( scriptPath ) || Directory . Exists ( scriptPath ) )
151+ Trace . WriteLine ( new FileLoadException ( scriptPath , error ) ) ;
121152 else
122- Trace . TraceWarning ( "Ignored missing script file {0}" , path ) ;
153+ Trace . TraceWarning ( "Ignored missing script {0}" , scriptPath ) ;
123154 return null ;
124155 }
125156 }
126157
127158 public Assembly LoadFolder ( string path )
128159 {
160+
129161 if ( Thread . CurrentThread . Name != "Loader Process" )
130162 Trace . TraceError ( "ScriptManager.Load incorrectly called by {0}; must be Loader Process or crashes will occur." , Thread . CurrentThread . Name ) ;
131163
@@ -138,50 +170,16 @@ public Assembly LoadFolder(string path)
138170
139171 if ( files == null || files . Length == 0 ) return null ;
140172
141- try
173+ if ( ! Scripts . ContainsKey ( path ) )
142174 {
143- var compilerResults = Compiler . CompileAssemblyFromFile ( GetCompilerParameters ( ) , files ) ;
144- if ( ! compilerResults . Errors . HasErrors )
145- {
146- return compilerResults . CompiledAssembly ;
147- }
148- else
149- {
150- var errorString = new StringBuilder ( ) ;
151- errorString . AppendFormat ( "Skipped script folder {0} with error:" , path ) ;
152- errorString . Append ( Environment . NewLine ) ;
153- foreach ( CompilerError error in compilerResults . Errors )
154- {
155- errorString . AppendFormat ( " {0}, file: {1}, line: {2}, column: {3}" , error . ErrorText , error . FileName , error . Line /*- prefixLines*/ , error . Column ) ;
156- errorString . Append ( Environment . NewLine ) ;
157- }
158-
159- Trace . TraceWarning ( errorString . ToString ( ) ) ;
175+ var assembly = CompileScript ( files ) ;
176+ if ( assembly == null )
160177 return null ;
161- }
162- }
163- catch ( InvalidDataException error )
164- {
165- Trace . TraceWarning ( "Skipped script folder {0} with error: {1}" , path , error . Message ) ;
166- return null ;
167- }
168- catch ( Exception error )
169- {
170- Trace . WriteLine ( new FileLoadException ( path , error ) ) ;
171- return null ;
172- }
173- }
174-
175- /*
176- static ClassType CreateInstance<ClassType>(Assembly assembly) where ClassType : class
177- {
178- foreach (var type in assembly.GetTypes())
179- if (typeof(ClassType).IsAssignableFrom(type))
180- return Activator.CreateInstance(type) as ClassType;
181178
182- return default(ClassType);
179+ Scripts [ path ] = assembly ;
180+ }
181+ return Scripts [ path ] ;
183182 }
184- */
185183
186184 [ CallOnThread ( "Updater" ) ]
187185 public string GetStatus ( )
0 commit comments