15
15
// You should have received a copy of the GNU General Public License
16
16
// along with Open Rails. If not, see <http://www.gnu.org/licenses/>.
17
17
18
- using Microsoft . CodeDom . Providers . DotNetCompilerPlatform ;
19
18
using Orts . Simulation ;
20
19
using ORTS . Common ;
21
20
using System ;
22
- using System . CodeDom . Compiler ;
23
21
using System . Collections . Generic ;
24
22
using System . Diagnostics ;
25
23
using System . IO ;
24
+ using System . Linq ;
26
25
using System . Reflection ;
27
26
using System . Text ;
28
27
using System . Threading ;
28
+ using Microsoft . CodeAnalysis ;
29
+ using Microsoft . CodeAnalysis . CSharp ;
30
+ using Microsoft . CodeAnalysis . Emit ;
29
31
30
32
namespace Orts . Common . Scripting
31
33
{
32
34
[ CallOnThread ( "Loader" ) ]
33
35
public class ScriptManager
34
36
{
35
- readonly Simulator Simulator ;
36
37
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 [ ]
41
39
{
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 ) ;
53
50
54
51
[ CallOnThread ( "Loader" ) ]
55
- internal ScriptManager ( Simulator simulator )
52
+ internal ScriptManager ( )
56
53
{
57
- Simulator = simulator ;
58
54
}
59
55
60
56
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
65
61
if ( pathArray == null || pathArray . Length == 0 || name == null || name == "" )
66
62
return null ;
67
63
68
- if ( Path . GetExtension ( name ) != ".cs" )
64
+ if ( Path . GetExtension ( name ) . ToLower ( ) != ".cs" )
69
65
name += ".cs" ;
70
66
71
67
var path = ORTSPaths . GetFileFromFolders ( pathArray , name ) ;
@@ -78,30 +74,65 @@ public object Load(string[] pathArray, string name, string nameSpace = "ORTS.Scr
78
74
var type = String . Format ( "{0}.{1}" , nameSpace , Path . GetFileNameWithoutExtension ( path ) . Replace ( '-' , '_' ) ) ;
79
75
80
76
if ( ! Scripts . ContainsKey ( path ) )
81
- Scripts [ path ] = CompileScript ( path ) ;
77
+ Scripts [ path ] = CompileScript ( new string [ ] { path } ) ;
82
78
return Scripts [ path ] ? . CreateInstance ( type , true ) ;
83
79
}
84
80
85
- private static Assembly CompileScript ( string path )
81
+ private static Assembly CompileScript ( string [ ] path )
86
82
{
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" ) ;
87
86
try
88
87
{
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 )
91
108
{
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);
93
115
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.") ;
95
117
return script ;
96
118
}
97
119
else
98
120
{
121
+ var errors = result . Diagnostics . Where ( diagnostic => diagnostic . IsWarningAsError || diagnostic . Severity == DiagnosticSeverity . Error ) ;
122
+
99
123
var errorString = new StringBuilder ( ) ;
100
- errorString . AppendFormat ( "Skipped script {0} with error:" , path ) ;
124
+ errorString . AppendFormat ( "Skipped script {0} with error:" , scriptPath ) ;
101
125
errorString . Append ( Environment . NewLine ) ;
102
- foreach ( CompilerError error in compilerResults . Errors )
126
+ foreach ( var error in errors )
103
127
{
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 ) ;
105
136
errorString . Append ( Environment . NewLine ) ;
106
137
}
107
138
@@ -111,21 +142,22 @@ private static Assembly CompileScript(string path)
111
142
}
112
143
catch ( InvalidDataException error )
113
144
{
114
- Trace . TraceWarning ( "Skipped script {0} with error: {1}" , path , error . Message ) ;
145
+ Trace . TraceWarning ( "Skipped script {0} with error: {1}" , scriptPath , error . Message ) ;
115
146
return null ;
116
147
}
117
148
catch ( Exception error )
118
149
{
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 ) ) ;
121
152
else
122
- Trace . TraceWarning ( "Ignored missing script file {0}" , path ) ;
153
+ Trace . TraceWarning ( "Ignored missing script {0}" , scriptPath ) ;
123
154
return null ;
124
155
}
125
156
}
126
157
127
158
public Assembly LoadFolder ( string path )
128
159
{
160
+
129
161
if ( Thread . CurrentThread . Name != "Loader Process" )
130
162
Trace . TraceError ( "ScriptManager.Load incorrectly called by {0}; must be Loader Process or crashes will occur." , Thread . CurrentThread . Name ) ;
131
163
@@ -138,50 +170,16 @@ public Assembly LoadFolder(string path)
138
170
139
171
if ( files == null || files . Length == 0 ) return null ;
140
172
141
- try
173
+ if ( ! Scripts . ContainsKey ( path ) )
142
174
{
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 )
160
177
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;
181
178
182
- return default(ClassType);
179
+ Scripts [ path ] = assembly ;
180
+ }
181
+ return Scripts [ path ] ;
183
182
}
184
- */
185
183
186
184
[ CallOnThread ( "Updater" ) ]
187
185
public string GetStatus ( )
0 commit comments