@@ -10,6 +10,49 @@ open System.Threading
10
10
11
11
let private FSIPath = @" .\tools\FSharp\;.\lib\FSharp\;[ProgramFilesX86]\Microsoft SDKs\F#\4.0\Framework\v4.0;[ProgramFilesX86]\Microsoft SDKs\F#\3.1\Framework\v4.0;[ProgramFilesX86]\Microsoft SDKs\F#\3.0\Framework\v4.0;[ProgramFiles]\Microsoft F#\v4.0\;[ProgramFilesX86]\Microsoft F#\v4.0\;[ProgramFiles]\FSharp-2.0.0.0\bin\;[ProgramFilesX86]\FSharp-2.0.0.0\bin\;[ProgramFiles]\FSharp-1.9.9.9\bin\;[ProgramFilesX86]\FSharp-1.9.9.9\bin\"
12
12
13
+ let createDirectiveRegex id =
14
+ Text.RegularExpressions.Regex(
15
+ " ^\s *#" + id + " \s *(@\" |\"\"\" |\" )(?<path>.+?)(\"\"\" |\" )" ,
16
+ System.Text.RegularExpressions.RegexOptions.Compiled |||
17
+ System.Text.RegularExpressions.RegexOptions.Multiline)
18
+ let loadRegex = createDirectiveRegex " load"
19
+ let rAssemblyRegex = createDirectiveRegex " r"
20
+ let searchPathRegex = createDirectiveRegex " I"
21
+
22
+ let private extractDirectives ( regex : System.Text.RegularExpressions.Regex ) scriptContents =
23
+ regex.Matches( scriptContents)
24
+ |> Seq.cast< Text.RegularExpressions.Match>
25
+ |> Seq.map( fun m ->
26
+ ( m.Groups.Item( " path" ) .Value)
27
+ )
28
+ let rec getAllScripts scriptPath : seq < string * string > =
29
+ let scriptPath =
30
+ if Path.IsPathRooted scriptPath then
31
+ scriptPath
32
+ else
33
+ Path.Combine( Directory.GetCurrentDirectory(), scriptPath)
34
+ let scriptContents = File.ReadAllText( scriptPath)
35
+ let loadedContents =
36
+ extractDirectives loadRegex scriptContents
37
+ |> Seq.collect( fun path ->
38
+ let path =
39
+ if Path.IsPathRooted path then
40
+ path
41
+ else
42
+ Path.Combine( Path.GetDirectoryName( scriptPath), path)
43
+ getAllScripts path
44
+ )
45
+ Seq.concat [ List.toSeq [ scriptPath, scriptContents]; loadedContents]
46
+
47
+ let getAllScriptContents ( pathsAndContents : seq < string * string >) =
48
+ pathsAndContents |> Seq.map( snd)
49
+ let getIncludedAssembly scriptContents = extractDirectives rAssemblyRegex scriptContents
50
+ let getSearchPaths scriptContents = extractDirectives searchPathRegex scriptContents
51
+
52
+ let getScriptHash pathsAndContents =
53
+ let fullContents = getAllScriptContents pathsAndContents |> String.concat( " \n " )
54
+ let hasher = HashLib.HashFactory.Checksum.CreateCRC32a()
55
+ hasher.ComputeString( fullContents) .ToString()
13
56
/// The path to the F# Interactive tool.
14
57
let fsiPath =
15
58
let ev = environVar " FSI"
@@ -87,9 +130,14 @@ let executeFSIWithScriptArgsAndReturnMessages script (scriptArgs: string[]) =
87
130
88
131
open Microsoft.FSharp .Compiler .Interactive .Shell
89
132
133
+ type private AssemblySource =
134
+ | GAC
135
+ | Disk
136
+
137
+ let hashRegex = Text.RegularExpressions.Regex( " (?<script>.+)_(?<hash>[a-zA-Z0-9]+\. dll$)" , System.Text.RegularExpressions.RegexOptions.Compiled)
90
138
/// Run the given FAKE script with fsi.exe at the given working directory. Provides full access to Fsi options and args. Redirect output and error messages.
91
- let internal runFAKEScriptWithFsiArgsAndRedirectMessages printDetails ( FsiArgs ( fsiOptions , script , scriptArgs )) args onErrMsg onOutMsg =
92
- if printDetails then traceFAKE " Running Buildscript: %s " script
139
+ let internal runFAKEScriptWithFsiArgsAndRedirectMessages printDetails ( FsiArgs ( fsiOptions , scriptPath , scriptArgs )) args onErrMsg onOutMsg useCache cleanCache =
140
+ if printDetails then traceFAKE " Running Buildscript: %s " scriptPath
93
141
94
142
// Add arguments to the Environment
95
143
for ( k, v) in args do
@@ -113,31 +161,118 @@ let internal runFAKEScriptWithFsiArgsAndRedirectMessages printDetails (FsiArgs(f
113
161
then onMsg s
114
162
handleMessagesFrom sbOut onOutMsg
115
163
handleMessagesFrom sbErr onErrMsg
164
+ let handleException ( ex : Exception ) =
165
+ onErrMsg ( ex.ToString())
116
166
117
167
use outStream = new StringWriter( sbOut)
118
168
use errStream = new StringWriter( sbErr)
119
169
use stdin = new StreamReader( Stream.Null)
120
170
121
- try
122
- let session = FsiEvaluationSession.Create( fsiConfig, commonOptions, stdin, outStream, errStream)
171
+ let allScriptContents = getAllScripts scriptPath
172
+ let scriptHash = lazy ( getScriptHash allScriptContents)
173
+ //TODO this is only calculating the hash for the input file, not anything #load-ed
174
+
175
+ let scriptFileName = lazy ( Path.GetFileName( scriptPath))
176
+ let hashPath = lazy ( " ./.fake/" + scriptFileName.Value + " _" + scriptHash.Value)
177
+ let assemblyPath = lazy ( hashPath.Value + " .dll" )
178
+ let assemblyRefPath = lazy ( hashPath.Value + " _references.txt" )
179
+ let cacheValid = lazy (
180
+ System.IO.File.Exists( assemblyPath.Value) &&
181
+ System.IO.File.Exists( assemblyRefPath.Value))
182
+
183
+ let getScriptAndHash fileName =
184
+ let matched = hashRegex.Match( fileName)
185
+ matched.Groups.Item( " script" ) .Value, matched.Groups.Item( " hash" ) .Value
186
+
187
+ if useCache && cacheValid.Value then
188
+
189
+ trace ( " Using cache" )
190
+ let noExtension = Path.GetFileNameWithoutExtension( scriptFileName.Value)
191
+ let fullName =
192
+ sprintf " <StartupCode$FSI_0001>.$FSI_0001_%s%s $%s "
193
+ ( noExtension.Substring( 0 , 1 ) .ToUpper())
194
+ ( noExtension.Substring( 1 ))
195
+ ( Path.GetExtension( scriptFileName.Value) .Substring( 1 ))
123
196
197
+ for loc in File.ReadAllLines( assemblyRefPath.Value) do
198
+ Reflection.Assembly.LoadFrom( loc) |> ignore
199
+
200
+ let assembly = Reflection.Assembly.LoadFrom( assemblyPath.Value)
201
+
202
+ let mainModule = assembly.GetType( fullName)
203
+
124
204
try
125
- session.EvalScript script
126
- // TODO: Reactivate when FCS don't show output any more
127
- // handleMessages()
205
+ let _result =
206
+ mainModule.InvokeMember(
207
+ " main@" ,
208
+ System.Reflection.BindingFlags.InvokeMethod |||
209
+ System.Reflection.BindingFlags.Public |||
210
+ System.Reflection.BindingFlags.Static, null , null , [||])
128
211
true
129
212
with
130
- | _ ->
131
- handleMessages ()
213
+ | ex ->
214
+ handleException ex
132
215
false
133
- with
134
- | exn ->
135
- traceError " FsiEvaluationSession could not be created."
136
- traceError <| sbErr.ToString()
137
- raise exn
216
+ else
217
+ if useCache then
218
+ let cacheDir = DirectoryInfo( " ./.fake" )
219
+ if cacheDir.Exists then
220
+ let oldFiles =
221
+ cacheDir.GetFiles()
222
+ |> Seq.filter( fun file ->
223
+ let oldScriptName , _ = getScriptAndHash( file.Name)
224
+ oldScriptName = scriptFileName.Value
225
+ )
226
+ if ( oldFiles |> Seq.length) > 0 then
227
+ if cleanCache then
228
+ for file in oldFiles do
229
+ file.Delete()
230
+ trace " Cache is invalid, recompiling"
231
+ else
232
+ trace " Cache doesnt exist"
233
+ else
234
+ trace " Cache doesnt exist"
235
+ try
236
+ let session = FsiEvaluationSession.Create( fsiConfig, commonOptions, stdin, outStream, errStream)
237
+ try
238
+ session.EvalScript scriptPath
239
+
240
+ try
241
+ if useCache && not cacheValid.Value then
242
+ let assemBuilder = session.DynamicAssembly :?> System.Reflection.Emit.AssemblyBuilder
243
+ assemBuilder.Save( " FSI-ASSEMBLY.dll" )
244
+ Directory.CreateDirectory( " ./.fake" ) |> ignore
245
+ File.Move( " ./FSI-ASSEMBLY.dll" , assemblyPath.Value)
246
+
247
+ if File.Exists( " ./FSI-ASSEMBLY.pdb" ) then
248
+ File.Delete( " ./FSI-ASSEMBLY.pdb" )
249
+
250
+ let refedAssemblies =
251
+ System.AppDomain.CurrentDomain.GetAssemblies()
252
+ |> Seq.filter( fun assem -> not assem.IsDynamic)
253
+ |> Seq.map( fun assem -> assem.Location)
254
+
255
+ File.WriteAllLines( assemblyRefPath.Value, refedAssemblies) |> ignore
256
+ trace ( System.Environment.NewLine + " Saved cache" )
257
+ with
258
+ | ex ->
259
+ handleException ex
260
+ reraise()
261
+ // TODO: Reactivate when FCS don't show output any more
262
+ // handleMessages()
263
+ true
264
+ with
265
+ | _ ex ->
266
+ handleMessages()
267
+ false
268
+ with
269
+ | exn ->
270
+ traceError " FsiEvaluationSession could not be created."
271
+ traceError <| sbErr.ToString()
272
+ raise exn
138
273
139
274
/// Run the given buildscript with fsi.exe and allows for extra arguments to the script. Returns output.
140
- let executeBuildScriptWithArgsAndReturnMessages script ( scriptArgs : string []) =
275
+ let executeBuildScriptWithArgsAndReturnMessages script ( scriptArgs : string []) useCache cleanCache =
141
276
let messages = ref []
142
277
let appendMessage isError msg =
143
278
messages := { IsError = isError
@@ -146,19 +281,21 @@ let executeBuildScriptWithArgsAndReturnMessages script (scriptArgs: string[]) =
146
281
let result =
147
282
runFAKEScriptWithFsiArgsAndRedirectMessages
148
283
true ( FsiArgs([], script, scriptArgs |> List.ofArray)) []
149
- ( appendMessage true ) ( appendMessage false )
284
+ ( appendMessage true ) ( appendMessage false ) useCache cleanCache
150
285
( result, ! messages)
151
286
152
287
/// Run the given buildscript with fsi.exe at the given working directory. Provides full access to Fsi options and args.
153
- let runBuildScriptWithFsiArgsAt printDetails ( FsiArgs ( fsiOptions , script , scriptArgs )) args =
288
+ let runBuildScriptWithFsiArgsAt printDetails ( FsiArgs ( fsiOptions , script , scriptArgs )) args useCache cleanCache =
154
289
runFAKEScriptWithFsiArgsAndRedirectMessages
155
290
printDetails ( FsiArgs( fsiOptions, script, scriptArgs)) args
156
291
traceError ( fun s -> traceFAKE " %s " s)
292
+ useCache
293
+ cleanCache
157
294
158
295
/// Run the given buildscript with fsi.exe at the given working directory.
159
- let runBuildScriptAt printDetails script extraFsiArgs args =
160
- runBuildScriptWithFsiArgsAt printDetails ( FsiArgs( extraFsiArgs, script, [])) args
296
+ let runBuildScriptAt printDetails script extraFsiArgs args useCache cleanCache =
297
+ runBuildScriptWithFsiArgsAt printDetails ( FsiArgs( extraFsiArgs, script, [])) args useCache cleanCache
161
298
162
299
/// Run the given buildscript with fsi.exe
163
- let runBuildScript printDetails script extraFsiArgs args =
164
- runBuildScriptAt printDetails script extraFsiArgs args
300
+ let runBuildScript printDetails script extraFsiArgs args useCache cleanCache =
301
+ runBuildScriptAt printDetails script extraFsiArgs args useCache cleanCache
0 commit comments