@@ -36,6 +36,37 @@ module internal ExtensionTyping =
3636 temporaryFolder: string }
3737
3838
39+ // Specify the tooling-compatible fragments of a path such as:
40+ // typeproviders/fsharp41/net461/MyProvider.DesignTime.dll
41+ // See https://github.com/Microsoft/visualfsharp/issues/3736
42+
43+ // Represents the FF#-compiler <-> type provider protocol.
44+ // When the API or protocol updates, add a new version moniker to the front of the list here.
45+ let toolingCompatibleTypeProviderProtocolMonikers () =
46+ [ " fsharp41" ]
47+
48+ // Detect the host tooling context
49+ let toolingCompatibleVersions () =
50+ if typeof< obj>. Assembly.GetName() .Name= " mscorlib" then
51+ [ " net461" ; " net452" ; " net451" ; " net45" ; " netstandard2.0" ]
52+ elif typeof< obj>. Assembly.GetName() .Name= " System.Private.CoreLib" then
53+ [ " netcoreapp2.0" ; " netstandard2.0" ]
54+ else
55+ System.Diagnostics.Debug.Assert( false , " Couldn't determine runtime tooling context, assuming it supports at least .NET Standard 2.0" )
56+ [ " netstandard2.0" ]
57+
58+ // When significant new processor types appear add a new moniker here. Note that use of this qualifier will be very rare
59+ // and we don't expect different design-time assemblies will be needed for different architectures very often. Some
60+ // exceptions may be design-time components for type providers for systems such as Python or R.
61+ let toolingCompatibleArch () = if sizeof< nativeint> = 8 then " x64" else " x86"
62+ let toolingCompatiblePaths () =
63+ [ for protocolin toolingCompatibleTypeProviderProtocolMonikers() do
64+ for netRuntimein toolingCompatibleVersions() do
65+ let dir = Path.Combine( " typeproviders" , protocol, netRuntime)
66+ yield Path.Combine( dir, toolingCompatibleArch())
67+ yield dir
68+ ]
69+
3970/// Load a the design-time part of a type-provider into the host process, and look for types
4071/// marked with the TypeProviderAttribute attribute.
4172let GetTypeProviderImplementationTypes ( runTimeAssemblyFileName , designTimeAssemblyNameString , m : range ) =
@@ -46,32 +77,58 @@ module internal ExtensionTyping =
4677
4778// Find and load the designer assembly for the type provider component.
4879//
49- // If the assembly name ends with .dll, or is just a simple name, we look in the directory next to runtime assembly.
50- // Else we only look in the GAC.
51- let designTimeAssemblyOpt =
52- let loadFromDir fileName =
80+ // We look in the directories stepping up from the location of the runtime assembly.
81+
82+ let loadFromLocation designTimeAssemblyPath =
83+ try
84+ Some( FileSystem.AssemblyLoadFrom designTimeAssemblyPath)
85+ with e->
86+ raiseError e
87+
88+ let rec searchParentDirChain dir designTimeAssemblyName =
89+ seq {
90+ for subdirin toolingCompatiblePaths() do
91+ let designTimeAssemblyPath = Path.Combine( dir, subdir, designTimeAssemblyName)
92+ if FileSystem.SafeExists designTimeAssemblyPaththen
93+ yield loadFromLocation designTimeAssemblyPath
94+ match Path.GetDirectoryName( dir) with
95+ | swhen s= " " || s= null || Path.GetFileName( dir) = " packages" || s= dir-> ()
96+ | parentDir-> yield ! searchParentDirChain parentDir designTimeAssemblyName
97+ }
98+
99+ let loadFromParentDirRelativeToRuntimeAssemblyLocation designTimeAssemblyName =
100+ let runTimeAssemblyPath = Path.GetDirectoryName runTimeAssemblyFileName
101+ searchParentDirChain runTimeAssemblyPath designTimeAssemblyName
102+ |> Seq.tryHead
103+ |> function
104+ | Some res-> res
105+ | None->
106+ // The search failed, just load from the first location and report an error
53107let runTimeAssemblyPath = Path.GetDirectoryName runTimeAssemblyFileName
54- let designTimeAssemblyPath = Path.Combine( runTimeAssemblyPath, fileName)
55- try
56- Some( FileSystem.AssemblyLoadFrom designTimeAssemblyPath)
57- with e->
58- raiseError e
59- let loadFromGac () =
60- try
61- let asmName = System.Reflection.AssemblyName designTimeAssemblyNameString
62- Some( FileSystem.AssemblyLoad( asmName))
63- with e->
64- raiseError e
108+ loadFromLocation( Path.Combine( runTimeAssemblyPath, designTimeAssemblyName))
109+
110+ let designTimeAssemblyOpt =
65111
66112if designTimeAssemblyNameString.EndsWith( " .dll" , StringComparison.OrdinalIgnoreCase) then
67- loadFromDir designTimeAssemblyNameString
113+ loadFromParentDirRelativeToRuntimeAssemblyLocation designTimeAssemblyNameString
68114else
69- let name = System.Reflection.AssemblyName designTimeAssemblyNameString
115+ // Cover the case where the ".dll" extension has been left off and no version etc. has been used in the assembly
116+ // string specification. The Name=FullName comparison is particularly strange, and was there to support
117+ // design-time DLLs specified using "x.DesignTIme, Version= ..." long assembly names and GAC loads.
118+ // These kind of design-time assembly specifications are no longer used to our knowledge so that comparison is basically legacy
119+ // and will always succeed.
120+ let name = System.Reflection.AssemblyName( Path.GetFileNameWithoutExtension designTimeAssemblyNameString)
70121if name.Name.Equals( name.FullName, StringComparison.OrdinalIgnoreCase) then
71- let fileName = designTimeAssemblyNameString+ " .dll"
72- loadFromDir fileName
122+ let designTimeAssemblyName = designTimeAssemblyNameString+ " .dll"
123+ loadFromParentDirRelativeToRuntimeAssemblyLocation designTimeAssemblyName
73124else
74- loadFromGac()
125+ // Load from the GAC using Assembly.Load. This is legacy since type provider design-time components are
126+ // never in the GAC these days and "x.DesignTIme, Version= ..." specifications are never used.
127+ try
128+ let asmName = System.Reflection.AssemblyName designTimeAssemblyNameString
129+ Some( FileSystem.AssemblyLoad( asmName))
130+ with e->
131+ raiseError e
75132
76133// If we've find a design-time assembly, look for the public types with TypeProviderAttribute
77134match designTimeAssemblyOptwith
@@ -152,12 +209,17 @@ module internal ExtensionTyping =
152209try
153210let designTimeAssemblyName =
154211try
155- Some( System.Reflection.AssemblyName designTimeAssemblyNameString)
212+ if designTimeAssemblyNameString.EndsWith( " .dll" , StringComparison.OrdinalIgnoreCase) then
213+ Some( System.Reflection.AssemblyName( Path.GetFileNameWithoutExtension designTimeAssemblyNameString))
214+ else
215+ Some( System.Reflection.AssemblyName designTimeAssemblyNameString)
156216with :? ArgumentException->
157217 errorR( Error( FSComp.SR.etInvalidTypeProviderAssemblyName( runTimeAssemblyFileName, designTimeAssemblyNameString), m))
158218 None
159219
160220[ match designTimeAssemblyName, resolutionEnvironment.outputFilewith
221+ // Check if the attribute is pointing to the file being compiled, in which case ignore it
222+ // This checks seems like legacy but is included for compat.
161223| Some designTimeAssemblyName, Some pathwhen String.Compare( designTimeAssemblyName.Name, Path.GetFileNameWithoutExtension path, StringComparison.OrdinalIgnoreCase) = 0 ->
162224()
163225| Some_, _ ->