PageRenderTime 59ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/compiler/3.1/Nov2013/src/fsharp/ReferenceResolution.fs

#
F# | 442 lines | 351 code | 47 blank | 44 comment | 45 complexity | 767d3f392236958b4dcc32c5fc43fb6b MD5 | raw file
Possible License(s): CC-BY-SA-3.0, Apache-2.0
  1. namespace Viz
  2. /// This type exists to have a concrete 'Target' type for a DebuggerVisualizerAttribute.
  3. /// Ideally it would be out in its own assembly, but then the compiler would need to take a dependency on that assembly, so instead we
  4. /// pragmatically just shove this into the compiler assembly itself.
  5. type internal Visualizable(o:obj) =
  6. member this.Data = o
  7. /// assuming this assembly is already in the debuggee process, then Viz.Visualiable.Make(foo) in the Watch window will make a visualizer for foo
  8. static member Make(o:obj) = new Visualizable(o)
  9. namespace Microsoft.FSharp.Compiler
  10. module internal MSBuildResolver =
  11. open Microsoft.FSharp.Compiler.AbstractIL.Internal.Library
  12. exception ResolutionFailure
  13. type ResolvedFrom =
  14. | AssemblyFolders
  15. | AssemblyFoldersEx
  16. | TargetFrameworkDirectory
  17. | RawFileName
  18. | GlobalAssemblyCache
  19. | Path of string
  20. | Unknown
  21. type ResolutionEnvironment = CompileTimeLike | RuntimeLike | DesigntimeLike
  22. #if SILVERLIGHT
  23. #else
  24. open System
  25. open Microsoft.Build.Tasks
  26. open Microsoft.Build.Utilities
  27. open Microsoft.Build.Framework
  28. open Microsoft.Build.BuildEngine
  29. open System.IO
  30. type ResolvedFile = {
  31. itemSpec:string
  32. resolvedFrom:ResolvedFrom
  33. fusionName:string
  34. version:string
  35. redist:string
  36. baggage:string
  37. }
  38. with override this.ToString() = sprintf "ResolvedFile(%s)" this.itemSpec
  39. type ResolutionResults = {
  40. resolvedFiles:ResolvedFile array
  41. referenceDependencyPaths:string array
  42. relatedPaths:string array
  43. referenceSatellitePaths:string array
  44. referenceScatterPaths:string array
  45. referenceCopyLocalPaths:string array
  46. suggestedBindingRedirects:string array
  47. }
  48. let DotNetFrameworkReferenceAssembliesRootDirectory =
  49. // Note that ProgramFilesX86 is correct for both x86 and x64 architectures (the reference assemblies are always in the 32-bit location, which is PF(x86) on an x64 machine)
  50. let PF =
  51. //System.Environment.GetFolderPath(System.Environment.SpecialFolder.ProgramFilesX86) // This API is not available to bootstrap compiler
  52. match System.Environment.GetEnvironmentVariable("ProgramFiles(x86)") with
  53. | null -> System.Environment.GetEnvironmentVariable("ProgramFiles") // if PFx86 is null, then we are 32-bit and just get PF
  54. | s -> s
  55. PF + @"\Reference Assemblies\Microsoft\Framework\.NETFramework"
  56. let ReplaceFrameworkVariables(dirs) =
  57. let windowsFramework = System.Environment.GetEnvironmentVariable("windir")+ @"\Microsoft.NET\Framework"
  58. let referenceAssemblies = DotNetFrameworkReferenceAssembliesRootDirectory
  59. dirs|>List.map(fun (d:string)->d.Replace("{WindowsFramework}",windowsFramework).Replace("{ReferenceAssemblies}",referenceAssemblies))
  60. // ATTENTION!: the following code needs to be updated every time we are switching to the new MSBuild version because new .NET framework version was released
  61. // 1. List of frameworks
  62. // 2. DeriveTargetFrameworkDirectoriesFor45Plus
  63. // 3. HighestInstalledNetFrameworkVersionMajorMinor
  64. // 4. GetPathToDotNetFramework
  65. [<Literal>]
  66. let private Net10 = "v1.0"
  67. [<Literal>]
  68. let private Net11 = "v1.1"
  69. [<Literal>]
  70. let private Net20 = "v2.0"
  71. [<Literal>]
  72. let private Net30 = "v3.0"
  73. [<Literal>]
  74. let private Net35 = "v3.5"
  75. [<Literal>]
  76. let private Net40 = "v4.0"
  77. [<Literal>]
  78. let private Net45 = "v4.5"
  79. [<Literal>]
  80. let private Net451 = "v4.5.1"
  81. let SupportedNetFrameworkVersions = set [ Net20; Net30; Net35; Net40; Net45; Net451; (*SL only*) "v5.0" ]
  82. let GetPathToDotNetFramework(v) =
  83. #if FX_ATLEAST_45
  84. let v =
  85. match v with
  86. | Net11 -> Some TargetDotNetFrameworkVersion.Version11
  87. | Net20 -> Some TargetDotNetFrameworkVersion.Version20
  88. | Net30 -> Some TargetDotNetFrameworkVersion.Version30
  89. | Net35 -> Some TargetDotNetFrameworkVersion.Version35
  90. | Net40 -> Some TargetDotNetFrameworkVersion.Version40
  91. | Net45 -> Some TargetDotNetFrameworkVersion.Version45
  92. | Net451 -> Some TargetDotNetFrameworkVersion.Version451
  93. | _ -> assert false; None
  94. match v with
  95. | Some v ->
  96. match ToolLocationHelper.GetPathToDotNetFramework v with
  97. | null -> []
  98. | x -> [x]
  99. | _ -> []
  100. #else
  101. // FX_ATLEAST_45 is not defined is required for step when we build compiler with proto compiler and this branch should not be hit
  102. // assert false
  103. []
  104. #endif
  105. let DeriveTargetFrameworkDirectoriesFor40Plus(version) =
  106. #if FX_ATLEAST_45
  107. // starting with .Net 4.0, the runtime dirs (WindowsFramework) are never used by MSBuild RAR
  108. let v =
  109. match version with
  110. | Net40 -> Some TargetDotNetFrameworkVersion.Version40
  111. | Net45 -> Some TargetDotNetFrameworkVersion.Version45
  112. | Net451 -> Some TargetDotNetFrameworkVersion.Version451
  113. | _ -> assert false; None // unknown version - some parts in the code are not synced
  114. match v with
  115. | Some v ->
  116. match ToolLocationHelper.GetPathToDotNetFrameworkReferenceAssemblies v with
  117. | null -> []
  118. | x -> [x]
  119. | None -> []
  120. #else
  121. // FX_ATLEAST_45 is not defined is required for step when we build compiler with proto compiler and this branch should not be hit
  122. //assert false
  123. []
  124. #endif
  125. /// Determine the default "frameworkVersion" (which is passed into MSBuild resolve).
  126. /// This code uses MSBuild to determine version of the highest installed framework.
  127. let HighestInstalledNetFrameworkVersionMajorMinor() =
  128. #if FX_ATLEAST_45
  129. if box (ToolLocationHelper.GetPathToDotNetFramework(TargetDotNetFrameworkVersion.Version451)) <> null then 4, Net451
  130. elif box (ToolLocationHelper.GetPathToDotNetFramework(TargetDotNetFrameworkVersion.Version45)) <> null then 4, Net45
  131. else 4, Net40 // version is 4.0 assumed since this code is running.
  132. #else
  133. // FX_ATLEAST_45 is not defined is required for step when we build compiler with proto compiler and this branch should not be hit
  134. 4, Net40
  135. #endif
  136. /// Derive the target framework directories.
  137. let DeriveTargetFrameworkDirectories
  138. (targetFrameworkVersion:string, // e.g. v2.0, v3.0, v3.5, v4.0 etc
  139. logmessage:string->unit) =
  140. let targetFrameworkVersion =
  141. if not(targetFrameworkVersion.StartsWith("v",StringComparison.Ordinal)) then "v"^targetFrameworkVersion
  142. else targetFrameworkVersion
  143. let FrameworkStartsWith(short) =
  144. targetFrameworkVersion.StartsWith(short,StringComparison.Ordinal)
  145. let result =
  146. if FrameworkStartsWith(Net10) then ReplaceFrameworkVariables([@"{WindowsFramework}\v1.0.3705"])
  147. else if FrameworkStartsWith(Net11) then ReplaceFrameworkVariables([@"{WindowsFramework}\v1.1.4322"])
  148. else if FrameworkStartsWith(Net20) then ReplaceFrameworkVariables([@"{WindowsFramework}\v2.0.50727"])
  149. else if FrameworkStartsWith(Net30) then ReplaceFrameworkVariables([@"{ReferenceAssemblies}\v3.0"; @"{WindowsFramework}\v3.0"; @"{WindowsFramework}\v2.0.50727"])
  150. else if FrameworkStartsWith(Net35) then ReplaceFrameworkVariables([@"{ReferenceAssemblies}\v3.5"; @"{WindowsFramework}\v3.5"; @"{ReferenceAssemblies}\v3.0"; @"{WindowsFramework}\v3.0"; @"{WindowsFramework}\v2.0.50727"])
  151. else DeriveTargetFrameworkDirectoriesFor40Plus(targetFrameworkVersion)
  152. let result = result |> Array.ofList
  153. logmessage (sprintf "Derived target framework directories for version %s are: %s" targetFrameworkVersion (String.Join(",", result)))
  154. result
  155. /// Decode the ResolvedFrom code from MSBuild.
  156. let DecodeResolvedFrom(resolvedFrom:string) : ResolvedFrom =
  157. let Same a b =
  158. String.CompareOrdinal(a,b) = 0
  159. match resolvedFrom with
  160. | r when Same "{RawFileName}" r -> RawFileName
  161. | r when Same "{GAC}" r -> GlobalAssemblyCache
  162. | r when Same "{TargetFrameworkDirectory}" r -> TargetFrameworkDirectory
  163. | r when Same "{AssemblyFolders}" r -> AssemblyFolders
  164. | r when r.Length >= 10 && Same "{Registry:" (r.Substring(0,10)) -> AssemblyFoldersEx
  165. | r -> ResolvedFrom.Path r
  166. type ErrorWarningCallbackSig = ((*code:*)string->(*message*)string->unit)
  167. type Foregrounded =
  168. | ForegroundedMessage of string
  169. | ForegroundedError of string * string
  170. | ForegroundedWarning of string * string
  171. let ResolveCore(
  172. resolutionEnvironment: ResolutionEnvironment,
  173. references:(string*(*baggage*)string)[],
  174. targetFrameworkVersion:string,
  175. targetFrameworkDirectories:string list,
  176. targetProcessorArchitecture:string,
  177. outputDirectory:string,
  178. fsharpCoreExplicitDirOrFSharpBinariesDir:string,
  179. explicitIncludeDirs:string list,
  180. implicitIncludeDir:string,
  181. frameworkRegistryBase:string,
  182. assemblyFoldersSuffix:string,
  183. assemblyFoldersConditions:string,
  184. allowRawFileName:bool,
  185. logmessage:string->unit,
  186. logwarning:ErrorWarningCallbackSig,
  187. logerror:ErrorWarningCallbackSig ) =
  188. // Message Foregrounding:
  189. // In version 4.0 MSBuild began calling log methods on a background (non-UI) thread. If there is an exception thrown from
  190. // logmessage, logwarning or logerror then it would kill the process.
  191. // The fix is to catch these exceptions and log the rest of the messages to a list to output at the end.
  192. // It looks simpler to always just accumulate the messages during resolution and show them all at the end, but then
  193. // we couldn't see the messages as resolution progresses.
  194. let foregrounded = ref []
  195. let backgroundException : exn option ref = ref None
  196. let logmessage message =
  197. match !backgroundException with
  198. | Some _ -> foregrounded := ForegroundedMessage(message) :: !foregrounded
  199. | None ->
  200. try
  201. logmessage message
  202. with e ->
  203. backgroundException := Some(e)
  204. foregrounded := ForegroundedMessage(message) :: !foregrounded
  205. let logwarning code message =
  206. match !backgroundException with
  207. | Some _ -> foregrounded := ForegroundedWarning(code,message) :: !foregrounded
  208. | None ->
  209. try
  210. logwarning code message
  211. with e ->
  212. backgroundException := Some(e)
  213. foregrounded := ForegroundedWarning(code,message) :: !foregrounded
  214. let logerror code message =
  215. match !backgroundException with
  216. | Some _ -> foregrounded := ForegroundedError(code,message) :: !foregrounded
  217. | None ->
  218. try
  219. logerror code message
  220. with e ->
  221. backgroundException := Some(e)
  222. foregrounded := ForegroundedError(code,message) :: !foregrounded
  223. let engine = { new IBuildEngine with
  224. member be.BuildProjectFile(projectFileName, targetNames, globalProperties, targetOutputs) = true
  225. member be.LogCustomEvent(e) = logmessage e.Message
  226. member be.LogErrorEvent(e) = logerror e.Code e.Message
  227. member be.LogMessageEvent(e) = logmessage e.Message
  228. member be.LogWarningEvent(e) = logwarning e.Code e.Message
  229. member be.ColumnNumberOfTaskNode with get() = 1
  230. member be.LineNumberOfTaskNode with get() = 1
  231. member be.ContinueOnError with get() = true
  232. member be.ProjectFileOfTaskNode with get() = "" }
  233. let rar = new ResolveAssemblyReference()
  234. rar.BuildEngine <- engine
  235. // Derive target framework directory if none was supplied.
  236. let targetFrameworkDirectories =
  237. if targetFrameworkDirectories=[] then DeriveTargetFrameworkDirectories(targetFrameworkVersion, logmessage)
  238. else targetFrameworkDirectories |> Array.ofList
  239. // Filter for null and zero length, and escape backslashes so legitimate path characters aren't mistaken for
  240. // escape characters (E.g., ".\r.dll")
  241. let explicitIncludeDirs = explicitIncludeDirs |> List.filter(fun eid->not(String.IsNullOrEmpty(eid)))
  242. let references = references |> Array.filter(fun (path,_)->not(String.IsNullOrEmpty(path))) // |> Array.map (fun (path,baggage) -> (path.Replace("\\","\\\\"),baggage))
  243. rar.TargetFrameworkDirectories <- targetFrameworkDirectories
  244. rar.FindRelatedFiles <- false
  245. rar.FindDependencies <- false
  246. rar.FindSatellites <- false
  247. rar.FindSerializationAssemblies <- false
  248. #if BUILDING_WITH_LKG
  249. ignore targetProcessorArchitecture
  250. #else
  251. rar.TargetedRuntimeVersion <- typeof<obj>.Assembly.ImageRuntimeVersion
  252. rar.TargetProcessorArchitecture <- targetProcessorArchitecture
  253. rar.CopyLocalDependenciesWhenParentReferenceInGac <- true
  254. #endif
  255. rar.Assemblies <- [|for (referenceName,baggage) in references ->
  256. let item = new Microsoft.Build.Utilities.TaskItem(referenceName)
  257. item.SetMetadata("Baggage", baggage)
  258. item:>ITaskItem|]
  259. let rawFileNamePath = if allowRawFileName then ["{RawFileName}"] else []
  260. let searchPaths =
  261. match resolutionEnvironment with
  262. | DesigntimeLike
  263. | RuntimeLike ->
  264. logmessage("Using scripting resolution precedence.")
  265. // These are search paths for runtime-like or scripting resolution. GAC searching is present.
  266. rawFileNamePath @ // Quick-resolve straight to filename first
  267. explicitIncludeDirs @ // From -I, #I
  268. [implicitIncludeDir] @ // Usually the project directory
  269. [fsharpCoreExplicitDirOrFSharpBinariesDir] @ // Location of explicit reference to FSharp.Core, otherwise location of fsc.exe
  270. ["{TargetFrameworkDirectory}"] @
  271. [sprintf "{Registry:%s,%s,%s%s}" frameworkRegistryBase targetFrameworkVersion assemblyFoldersSuffix assemblyFoldersConditions] @
  272. ["{AssemblyFolders}"] @
  273. ["{GAC}"]
  274. | CompileTimeLike ->
  275. logmessage("Using compilation resolution precedence.")
  276. // These are search paths for compile-like resolution. GAC searching is not present.
  277. ["{TargetFrameworkDirectory}"] @
  278. rawFileNamePath @ // Quick-resolve straight to filename first
  279. explicitIncludeDirs @ // From -I, #I
  280. [implicitIncludeDir] @ // Usually the project directory
  281. [fsharpCoreExplicitDirOrFSharpBinariesDir] @ // Location of explicit reference to FSharp.Core, otherwise location of fsc.exe
  282. [sprintf "{Registry:%s,%s,%s%s}" frameworkRegistryBase targetFrameworkVersion assemblyFoldersSuffix assemblyFoldersConditions] @ // Like {Registry:Software\Microsoft\.NETFramework,v2.0,AssemblyFoldersEx}
  283. ["{AssemblyFolders}"] @
  284. [outputDirectory] @
  285. ["{GAC}"] @
  286. GetPathToDotNetFramework targetFrameworkVersion // use path to implementation assemblies as the last resort
  287. rar.SearchPaths <- searchPaths |> Array.ofList
  288. rar.AllowedAssemblyExtensions <- [| ".dll" ; ".exe" |]
  289. let succeeded = rar.Execute()
  290. // Unroll any foregrounded messages
  291. match !backgroundException with
  292. | Some(backGroundException) ->
  293. logwarning "" "Saw error on logger thread during resolution."
  294. logwarning "" (sprintf "%A" backGroundException)
  295. logwarning "" "Showing messages seen after exception."
  296. !foregrounded
  297. |> List.iter(fun message->
  298. match message with
  299. | ForegroundedMessage(message) -> logmessage message
  300. | ForegroundedWarning(code,message) -> logwarning code message
  301. | ForegroundedError(code,message) -> logerror code message )
  302. | None -> ()
  303. if not succeeded then
  304. raise ResolutionFailure
  305. {
  306. resolvedFiles = [| for p in rar.ResolvedFiles -> {itemSpec = p.ItemSpec;
  307. resolvedFrom = DecodeResolvedFrom(p.GetMetadata("ResolvedFrom"));
  308. fusionName = p.GetMetadata("FusionName");
  309. version = p.GetMetadata("Version");
  310. redist = p.GetMetadata("Redist");
  311. baggage = p.GetMetadata("Baggage") } |]
  312. referenceDependencyPaths = [| for p in rar.ResolvedDependencyFiles -> p.ItemSpec |]
  313. relatedPaths = [| for p in rar.RelatedFiles -> p.ItemSpec |]
  314. referenceSatellitePaths = [| for p in rar.SatelliteFiles -> p.ItemSpec |]
  315. referenceScatterPaths = [| for p in rar.ScatterFiles -> p.ItemSpec |]
  316. referenceCopyLocalPaths = [| for p in rar.CopyLocalFiles -> p.ItemSpec |]
  317. suggestedBindingRedirects = [| for p in rar.SuggestedRedirects -> p.ItemSpec |]
  318. }
  319. let Resolve(
  320. resolutionEnvironment: ResolutionEnvironment,
  321. references:(string*(*baggage*)string)[],
  322. targetFrameworkVersion:string,
  323. targetFrameworkDirectories:string list,
  324. targetProcessorArchitecture:string,
  325. outputDirectory:string,
  326. fsharpCoreExplicitDirOrFSharpBinariesDir:string,
  327. explicitIncludeDirs:string list,
  328. implicitIncludeDir:string,
  329. frameworkRegistryBase:string,
  330. assemblyFoldersSuffix:string,
  331. assemblyFoldersConditions:string,
  332. logmessage:string->unit,
  333. logwarning:ErrorWarningCallbackSig,
  334. logerror:ErrorWarningCallbackSig ) =
  335. // The {RawFileName} target is 'dangerous', in the sense that is uses Directory.GetCurrentDirectory() to resolve unrooted file paths.
  336. // It is unreliable to use this mutable global state inside Visual Studio. As a result, we partition all references into a "rooted" set
  337. // (which contains e.g. C:\MyDir\MyAssem.dll) and "unrooted" (everything else). We only allow "rooted" to use {RawFileName}. Note that
  338. // unrooted may still find 'local' assemblies by virtue of the fact that "implicitIncludeDir" is one of the places searched during
  339. // assembly resolution.
  340. let references = references |> Array.map (fun ((file,baggage) as data) ->
  341. // However, MSBuild will not resolve 'relative' paths, even when e.g. implicitIncludeDir is part of the search. As a result,
  342. // if we have an unrooted path+filename, we'll assume this is relative to the project directory and root it.
  343. if FileSystem.IsPathRootedShim(file) then
  344. data // fine, e.g. "C:\Dir\foo.dll"
  345. elif not(file.Contains("\\") || file.Contains("/")) then
  346. data // fine, e.g. "System.Transactions.dll"
  347. else
  348. // we have a 'relative path', e.g. "bin/Debug/foo.exe" or "..\Yadda\bar.dll"
  349. // turn it into an absolute path based at implicitIncludeDir
  350. (System.IO.Path.Combine(implicitIncludeDir, file), baggage)
  351. )
  352. let rooted, unrooted = references |> Array.partition (fun (file,_baggage) -> FileSystem.IsPathRootedShim(file))
  353. let CallResolveCore(references, allowRawFileName) =
  354. if Array.isEmpty references then
  355. {
  356. resolvedFiles = [| |]
  357. referenceDependencyPaths = [| |]
  358. relatedPaths = [| |]
  359. referenceSatellitePaths = [| |]
  360. referenceScatterPaths = [| |]
  361. referenceCopyLocalPaths = [| |]
  362. suggestedBindingRedirects = [| |]
  363. }
  364. else
  365. // all the params are the same...
  366. ResolveCore(
  367. resolutionEnvironment,
  368. references, // ... except this
  369. targetFrameworkVersion,
  370. targetFrameworkDirectories,
  371. targetProcessorArchitecture,
  372. outputDirectory,
  373. fsharpCoreExplicitDirOrFSharpBinariesDir,
  374. explicitIncludeDirs,
  375. implicitIncludeDir,
  376. frameworkRegistryBase,
  377. assemblyFoldersSuffix,
  378. assemblyFoldersConditions,
  379. allowRawFileName, // ... and this
  380. logmessage,
  381. logwarning,
  382. logerror)
  383. let rootedResults = CallResolveCore(rooted, true)
  384. let unrootedResults = CallResolveCore(unrooted, false)
  385. // now unify the two sets of results
  386. {
  387. resolvedFiles = Array.concat [| rootedResults.resolvedFiles; unrootedResults.resolvedFiles |]
  388. referenceDependencyPaths = set rootedResults.referenceDependencyPaths |> Set.union (set unrootedResults.referenceDependencyPaths) |> Set.toArray
  389. relatedPaths = set rootedResults.relatedPaths |> Set.union (set unrootedResults.relatedPaths) |> Set.toArray
  390. referenceSatellitePaths = set rootedResults.referenceSatellitePaths |> Set.union (set unrootedResults.referenceSatellitePaths) |> Set.toArray
  391. referenceScatterPaths = set rootedResults.referenceScatterPaths |> Set.union (set unrootedResults.referenceScatterPaths) |> Set.toArray
  392. referenceCopyLocalPaths = set rootedResults.referenceCopyLocalPaths |> Set.union (set unrootedResults.referenceCopyLocalPaths) |> Set.toArray
  393. suggestedBindingRedirects = set rootedResults.suggestedBindingRedirects |> Set.union (set unrootedResults.suggestedBindingRedirects) |> Set.toArray
  394. }
  395. #endif