PageRenderTime 65ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/compiler/3.0/Sep2012/src/fsharp/FSharp.Build/Fsc.fs

#
F# | 523 lines | 369 code | 53 blank | 101 comment | 42 complexity | ad9fdc004255167857a4af257f78562d 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 capturedArguments : string list = [] // list of individual args, to pass to HostObject Compile()
  138. let mutable capturedFilenames : string list = [] // list of individual source filenames, to pass to HostObject Compile()
  139. #if MONO
  140. #else
  141. do
  142. this.YieldDuringToolExecution <- true // See bug 6483; this makes parallel build faster, and is fine to set unconditionally
  143. #endif
  144. // --baseaddress
  145. member fsc.BaseAddress
  146. with get() = baseAddress
  147. and set(s) = baseAddress <- s
  148. // --codepage <int>: Specify the codepage to use when opening source files
  149. member fsc.CodePage
  150. with get() = codePage
  151. and set(s) = codePage <- s
  152. // -g: Produce debug file. Disables optimizations if a -O flag is not given.
  153. member fsc.DebugSymbols
  154. with get() = debugSymbols
  155. and set(b) = debugSymbols <- b
  156. // --debug <none/pdbonly/full>: Emit debugging information
  157. member fsc.DebugType
  158. with get() = debugType
  159. and set(s) = debugType <- s
  160. // --nowarn <string>: Do not report the given specific warning.
  161. member fsc.DisabledWarnings
  162. with get() = disabledWarnings
  163. and set(a) = disabledWarnings <- a
  164. // --define <string>: Define the given conditional compilation symbol.
  165. member fsc.DefineConstants
  166. with get() = defineConstants
  167. and set(a) = defineConstants <- a
  168. // --doc <string>: Write the xmldoc of the assembly to the given file.
  169. member fsc.DocumentationFile
  170. with get() = documentationFile
  171. and set(s) = documentationFile <- s
  172. // --generate-interface-file <string>:
  173. // Print the inferred interface of the
  174. // assembly to a file.
  175. member fsc.GenerateInterfaceFile
  176. with get() = generateInterfaceFile
  177. and set(s) = generateInterfaceFile <- s
  178. // --keyfile <string>:
  179. // Sign the assembly the given keypair file, as produced
  180. // by the .NET Framework SDK 'sn.exe' tool. This produces
  181. // an assembly with a strong name. This is only relevant if producing
  182. // an assembly to be shared amongst programs from different
  183. // directories, e.g. to be installed in the Global Assembly Cache.
  184. member fsc.KeyFile
  185. with get() = keyFile
  186. and set(s) = keyFile <- s
  187. // --noframework
  188. member fsc.NoFramework
  189. with get() = noFramework
  190. and set(b) = noFramework <- b
  191. // --optimize
  192. member fsc.Optimize
  193. with get() = optimize
  194. and set(p) = optimize <- p
  195. // --tailcalls
  196. member fsc.Tailcalls
  197. with get() = tailcalls
  198. and set(p) = tailcalls <- p
  199. // REVIEW: decide whether to keep this, for now is handy way to deal with as-yet-unimplemented features
  200. member fsc.OtherFlags
  201. with get() = otherFlags
  202. and set(s) = otherFlags <- s
  203. // -o <string>: Name the output file.
  204. member fsc.OutputAssembly
  205. with get() = outputAssembly
  206. and set(s) = outputAssembly <- s
  207. // --pdb <string>:
  208. // Name the debug output file.
  209. member fsc.PdbFile
  210. with get() = pdbFile
  211. and set(s) = pdbFile <- s
  212. // --platform <string>: Limit which platforms this code can run on:
  213. // x86
  214. // x64
  215. // Itanium
  216. // anycpu
  217. // anycpu32bitpreferred
  218. member fsc.Platform
  219. with get() = platform
  220. and set(s) = platform <- s
  221. // indicator whether anycpu32bitpreferred is applicable or not
  222. member fsc.Prefer32Bit
  223. with get() = prefer32bit
  224. and set(s) = prefer32bit <- s
  225. // -r <string>: Reference an F# or .NET assembly.
  226. member fsc.References
  227. with get() = references
  228. and set(a) = references <- a
  229. // --lib
  230. member fsc.ReferencePath
  231. with get() = referencePath
  232. and set(s) = referencePath <- s
  233. // --resource <string>: Embed the specified managed resources (.resource).
  234. // Produce .resource files from .resx files using resgen.exe or resxc.exe.
  235. member fsc.Resources
  236. with get() = resources
  237. and set(a) = resources <- a
  238. // source files
  239. member fsc.Sources
  240. with get() = sources
  241. and set(a) = sources <- a
  242. // --target exe: Produce an executable with a console
  243. // --target winexe: Produce an executable which does not have a
  244. // stdin/stdout/stderr
  245. // --target library: Produce a DLL
  246. // --target module: Produce a module that can be added to another assembly
  247. member fsc.TargetType
  248. with get() = targetType
  249. and set(s) = targetType <- s
  250. // --version-file <string>:
  251. member fsc.VersionFile
  252. with get() = versionFile
  253. and set(s) = versionFile <- s
  254. #if FX_ATLEAST_35
  255. #else
  256. // Allow overriding to the executable name "fsc.exe"
  257. member fsc.ToolExe
  258. with get() = toolExe
  259. and set(s) = toolExe<- s
  260. #endif
  261. // For targeting other folders for "fsc.exe" (or ToolExe if different)
  262. member fsc.ToolPath
  263. with get() = toolPath
  264. and set(s) = toolPath <- s
  265. // For specifying a win32 native resource file (.res)
  266. member fsc.Win32ResourceFile
  267. with get() = win32res
  268. and set(s) = win32res <- s
  269. // For specifying a win32 manifest file
  270. member fsc.Win32ManifestFile
  271. with get() = win32manifest
  272. and set(m) = win32manifest <- m
  273. // For specifying the warning level (0-4)
  274. member fsc.WarningLevel
  275. with get() = warningLevel
  276. and set(s) = warningLevel <- s
  277. member fsc.TreatWarningsAsErrors
  278. with get() = treatWarningsAsErrors
  279. and set(p) = treatWarningsAsErrors <- p
  280. member fsc.WarningsAsErrors
  281. with get() = warningsAsErrors
  282. and set(s) = warningsAsErrors <- s
  283. member fsc.VisualStudioStyleErrors
  284. with get() = vserrors
  285. and set(p) = vserrors <- p
  286. member fsc.ValidateTypeProviders
  287. with get() = validateTypeProviders
  288. and set(p) = validateTypeProviders <- p
  289. member fsc.LCID
  290. with get() = vslcid
  291. and set(p) = vslcid <- p
  292. member fsc.Utf8Output
  293. with get() = utf8output
  294. and set(p) = utf8output <- p
  295. member fsc.SubsystemVersion
  296. with get() = subsystemVersion
  297. and set(p) = subsystemVersion <- p
  298. member fsc.HighEntropyVA
  299. with get() = highEntropyVA
  300. and set(p) = highEntropyVA <- p
  301. // ToolTask methods
  302. override fsc.ToolName = "fsc.exe"
  303. override fsc.StandardErrorEncoding = if utf8output then System.Text.Encoding.UTF8 else base.StandardErrorEncoding
  304. override fsc.StandardOutputEncoding = if utf8output then System.Text.Encoding.UTF8 else base.StandardOutputEncoding
  305. override fsc.GenerateFullPathToTool() =
  306. if toolPath = "" then
  307. raise (new System.InvalidOperationException(FSBuild.SR.toolpathUnknown()))
  308. System.IO.Path.Combine(toolPath, fsc.ToolExe)
  309. member internal fsc.InternalGenerateFullPathToTool() = fsc.GenerateFullPathToTool() // expose for unit testing
  310. member internal fsc.BaseExecuteTool(pathToTool, responseFileCommands, commandLineCommands) = // F# does not allow protected members to be captured by lambdas, this is the standard workaround
  311. base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands)
  312. /// Intercept the call to ExecuteTool to handle the host compile case.
  313. override fsc.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands) =
  314. let ho = box fsc.HostObject
  315. match ho with
  316. | null ->
  317. base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands)
  318. | _ ->
  319. let sources = sources|>Array.map(fun i->i.ItemSpec)
  320. let baseCall = fun (dummy : int) -> fsc.BaseExecuteTool(pathToTool, responseFileCommands, commandLineCommands)
  321. // We are using a Converter<int,int> rather than a "unit->int" because it is too hard to
  322. // figure out how to pass an F# function object via reflection.
  323. let baseCallDelegate = new System.Converter<int,int>(baseCall)
  324. try
  325. let ret =
  326. (ho.GetType()).InvokeMember("Compile", BindingFlags.Public ||| BindingFlags.NonPublic ||| BindingFlags.InvokeMethod ||| BindingFlags.Instance, null, ho,
  327. [| box baseCallDelegate; box (capturedArguments |> List.toArray); box (capturedFilenames |> List.toArray) |],
  328. System.Globalization.CultureInfo.InvariantCulture)
  329. unbox ret
  330. with
  331. #if MONO
  332. #else
  333. | :? System.Reflection.TargetInvocationException as tie when (match tie.InnerException with | :? Microsoft.Build.Exceptions.BuildAbortedException -> true | _ -> false) ->
  334. fsc.Log.LogError(tie.InnerException.Message, [| |])
  335. -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
  336. #endif
  337. | e ->
  338. 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()))
  339. reraise()
  340. override fsc.GenerateCommandLineCommands() =
  341. let builder = new FscCommandLineBuilder()
  342. // OutputAssembly
  343. builder.AppendSwitchIfNotNull("-o:", outputAssembly)
  344. // CodePage
  345. builder.AppendSwitchIfNotNull("--codepage:", codePage)
  346. // Debug
  347. if debugSymbols then
  348. builder.AppendSwitch("-g")
  349. // DebugType
  350. builder.AppendSwitchIfNotNull("--debug:",
  351. if debugType = null then null else
  352. match debugType.ToUpperInvariant() with
  353. | "NONE" -> null
  354. | "PDBONLY" -> "pdbonly"
  355. | "FULL" -> "full"
  356. | _ -> null)
  357. // NoFramework
  358. if noFramework then
  359. builder.AppendSwitch("--noframework")
  360. // BaseAddress
  361. builder.AppendSwitchIfNotNull("--baseaddress:", baseAddress)
  362. // DefineConstants
  363. for item in defineConstants do
  364. builder.AppendSwitchIfNotNull("--define:", item.ItemSpec)
  365. // DocumentationFile
  366. builder.AppendSwitchIfNotNull("--doc:", documentationFile)
  367. // GenerateInterfaceFile
  368. builder.AppendSwitchIfNotNull("--sig:", generateInterfaceFile)
  369. // KeyFile
  370. builder.AppendSwitchIfNotNull("--keyfile:", keyFile)
  371. // Optimize
  372. if optimize then
  373. builder.AppendSwitch("--optimize+")
  374. else
  375. builder.AppendSwitch("--optimize-")
  376. if not tailcalls then
  377. builder.AppendSwitch("--tailcalls-")
  378. // PdbFile
  379. builder.AppendSwitchIfNotNull("--pdb:", pdbFile)
  380. // Platform
  381. builder.AppendSwitchIfNotNull("--platform:",
  382. let ToUpperInvariant (s:string) = if s = null then null else s.ToUpperInvariant()
  383. match ToUpperInvariant(platform), prefer32bit, ToUpperInvariant(targetType) with
  384. | "ANYCPU", true, "EXE"
  385. | "ANYCPU", true, "WINEXE" -> "anycpu32bitpreferred"
  386. | "ANYCPU", _, _ -> "anycpu"
  387. | "X86" , _, _ -> "x86"
  388. | "X64" , _, _ -> "x64"
  389. | "ITANIUM", _, _ -> "Itanium"
  390. | _ -> null)
  391. // Resources
  392. for item in resources do
  393. builder.AppendSwitchIfNotNull("--resource:", item.ItemSpec)
  394. // VersionFile
  395. builder.AppendSwitchIfNotNull("--versionfile:", versionFile)
  396. // References
  397. for item in references do
  398. builder.AppendSwitchIfNotNull("-r:", item.ItemSpec)
  399. // ReferencePath
  400. let referencePathArray = // create a array of strings
  401. match referencePath with
  402. | null -> null
  403. | _ -> referencePath.Split([|';'; ','|], StringSplitOptions.RemoveEmptyEntries)
  404. builder.AppendSwitchIfNotNull("--lib:", referencePathArray, ",")
  405. // TargetType
  406. builder.AppendSwitchIfNotNull("--target:",
  407. if targetType = null then null else
  408. match targetType.ToUpperInvariant() with
  409. | "LIBRARY" -> "library"
  410. | "EXE" -> "exe"
  411. | "WINEXE" -> "winexe"
  412. | "MODULE" -> "module"
  413. | _ -> null)
  414. // NoWarn
  415. match disabledWarnings with
  416. | null -> ()
  417. | _ -> builder.AppendSwitchIfNotNull("--nowarn:", disabledWarnings.Split([|' '; ';'; ','; '\r'; '\n'|], StringSplitOptions.RemoveEmptyEntries), ",")
  418. // WarningLevel
  419. builder.AppendSwitchIfNotNull("--warn:", warningLevel)
  420. // TreatWarningsAsErrors
  421. if treatWarningsAsErrors then
  422. builder.AppendSwitch("--warnaserror")
  423. // WarningsAsErrors
  424. // Change warning 76, HashReferenceNotAllowedInNonScript/HashDirectiveNotAllowedInNonScript/HashIncludeNotAllowedInNonScript, into an error
  425. // REVIEW: why is this logic here? In any case these are errors already by default!
  426. let warningsAsErrorsArray =
  427. match warningsAsErrors with
  428. | null -> [|"76"|]
  429. | _ -> (warningsAsErrors + " 76 ").Split([|' '; ';'; ','|], StringSplitOptions.RemoveEmptyEntries)
  430. builder.AppendSwitchIfNotNull("--warnaserror:", warningsAsErrorsArray, ",")
  431. // Win32ResourceFile
  432. builder.AppendSwitchIfNotNull("--win32res:", win32res)
  433. // Win32ManifestFile
  434. builder.AppendSwitchIfNotNull("--win32manifest:", win32manifest)
  435. // VisualStudioStyleErrors
  436. if vserrors then
  437. builder.AppendSwitch("--vserrors")
  438. // ValidateTypeProviders
  439. if validateTypeProviders then
  440. builder.AppendSwitch("--validate-type-providers")
  441. builder.AppendSwitchIfNotNull("--LCID:", vslcid)
  442. if utf8output then
  443. builder.AppendSwitch("--utf8output")
  444. // When building using the fsc task, always emit the "fullpaths" flag to make the output easier
  445. // for the user to parse
  446. builder.AppendSwitch("--fullpaths")
  447. // When building using the fsc task, also emit "flaterrors" to ensure that multi-line error messages
  448. // aren't trimmed
  449. builder.AppendSwitch("--flaterrors")
  450. builder.AppendSwitchIfNotNull("--subsystemversion:", subsystemVersion)
  451. if highEntropyVA then
  452. builder.AppendSwitch("--highentropyva+")
  453. else
  454. builder.AppendSwitch("--highentropyva-")
  455. // OtherFlags - must be second-to-last
  456. builder.AppendSwitchUnquotedIfNotNull("", otherFlags)
  457. capturedArguments <- builder.CapturedArguments()
  458. // Sources - these have to go last
  459. builder.AppendFileNamesIfNotNull(sources, " ")
  460. capturedFilenames <- builder.CapturedFilenames()
  461. let s = builder.ToString()
  462. s
  463. // expose this to internal components (for nunit testing)
  464. member internal fsc.InternalGenerateCommandLineCommands() =
  465. fsc.GenerateCommandLineCommands()
  466. member internal fsc.InternalExecuteTool(pathToTool, responseFileCommands, commandLineCommands) =
  467. fsc.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands)
  468. module Attributes =
  469. //[<assembly: System.Security.SecurityTransparent>]
  470. do()