PageRenderTime 52ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/compiler/3.1/Nov2013/src/fsharp/FSharp.Build/Fsc.fs

#
F# | 531 lines | 373 code | 57 blank | 101 comment | 42 complexity | 6473e387e1ef8b51f97911ec8db0c7b7 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, Apache-2.0
  1. namespace Microsoft.FSharp.Build
  2. open System
  3. open System.Text
  4. open System.Diagnostics.CodeAnalysis
  5. open System.Reflection
  6. open Microsoft.Build.Framework
  7. open Microsoft.Build.Utilities
  8. open Internal.Utilities
  9. [<assembly: System.Runtime.InteropServices.ComVisible(false)>]
  10. [<assembly: System.CLSCompliant(true)>]
  11. [<assembly: SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Scope="type", Target="Microsoft.FSharp.Build.Fsc", MessageId="Fsc")>]
  12. do()
  13. type FscCommandLineBuilder() =
  14. // In addition to generating a command-line that will be handed to cmd.exe, we also generate
  15. // an array of individual arguments. The former needs to be quoted (and cmd.exe will strip the
  16. // quotes while parsing), whereas the latter is not. See bug 4357 for background; this helper
  17. // class gets us out of the business of unparsing-then-reparsing arguments.
  18. let builder = new CommandLineBuilder()
  19. let mutable args = [] // in reverse order
  20. let mutable srcs = [] // in reverse order
  21. let mutable alreadyCalledWithFilenames = false
  22. /// Return a list of the arguments (with no quoting for the cmd.exe shell)
  23. member x.CapturedArguments() =
  24. assert(not alreadyCalledWithFilenames)
  25. List.rev args
  26. /// Return a list of the sources (with no quoting for the cmd.exe shell)
  27. member x.CapturedFilenames() =
  28. assert(alreadyCalledWithFilenames)
  29. List.rev srcs
  30. /// Return a full command line (with quoting for the cmd.exe shell)
  31. override x.ToString() =
  32. builder.ToString()
  33. member x.AppendFileNamesIfNotNull(filenames:ITaskItem array, sep:string) =
  34. builder.AppendFileNamesIfNotNull(filenames, sep)
  35. // do not update "args", not used
  36. for item in filenames do
  37. let tmp = new CommandLineBuilder()
  38. tmp.AppendSwitchUnquotedIfNotNull("", item.ItemSpec) // we don't want to quote the filename, this is a way to get that
  39. let s = tmp.ToString()
  40. if s <> String.Empty then
  41. srcs <- tmp.ToString() :: srcs
  42. alreadyCalledWithFilenames <- true
  43. member x.AppendSwitchIfNotNull(switch:string, values:string array, sep:string) =
  44. builder.AppendSwitchIfNotNull(switch, values, sep)
  45. let tmp = new CommandLineBuilder()
  46. tmp.AppendSwitchUnquotedIfNotNull(switch, values, sep)
  47. let s = tmp.ToString()
  48. if s <> String.Empty then
  49. args <- s :: args
  50. member x.AppendSwitchIfNotNull(switch:string, value:string) =
  51. builder.AppendSwitchIfNotNull(switch, value)
  52. let tmp = new CommandLineBuilder()
  53. tmp.AppendSwitchUnquotedIfNotNull(switch, value)
  54. let s = tmp.ToString()
  55. if s <> String.Empty then
  56. args <- s :: args
  57. member x.AppendSwitchUnquotedIfNotNull(switch:string, value:string) =
  58. assert(switch = "") // we only call this method for "OtherFlags"
  59. // Unfortunately we still need to mimic what cmd.exe does, but only for "OtherFlags".
  60. let ParseCommandLineArgs(commandLine:string) = // returns list in reverse order
  61. let mutable args = []
  62. let mutable i = 0 // index into commandLine
  63. let len = commandLine.Length
  64. while i < len do
  65. // skip whitespace
  66. while i < len && System.Char.IsWhiteSpace(commandLine, i) do
  67. i <- i + 1
  68. if i < len then
  69. // parse an argument
  70. let sb = new StringBuilder()
  71. let mutable finished = false
  72. let mutable insideQuote = false
  73. while i < len && not finished do
  74. match commandLine.[i] with
  75. | '"' -> insideQuote <- not insideQuote; i <- i + 1
  76. | c when not insideQuote && System.Char.IsWhiteSpace(c) -> finished <- true
  77. | c -> sb.Append(c) |> ignore; i <- i + 1
  78. args <- sb.ToString() :: args
  79. args
  80. builder.AppendSwitchUnquotedIfNotNull(switch, value)
  81. let tmp = new CommandLineBuilder()
  82. tmp.AppendSwitchUnquotedIfNotNull(switch, value)
  83. let s = tmp.ToString()
  84. if s <> String.Empty then
  85. args <- ParseCommandLineArgs(s) @ args
  86. member x.AppendSwitch(switch:string) =
  87. builder.AppendSwitch(switch)
  88. args <- switch :: args
  89. //There are a lot of flags on fsc.exe.
  90. //For now, not all of them are represented in the "Fsc class" object model.
  91. //The goal is to have the most common/important flags available via the Fsc class, and the
  92. //rest can be "backdoored" through the .OtherFlags property.
  93. type [<Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")>] Fsc() as this =
  94. inherit ToolTask()
  95. let mutable baseAddress : string = null
  96. let mutable codePage : string = null
  97. let mutable debugSymbols = false
  98. let mutable debugType : string = null
  99. let mutable defineConstants : ITaskItem[] = [||]
  100. let mutable disabledWarnings : string = null
  101. let mutable documentationFile : string = null
  102. let mutable generateInterfaceFile : string = null
  103. let mutable keyFile : string = null
  104. let mutable noFramework = false
  105. let mutable optimize : bool = true
  106. let mutable tailcalls : bool = true
  107. let mutable otherFlags : string = null
  108. let mutable outputAssembly : string = null
  109. let mutable pdbFile : string = null
  110. let mutable platform : string = null
  111. let mutable prefer32bit : bool = false
  112. let mutable references : ITaskItem[] = [||]
  113. let mutable referencePath : string = null
  114. let mutable resources : ITaskItem[] = [||]
  115. let mutable sources : ITaskItem[] = [||]
  116. let mutable targetType : string = null
  117. #if FX_ATLEAST_35
  118. #else
  119. let mutable toolExe : string = "fsc.exe"
  120. #endif
  121. let mutable warningLevel : string = null
  122. let mutable treatWarningsAsErrors : bool = false
  123. let mutable warningsAsErrors : string = null
  124. let mutable toolPath : string =
  125. match FSharpEnvironment.BinFolderOfDefaultFSharpCompiler with
  126. | Some s -> s
  127. | None -> ""
  128. let mutable versionFile : string = null
  129. let mutable win32res : string = null
  130. let mutable win32manifest : string = null
  131. let mutable vserrors : bool = false
  132. let mutable validateTypeProviders : bool = false
  133. let mutable vslcid : string = null
  134. let mutable utf8output : bool = false
  135. let mutable subsystemVersion : string = null
  136. let mutable highEntropyVA : bool = false
  137. let mutable targetProfile : string = null
  138. let mutable sqmSessionGuid : string = null
  139. let mutable capturedArguments : string list = [] // list of individual args, to pass to HostObject Compile()
  140. let mutable capturedFilenames : string list = [] // list of individual source filenames, to pass to HostObject Compile()
  141. do
  142. this.YieldDuringToolExecution <- true // See bug 6483; this makes parallel build faster, and is fine to set unconditionally
  143. // --baseaddress
  144. member fsc.BaseAddress
  145. with get() = baseAddress
  146. and set(s) = baseAddress <- s
  147. // --codepage <int>: Specify the codepage to use when opening source files
  148. member fsc.CodePage
  149. with get() = codePage
  150. and set(s) = codePage <- s
  151. // -g: Produce debug file. Disables optimizations if a -O flag is not given.
  152. member fsc.DebugSymbols
  153. with get() = debugSymbols
  154. and set(b) = debugSymbols <- b
  155. // --debug <none/pdbonly/full>: Emit debugging information
  156. member fsc.DebugType
  157. with get() = debugType
  158. and set(s) = debugType <- s
  159. // --nowarn <string>: Do not report the given specific warning.
  160. member fsc.DisabledWarnings
  161. with get() = disabledWarnings
  162. and set(a) = disabledWarnings <- a
  163. // --define <string>: Define the given conditional compilation symbol.
  164. member fsc.DefineConstants
  165. with get() = defineConstants
  166. and set(a) = defineConstants <- a
  167. // --doc <string>: Write the xmldoc of the assembly to the given file.
  168. member fsc.DocumentationFile
  169. with get() = documentationFile
  170. and set(s) = documentationFile <- s
  171. // --generate-interface-file <string>:
  172. // Print the inferred interface of the
  173. // assembly to a file.
  174. member fsc.GenerateInterfaceFile
  175. with get() = generateInterfaceFile
  176. and set(s) = generateInterfaceFile <- s
  177. // --keyfile <string>:
  178. // Sign the assembly the given keypair file, as produced
  179. // by the .NET Framework SDK 'sn.exe' tool. This produces
  180. // an assembly with a strong name. This is only relevant if producing
  181. // an assembly to be shared amongst programs from different
  182. // directories, e.g. to be installed in the Global Assembly Cache.
  183. member fsc.KeyFile
  184. with get() = keyFile
  185. and set(s) = keyFile <- s
  186. // --noframework
  187. member fsc.NoFramework
  188. with get() = noFramework
  189. and set(b) = noFramework <- b
  190. // --optimize
  191. member fsc.Optimize
  192. with get() = optimize
  193. and set(p) = optimize <- p
  194. // --tailcalls
  195. member fsc.Tailcalls
  196. with get() = tailcalls
  197. and set(p) = tailcalls <- p
  198. // REVIEW: decide whether to keep this, for now is handy way to deal with as-yet-unimplemented features
  199. member fsc.OtherFlags
  200. with get() = otherFlags
  201. and set(s) = otherFlags <- s
  202. // -o <string>: Name the output file.
  203. member fsc.OutputAssembly
  204. with get() = outputAssembly
  205. and set(s) = outputAssembly <- s
  206. // --pdb <string>:
  207. // Name the debug output file.
  208. member fsc.PdbFile
  209. with get() = pdbFile
  210. and set(s) = pdbFile <- s
  211. // --platform <string>: Limit which platforms this code can run on:
  212. // x86
  213. // x64
  214. // Itanium
  215. // anycpu
  216. // anycpu32bitpreferred
  217. member fsc.Platform
  218. with get() = platform
  219. and set(s) = platform <- s
  220. // indicator whether anycpu32bitpreferred is applicable or not
  221. member fsc.Prefer32Bit
  222. with get() = prefer32bit
  223. and set(s) = prefer32bit <- s
  224. // -r <string>: Reference an F# or .NET assembly.
  225. member fsc.References
  226. with get() = references
  227. and set(a) = references <- a
  228. // --lib
  229. member fsc.ReferencePath
  230. with get() = referencePath
  231. and set(s) = referencePath <- s
  232. // --resource <string>: Embed the specified managed resources (.resource).
  233. // Produce .resource files from .resx files using resgen.exe or resxc.exe.
  234. member fsc.Resources
  235. with get() = resources
  236. and set(a) = resources <- a
  237. // source files
  238. member fsc.Sources
  239. with get() = sources
  240. and set(a) = sources <- a
  241. // --target exe: Produce an executable with a console
  242. // --target winexe: Produce an executable which does not have a
  243. // stdin/stdout/stderr
  244. // --target library: Produce a DLL
  245. // --target module: Produce a module that can be added to another assembly
  246. member fsc.TargetType
  247. with get() = targetType
  248. and set(s) = targetType <- s
  249. // --version-file <string>:
  250. member fsc.VersionFile
  251. with get() = versionFile
  252. and set(s) = versionFile <- s
  253. #if FX_ATLEAST_35
  254. #else
  255. // Allow overriding to the executable name "fsc.exe"
  256. member fsc.ToolExe
  257. with get() = toolExe
  258. and set(s) = toolExe<- s
  259. #endif
  260. // For targeting other folders for "fsc.exe" (or ToolExe if different)
  261. member fsc.ToolPath
  262. with get() = toolPath
  263. and set(s) = toolPath <- s
  264. // For specifying a win32 native resource file (.res)
  265. member fsc.Win32ResourceFile
  266. with get() = win32res
  267. and set(s) = win32res <- s
  268. // For specifying a win32 manifest file
  269. member fsc.Win32ManifestFile
  270. with get() = win32manifest
  271. and set(m) = win32manifest <- m
  272. // For specifying the warning level (0-4)
  273. member fsc.WarningLevel
  274. with get() = warningLevel
  275. and set(s) = warningLevel <- s
  276. member fsc.TreatWarningsAsErrors
  277. with get() = treatWarningsAsErrors
  278. and set(p) = treatWarningsAsErrors <- p
  279. member fsc.WarningsAsErrors
  280. with get() = warningsAsErrors
  281. and set(s) = warningsAsErrors <- s
  282. member fsc.VisualStudioStyleErrors
  283. with get() = vserrors
  284. and set(p) = vserrors <- p
  285. member fsc.ValidateTypeProviders
  286. with get() = validateTypeProviders
  287. and set(p) = validateTypeProviders <- p
  288. member fsc.LCID
  289. with get() = vslcid
  290. and set(p) = vslcid <- p
  291. member fsc.Utf8Output
  292. with get() = utf8output
  293. and set(p) = utf8output <- p
  294. member fsc.SubsystemVersion
  295. with get() = subsystemVersion
  296. and set(p) = subsystemVersion <- p
  297. member fsc.HighEntropyVA
  298. with get() = highEntropyVA
  299. and set(p) = highEntropyVA <- p
  300. member fsc.TargetProfile
  301. with get() = targetProfile
  302. and set(p) = targetProfile <- p
  303. member fsc.SqmSessionGuid
  304. with get() = sqmSessionGuid
  305. and set(p) = sqmSessionGuid <- p
  306. // ToolTask methods
  307. override fsc.ToolName = "fsc.exe"
  308. override fsc.StandardErrorEncoding = if utf8output then System.Text.Encoding.UTF8 else base.StandardErrorEncoding
  309. override fsc.StandardOutputEncoding = if utf8output then System.Text.Encoding.UTF8 else base.StandardOutputEncoding
  310. override fsc.GenerateFullPathToTool() =
  311. if toolPath = "" then
  312. raise (new System.InvalidOperationException(FSBuild.SR.toolpathUnknown()))
  313. System.IO.Path.Combine(toolPath, fsc.ToolExe)
  314. member internal fsc.InternalGenerateFullPathToTool() = fsc.GenerateFullPathToTool() // expose for unit testing
  315. member internal fsc.BaseExecuteTool(pathToTool, responseFileCommands, commandLineCommands) = // F# does not allow protected members to be captured by lambdas, this is the standard workaround
  316. base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands)
  317. /// Intercept the call to ExecuteTool to handle the host compile case.
  318. override fsc.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands) =
  319. let ho = box fsc.HostObject
  320. match ho with
  321. | null ->
  322. base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands)
  323. | _ ->
  324. let sources = sources|>Array.map(fun i->i.ItemSpec)
  325. let baseCall = fun (dummy : int) -> fsc.BaseExecuteTool(pathToTool, responseFileCommands, commandLineCommands)
  326. // We are using a Converter<int,int> rather than a "unit->int" because it is too hard to
  327. // figure out how to pass an F# function object via reflection.
  328. let baseCallDelegate = new System.Converter<int,int>(baseCall)
  329. try
  330. let ret =
  331. (ho.GetType()).InvokeMember("Compile", BindingFlags.Public ||| BindingFlags.NonPublic ||| BindingFlags.InvokeMethod ||| BindingFlags.Instance, null, ho,
  332. [| box baseCallDelegate; box (capturedArguments |> List.toArray); box (capturedFilenames |> List.toArray) |],
  333. System.Globalization.CultureInfo.InvariantCulture)
  334. unbox ret
  335. with
  336. | :? System.Reflection.TargetInvocationException as tie when (match tie.InnerException with | :? Microsoft.Build.Exceptions.BuildAbortedException -> true | _ -> false) ->
  337. fsc.Log.LogError(tie.InnerException.Message, [| |])
  338. -1 // ok, this is what happens when VS IDE cancels the build, no need to assert, just log the build-canceled error and return -1 to denote task failed
  339. | e ->
  340. System.Diagnostics.Debug.Assert(false, "HostObject received by Fsc task did not have a Compile method or the compile method threw an exception. "+(e.ToString()))
  341. reraise()
  342. override fsc.GenerateCommandLineCommands() =
  343. let builder = new FscCommandLineBuilder()
  344. // OutputAssembly
  345. builder.AppendSwitchIfNotNull("-o:", outputAssembly)
  346. // CodePage
  347. builder.AppendSwitchIfNotNull("--codepage:", codePage)
  348. // Debug
  349. if debugSymbols then
  350. builder.AppendSwitch("-g")
  351. // DebugType
  352. builder.AppendSwitchIfNotNull("--debug:",
  353. if debugType = null then null else
  354. match debugType.ToUpperInvariant() with
  355. | "NONE" -> null
  356. | "PDBONLY" -> "pdbonly"
  357. | "FULL" -> "full"
  358. | _ -> null)
  359. // NoFramework
  360. if noFramework then
  361. builder.AppendSwitch("--noframework")
  362. // BaseAddress
  363. builder.AppendSwitchIfNotNull("--baseaddress:", baseAddress)
  364. // DefineConstants
  365. for item in defineConstants do
  366. builder.AppendSwitchIfNotNull("--define:", item.ItemSpec)
  367. // DocumentationFile
  368. builder.AppendSwitchIfNotNull("--doc:", documentationFile)
  369. // GenerateInterfaceFile
  370. builder.AppendSwitchIfNotNull("--sig:", generateInterfaceFile)
  371. // KeyFile
  372. builder.AppendSwitchIfNotNull("--keyfile:", keyFile)
  373. // Optimize
  374. if optimize then
  375. builder.AppendSwitch("--optimize+")
  376. else
  377. builder.AppendSwitch("--optimize-")
  378. if not tailcalls then
  379. builder.AppendSwitch("--tailcalls-")
  380. // PdbFile
  381. builder.AppendSwitchIfNotNull("--pdb:", pdbFile)
  382. // Platform
  383. builder.AppendSwitchIfNotNull("--platform:",
  384. let ToUpperInvariant (s:string) = if s = null then null else s.ToUpperInvariant()
  385. match ToUpperInvariant(platform), prefer32bit, ToUpperInvariant(targetType) with
  386. | "ANYCPU", true, "EXE"
  387. | "ANYCPU", true, "WINEXE" -> "anycpu32bitpreferred"
  388. | "ANYCPU", _, _ -> "anycpu"
  389. | "X86" , _, _ -> "x86"
  390. | "X64" , _, _ -> "x64"
  391. | "ITANIUM", _, _ -> "Itanium"
  392. | _ -> null)
  393. // Resources
  394. for item in resources do
  395. builder.AppendSwitchIfNotNull("--resource:", item.ItemSpec)
  396. // VersionFile
  397. builder.AppendSwitchIfNotNull("--versionfile:", versionFile)
  398. // References
  399. for item in references do
  400. builder.AppendSwitchIfNotNull("-r:", item.ItemSpec)
  401. // ReferencePath
  402. let referencePathArray = // create a array of strings
  403. match referencePath with
  404. | null -> null
  405. | _ -> referencePath.Split([|';'; ','|], StringSplitOptions.RemoveEmptyEntries)
  406. builder.AppendSwitchIfNotNull("--lib:", referencePathArray, ",")
  407. // TargetType
  408. builder.AppendSwitchIfNotNull("--target:",
  409. if targetType = null then null else
  410. match targetType.ToUpperInvariant() with
  411. | "LIBRARY" -> "library"
  412. | "EXE" -> "exe"
  413. | "WINEXE" -> "winexe"
  414. | "MODULE" -> "module"
  415. | _ -> null)
  416. // NoWarn
  417. match disabledWarnings with
  418. | null -> ()
  419. | _ -> builder.AppendSwitchIfNotNull("--nowarn:", disabledWarnings.Split([|' '; ';'; ','; '\r'; '\n'|], StringSplitOptions.RemoveEmptyEntries), ",")
  420. // WarningLevel
  421. builder.AppendSwitchIfNotNull("--warn:", warningLevel)
  422. // TreatWarningsAsErrors
  423. if treatWarningsAsErrors then
  424. builder.AppendSwitch("--warnaserror")
  425. // WarningsAsErrors
  426. // Change warning 76, HashReferenceNotAllowedInNonScript/HashDirectiveNotAllowedInNonScript/HashIncludeNotAllowedInNonScript, into an error
  427. // REVIEW: why is this logic here? In any case these are errors already by default!
  428. let warningsAsErrorsArray =
  429. match warningsAsErrors with
  430. | null -> [|"76"|]
  431. | _ -> (warningsAsErrors + " 76 ").Split([|' '; ';'; ','|], StringSplitOptions.RemoveEmptyEntries)
  432. builder.AppendSwitchIfNotNull("--warnaserror:", warningsAsErrorsArray, ",")
  433. // Win32ResourceFile
  434. builder.AppendSwitchIfNotNull("--win32res:", win32res)
  435. // Win32ManifestFile
  436. builder.AppendSwitchIfNotNull("--win32manifest:", win32manifest)
  437. // VisualStudioStyleErrors
  438. if vserrors then
  439. builder.AppendSwitch("--vserrors")
  440. // ValidateTypeProviders
  441. if validateTypeProviders then
  442. builder.AppendSwitch("--validate-type-providers")
  443. builder.AppendSwitchIfNotNull("--LCID:", vslcid)
  444. if utf8output then
  445. builder.AppendSwitch("--utf8output")
  446. // When building using the fsc task, always emit the "fullpaths" flag to make the output easier
  447. // for the user to parse
  448. builder.AppendSwitch("--fullpaths")
  449. // When building using the fsc task, also emit "flaterrors" to ensure that multi-line error messages
  450. // aren't trimmed
  451. builder.AppendSwitch("--flaterrors")
  452. builder.AppendSwitchIfNotNull("--subsystemversion:", subsystemVersion)
  453. if highEntropyVA then
  454. builder.AppendSwitch("--highentropyva+")
  455. else
  456. builder.AppendSwitch("--highentropyva-")
  457. builder.AppendSwitchIfNotNull("--sqmsessionguid:", sqmSessionGuid)
  458. builder.AppendSwitchIfNotNull("--targetprofile:", targetProfile)
  459. // OtherFlags - must be second-to-last
  460. builder.AppendSwitchUnquotedIfNotNull("", otherFlags)
  461. capturedArguments <- builder.CapturedArguments()
  462. // Sources - these have to go last
  463. builder.AppendFileNamesIfNotNull(sources, " ")
  464. capturedFilenames <- builder.CapturedFilenames()
  465. let s = builder.ToString()
  466. s
  467. // expose this to internal components (for nunit testing)
  468. member internal fsc.InternalGenerateCommandLineCommands() =
  469. fsc.GenerateCommandLineCommands()
  470. member internal fsc.InternalExecuteTool(pathToTool, responseFileCommands, commandLineCommands) =
  471. fsc.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands)
  472. module Attributes =
  473. //[<assembly: System.Security.SecurityTransparent>]
  474. do()