提交 fce4e9e2 编写于 作者: D Don Syme

Merge commit '0fb398da' into feature/ext

......@@ -16,6 +16,7 @@ artifacts/
src/Compiler/Checking/**/*.fs
src/Compiler/CodeGen/**/*.fs
src/Compiler/DependencyManager/**/*.fs
src/Compiler/Facilities/**/*.fs
src/Compiler/Interactive/**/*.fs
src/Compiler/Legacy/**/*.fs
src/Compiler/Optimize/**/*.fs
......
......@@ -61,6 +61,7 @@ param (
[string]$officialSkipTests = "false",
[switch]$noVisualStudio,
[switch]$sourceBuild,
[switch]$skipBuild,
[parameter(ValueFromRemainingArguments = $true)][string[]]$properties)
......@@ -114,6 +115,7 @@ function Print-Usage() {
Write-Host " -useGlobalNuGetCache Use global NuGet cache."
Write-Host " -noVisualStudio Only build fsc and fsi as .NET Core applications. No Visual Studio required. '-configuration', '-verbosity', '-norestore', '-rebuild' are supported."
Write-Host " -sourceBuild Simulate building for source-build."
Write-Host " -skipbuild Skip building product"
Write-Host ""
Write-Host "Command line arguments starting with '/p:' are passed through to MSBuild."
}
......@@ -458,7 +460,7 @@ try {
}
$script:BuildMessage = "Failure building product"
if ($restore -or $build -or $rebuild -or $pack -or $sign -or $publish) {
if ($restore -or $build -or $rebuild -or $pack -or $sign -or $publish -and -not $skipBuild) {
if ($noVisualStudio) {
BuildSolution "FSharp.sln"
}
......
......@@ -12,78 +12,96 @@ open Internal.Utilities.FSharpEnvironment
type AssemblyResolutionProbe = delegate of Unit -> seq<string>
/// Type that encapsulates AssemblyResolveHandler for managed packages
type AssemblyResolveHandlerCoreclr (assemblyProbingPaths: AssemblyResolutionProbe option) as this =
let assemblyLoadContextType: Type = Type.GetType("System.Runtime.Loader.AssemblyLoadContext, System.Runtime.Loader", false)
type AssemblyResolveHandlerCoreclr(assemblyProbingPaths: AssemblyResolutionProbe option) as this =
let loadContextType =
Type.GetType("System.Runtime.Loader.AssemblyLoadContext, System.Runtime.Loader", false)
let loadFromAssemblyPathMethod =
assemblyLoadContextType.GetMethod("LoadFromAssemblyPath", [| typeof<string> |])
loadContextType.GetMethod("LoadFromAssemblyPath", [| typeof<string> |])
let eventInfo, handler, defaultAssemblyLoadContext =
let eventInfo = assemblyLoadContextType.GetEvent("Resolving")
let mi =
let gmi = this.GetType().GetMethod("ResolveAssemblyNetStandard", BindingFlags.Instance ||| BindingFlags.NonPublic)
gmi.MakeGenericMethod(assemblyLoadContextType)
let eventInfo = loadContextType.GetEvent("Resolving")
eventInfo,
Delegate.CreateDelegate(eventInfo.EventHandlerType, this, mi),
assemblyLoadContextType.GetProperty("Default", BindingFlags.Static ||| BindingFlags.Public).GetValue(null, null)
let handler, defaultAssemblyLoadContext =
let ti = typeof<AssemblyResolveHandlerCoreclr>
let gmi =
ti.GetMethod("ResolveAssemblyNetStandard", BindingFlags.Instance ||| BindingFlags.NonPublic)
let mi = gmi.MakeGenericMethod(loadContextType)
let del = Delegate.CreateDelegate(eventInfo.EventHandlerType, this, mi)
let prop =
loadContextType
.GetProperty("Default", BindingFlags.Static ||| BindingFlags.Public)
.GetValue(null, null)
del, prop
do eventInfo.AddEventHandler(defaultAssemblyLoadContext, handler)
member _.ResolveAssemblyNetStandard (ctxt: 'T) (assemblyName: AssemblyName): Assembly =
member _.ResolveAssemblyNetStandard (ctxt: 'T) (assemblyName: AssemblyName) : Assembly =
let loadAssembly path =
loadFromAssemblyPathMethod.Invoke(ctxt, [| path |]) :?> Assembly
let assemblyPaths =
match assemblyProbingPaths with
| None -> Seq.empty<string>
| Some assemblyProbingPaths -> assemblyProbingPaths.Invoke()
| Some assemblyProbingPaths -> assemblyProbingPaths.Invoke()
try
// args.Name is a displayname formatted assembly version.
// E.g: "System.IO.FileSystem, Version=4.1.1.0, Culture=en-US, PublicKeyToken=b03f5f7f11d50a3a"
let simpleName = assemblyName.Name
let assemblyPathOpt = assemblyPaths |> Seq.tryFind(fun path -> Path.GetFileNameWithoutExtension(path) = simpleName)
let assemblyPathOpt =
assemblyPaths
|> Seq.tryFind (fun path -> Path.GetFileNameWithoutExtension(path) = simpleName)
match assemblyPathOpt with
| Some path -> loadAssembly path
| None -> Unchecked.defaultof<Assembly>
with | _ -> Unchecked.defaultof<Assembly>
with _ ->
Unchecked.defaultof<Assembly>
interface IDisposable with
member _x.Dispose() =
eventInfo.RemoveEventHandler(defaultAssemblyLoadContext, handler)
/// Type that encapsulates AssemblyResolveHandler for managed packages
type AssemblyResolveHandlerDeskTop (assemblyProbingPaths: AssemblyResolutionProbe option) =
type AssemblyResolveHandlerDeskTop(assemblyProbingPaths: AssemblyResolutionProbe option) =
let resolveAssemblyNET (assemblyName: AssemblyName): Assembly =
let loadAssembly assemblyPath =
Assembly.LoadFrom(assemblyPath)
let resolveAssemblyNET (assemblyName: AssemblyName) : Assembly =
let assemblyPaths =
match assemblyProbingPaths with
| None -> Seq.empty<string>
| Some assemblyProbingPaths -> assemblyProbingPaths.Invoke()
| Some assemblyProbingPaths -> assemblyProbingPaths.Invoke()
try
// args.Name is a displayname formatted assembly version.
// E.g: "System.IO.FileSystem, Version=4.1.1.0, Culture=en-US, PublicKeyToken=b03f5f7f11d50a3a"
let simpleName = assemblyName.Name
let assemblyPathOpt = assemblyPaths |> Seq.tryFind(fun path -> Path.GetFileNameWithoutExtension(path) = simpleName)
let assemblyPathOpt =
assemblyPaths
|> Seq.tryFind (fun path -> Path.GetFileNameWithoutExtension(path) = simpleName)
match assemblyPathOpt with
| Some path -> loadAssembly path
| Some path -> Assembly.LoadFrom path
| None -> Unchecked.defaultof<Assembly>
with | _ -> Unchecked.defaultof<Assembly>
with _ ->
Unchecked.defaultof<Assembly>
let handler =
ResolveEventHandler(fun _ (args: ResolveEventArgs) -> resolveAssemblyNET (AssemblyName(args.Name)))
let handler = ResolveEventHandler(fun _ (args: ResolveEventArgs) -> resolveAssemblyNET (AssemblyName(args.Name)))
do AppDomain.CurrentDomain.add_AssemblyResolve(handler)
do AppDomain.CurrentDomain.add_AssemblyResolve (handler)
interface IDisposable with
member _x.Dispose() =
AppDomain.CurrentDomain.remove_AssemblyResolve(handler)
AppDomain.CurrentDomain.remove_AssemblyResolve (handler)
type AssemblyResolveHandler internal (assemblyProbingPaths: AssemblyResolutionProbe option) =
......@@ -93,7 +111,7 @@ type AssemblyResolveHandler internal (assemblyProbingPaths: AssemblyResolutionPr
else
new AssemblyResolveHandlerDeskTop(assemblyProbingPaths) :> IDisposable
new (assemblyProbingPaths: AssemblyResolutionProbe) = new AssemblyResolveHandler(Option.ofObj assemblyProbingPaths)
new(assemblyProbingPaths: AssemblyResolutionProbe) = new AssemblyResolveHandler(Option.ofObj assemblyProbingPaths)
interface IDisposable with
member _.Dispose() = handler.Dispose()
......@@ -16,14 +16,13 @@ module Option =
/// Convert string into Option string where null and String.Empty result in None
let ofString s =
if String.IsNullOrEmpty(s) then None
else Some(s)
if String.IsNullOrEmpty(s) then None else Some(s)
[<AutoOpen>]
module ReflectionHelper =
let dependencyManagerPattern = "*DependencyManager*.dll"
let dependencyManagerAttributeName= "DependencyManagerAttribute"
let dependencyManagerAttributeName = "DependencyManagerAttribute"
let resolveDependenciesMethodName = "ResolveDependencies"
......@@ -33,7 +32,7 @@ module ReflectionHelper =
let helpMessagesPropertyName = "HelpMessages"
let arrEmpty = Array.empty<string>
let arrEmpty = [||]
let seqEmpty = Seq.empty<string>
......@@ -41,40 +40,46 @@ module ReflectionHelper =
try
CustomAttributeExtensions.GetCustomAttributes(theAssembly)
|> Seq.exists (fun a -> a.GetType().Name = attributeName)
with | _ -> false
with _ ->
false
let getAttributeNamed (theType: Type) attributeName =
try
theType.GetTypeInfo().GetCustomAttributes false
|> Seq.tryFind (fun a -> a.GetType().Name = attributeName)
with | _ -> None
with _ ->
None
let getInstanceProperty<'treturn> (theType: Type) propertyName =
let getInstanceProperty<'T> (theType: Type) propertyName =
try
let property = theType.GetProperty(propertyName, BindingFlags.Public ||| BindingFlags.NonPublic ||| BindingFlags.Instance, Unchecked.defaultof<Binder>, typeof<'treturn>, Array.empty, Array.empty)
let instanceFlags =
BindingFlags.Public ||| BindingFlags.NonPublic ||| BindingFlags.Instance
let property =
theType.GetProperty(propertyName, instanceFlags, null, typeof<'T>, [||], [||])
if isNull property then
None
else
let getMethod = property.GetGetMethod()
if not (isNull getMethod) && not getMethod.IsStatic then
Some property
else
None
with | _ -> None
with _ ->
None
let getInstanceMethod<'treturn> (theType: Type) (parameterTypes: Type array) methodName =
let getInstanceMethod<'T> (theType: Type) (parameterTypes: Type[]) methodName =
try
let theMethod = theType.GetMethod(methodName, parameterTypes)
if isNull theMethod then
None
else
Some theMethod
with | _ -> None
if isNull theMethod then None else Some theMethod
with _ ->
None
let stripTieWrapper (e:Exception) =
let stripTieWrapper (e: Exception) =
match e with
| :? TargetInvocationException as e->
e.InnerException
| :? TargetInvocationException as e -> e.InnerException
| _ -> e
/// Indicate the type of error to report
......@@ -117,15 +122,26 @@ type IResolveDependenciesResult =
/// #I @"c:\somepath\to\packages\1.1.1\ResolvedPackage"
abstract Roots: seq<string>
[<AllowNullLiteral>]
type IDependencyManagerProvider =
abstract Name: string
abstract Key: string
abstract HelpMessages: string[]
abstract ResolveDependencies: scriptDir: string * mainScriptName: string * scriptName: string * scriptExt: string * packageManagerTextLines: (string * string) seq * tfm: string * rid: string * timeout: int-> IResolveDependenciesResult
type ReflectionDependencyManagerProvider(theType: Type,
abstract ResolveDependencies:
scriptDir: string *
mainScriptName: string *
scriptName: string *
scriptExt: string *
packageManagerTextLines: (string * string) seq *
tfm: string *
rid: string *
timeout: int ->
IResolveDependenciesResult
type ReflectionDependencyManagerProvider
(
theType: Type,
nameProperty: PropertyInfo,
keyProperty: PropertyInfo,
helpMessagesProperty: PropertyInfo option,
......@@ -133,40 +149,160 @@ type ReflectionDependencyManagerProvider(theType: Type,
resolveDepsEx: MethodInfo option,
resolveDepsExWithTimeout: MethodInfo option,
resolveDepsExWithScriptInfoAndTimeout: MethodInfo option,
outputDir: string option) =
outputDir: string option
) =
let instance = Activator.CreateInstance(theType, [| outputDir :> obj |])
let nameProperty = nameProperty.GetValue >> string
let keyProperty = keyProperty.GetValue >> string
let helpMessagesProperty =
let toStringArray(o:obj) = o :?> string[]
let toStringArray (o: obj) = o :?> string[]
match helpMessagesProperty with
| Some helpMessagesProperty -> helpMessagesProperty.GetValue >> toStringArray
| None -> fun _ -> Array.empty<string>
| None -> fun _ -> [||]
static member InstanceMaker (theType: Type, outputDir: string option) =
static member InstanceMaker(theType: Type, outputDir: string option) =
match getAttributeNamed theType dependencyManagerAttributeName,
getInstanceProperty<string> theType namePropertyName,
getInstanceProperty<string> theType keyPropertyName,
getInstanceProperty<string[]> theType helpMessagesPropertyName
with
with
| None, _, _, _
| _, None, _, _
| _, _, None, _ -> None
| Some _, Some nameProperty, Some keyProperty, None ->
let resolveMethod = getInstanceMethod<bool * string list * string list> theType [| typeof<string>; typeof<string>; typeof<string>; typeof<string seq>; typeof<string> |] resolveDependenciesMethodName
let resolveMethodEx = getInstanceMethod<bool * string list * string list> theType [| typeof<string>; typeof<(string * string) seq>; typeof<string>; typeof<string> |] resolveDependenciesMethodName
let resolveMethodExWithTimeout = getInstanceMethod<bool * string list * string list> theType [| typeof<string>; typeof<(string * string) seq>; typeof<string>; typeof<string>; typeof<int> |] resolveDependenciesMethodName
let resolveDepsExWithScriptInfoAndTimeout = getInstanceMethod<bool * string list * string list> theType [| typeof<string>; typeof<string>; typeof<string>; typeof<(string * string) seq>; typeof<string>; typeof<string>; typeof<int> |] resolveDependenciesMethodName
Some (fun () -> ReflectionDependencyManagerProvider(theType, nameProperty, keyProperty, None, resolveMethod, resolveMethodEx, resolveMethodExWithTimeout, resolveDepsExWithScriptInfoAndTimeout,outputDir) :> IDependencyManagerProvider)
let resolveMethod =
getInstanceMethod<bool * string list * string list>
theType
[|
typeof<string>
typeof<string>
typeof<string>
typeof<seq<string>>
typeof<string>
|]
resolveDependenciesMethodName
let resolveMethodEx =
getInstanceMethod<bool * string list * string list>
theType
[|
typeof<string>
typeof<(string * string) seq>
typeof<string>
typeof<string>
|]
resolveDependenciesMethodName
let resolveMethodExWithTimeout =
getInstanceMethod<bool * string list * string list>
theType
[|
typeof<string>
typeof<(string * string) seq>
typeof<string>
typeof<string>
typeof<int>
|]
resolveDependenciesMethodName
let resolveDepsExWithScriptInfoAndTimeout =
getInstanceMethod<bool * string list * string list>
theType
[|
typeof<string>
typeof<string>
typeof<string>
typeof<(string * string) seq>
typeof<string>
typeof<string>
typeof<int>
|]
resolveDependenciesMethodName
Some(fun () ->
ReflectionDependencyManagerProvider(
theType,
nameProperty,
keyProperty,
None,
resolveMethod,
resolveMethodEx,
resolveMethodExWithTimeout,
resolveDepsExWithScriptInfoAndTimeout,
outputDir
)
:> IDependencyManagerProvider)
| Some _, Some nameProperty, Some keyProperty, Some helpMessagesProperty ->
let resolveMethod = getInstanceMethod<bool * string list * string list> theType [| typeof<string>; typeof<string>; typeof<string>; typeof<string seq>; typeof<string> |] resolveDependenciesMethodName
let resolveMethodEx = getInstanceMethod<bool * string list * string list> theType [| typeof<string>; typeof<(string * string) seq>; typeof<string>; typeof<string> |] resolveDependenciesMethodName
let resolveMethodExWithTimeout = getInstanceMethod<bool * string list * string list> theType [| typeof<string>; typeof<(string * string) seq>; typeof<string>; typeof<string>; typeof<int>; |] resolveDependenciesMethodName
let resolveDepsExWithScriptInfoAndTimeout = getInstanceMethod<bool * string list * string list> theType [| typeof<string>; typeof<string>; typeof<string>; typeof<(string * string) seq>; typeof<string>; typeof<string>; typeof<int> |] resolveDependenciesMethodName
Some (fun () -> ReflectionDependencyManagerProvider(theType, nameProperty, keyProperty, Some helpMessagesProperty, resolveMethod, resolveMethodEx, resolveMethodExWithTimeout, resolveDepsExWithScriptInfoAndTimeout, outputDir) :> IDependencyManagerProvider)
static member MakeResultFromObject(result: obj) = {
new IResolveDependenciesResult with
let resolveMethod =
getInstanceMethod<bool * string list * string list>
theType
[|
typeof<string>
typeof<string>
typeof<string>
typeof<seq<string>>
typeof<string>
|]
resolveDependenciesMethodName
let resolveMethodEx =
getInstanceMethod<bool * string list * string list>
theType
[|
typeof<string>
typeof<(string * string) seq>
typeof<string>
typeof<string>
|]
resolveDependenciesMethodName
let resolveMethodExWithTimeout =
getInstanceMethod<bool * string list * string list>
theType
[|
typeof<string>
typeof<(string * string) seq>
typeof<string>
typeof<string>
typeof<int>
|]
resolveDependenciesMethodName
let resolveDepsExWithScriptInfoAndTimeout =
getInstanceMethod<bool * string list * string list>
theType
[|
typeof<string>
typeof<string>
typeof<string>
typeof<(string * string) seq>
typeof<string>
typeof<string>
typeof<int>
|]
resolveDependenciesMethodName
Some(fun () ->
ReflectionDependencyManagerProvider(
theType,
nameProperty,
keyProperty,
Some helpMessagesProperty,
resolveMethod,
resolveMethodEx,
resolveMethodExWithTimeout,
resolveDepsExWithScriptInfoAndTimeout,
outputDir
)
:> IDependencyManagerProvider)
static member MakeResultFromObject(result: obj) =
{ new IResolveDependenciesResult with
/// Succeded?
member _.Success =
match getInstanceProperty<bool> (result.GetType()) "Success" with
......@@ -175,37 +311,45 @@ type ReflectionDependencyManagerProvider(theType: Type,
/// The resolution output log
member _.StdOut =
match getInstanceProperty<string array> (result.GetType()) "StdOut" with
| None -> Array.empty<string>
| Some p -> p.GetValue(result) :?> string array
match getInstanceProperty<string[]> (result.GetType()) "StdOut" with
| None -> [||]
| Some p -> p.GetValue(result) :?> string[]
/// The resolution error log (* process stderror *)
member _.StdError =
match getInstanceProperty<string array> (result.GetType()) "StdError" with
| None -> Array.empty<string>
| Some p -> p.GetValue(result) :?> string array
match getInstanceProperty<string[]> (result.GetType()) "StdError" with
| None -> [||]
| Some p -> p.GetValue(result) :?> string[]
/// The resolution paths
member _.Resolutions =
match getInstanceProperty<string seq> (result.GetType()) "Resolutions" with
match getInstanceProperty<seq<string>> (result.GetType()) "Resolutions" with
| None -> Seq.empty<string>
| Some p -> p.GetValue(result) :?> string seq
| Some p -> p.GetValue(result) :?> seq<string>
/// The source code file paths
member _.SourceFiles =
match getInstanceProperty<string seq> (result.GetType()) "SourceFiles" with
match getInstanceProperty<seq<string>> (result.GetType()) "SourceFiles" with
| None -> Seq.empty<string>
| Some p -> p.GetValue(result) :?> string seq
| Some p -> p.GetValue(result) :?> seq<string>
/// The roots to package directories
member _.Roots =
match getInstanceProperty<string seq> (result.GetType()) "Roots" with
match getInstanceProperty<seq<string>> (result.GetType()) "Roots" with
| None -> Seq.empty<string>
| Some p -> p.GetValue(result) :?> string seq
| Some p -> p.GetValue(result) :?> seq<string>
}
static member MakeResultFromFields(success: bool, stdOut: string array, stdError: string array, resolutions: string seq, sourceFiles: string seq, roots: string seq) = {
new IResolveDependenciesResult with
static member MakeResultFromFields
(
success: bool,
stdOut: string[],
stdError: string[],
resolutions: seq<string>,
sourceFiles: seq<string>,
roots: seq<string>
) =
{ new IResolveDependenciesResult with
/// Succeded?
member _.Success = success
......@@ -225,7 +369,6 @@ type ReflectionDependencyManagerProvider(theType: Type,
member _.Roots = roots
}
interface IDependencyManagerProvider with
/// Name of dependency Manager
......@@ -238,7 +381,17 @@ type ReflectionDependencyManagerProvider(theType: Type,
member _.HelpMessages = instance |> helpMessagesProperty
/// Resolve the dependencies for the given arguments
member _.ResolveDependencies(scriptDir, mainScriptName, scriptName, scriptExt, packageManagerTextLines, tfm, rid, timeout): IResolveDependenciesResult =
member _.ResolveDependencies
(
scriptDir,
mainScriptName,
scriptName,
scriptExt,
packageManagerTextLines,
tfm,
rid,
timeout
) : IResolveDependenciesResult =
// The ResolveDependencies method, has two signatures, the original signaature in the variable resolveDeps and the updated signature resolveDepsEx
// the resolve method can return values in two different tuples:
// (bool * string list * string list * string list)
......@@ -246,19 +399,29 @@ type ReflectionDependencyManagerProvider(theType: Type,
// We use reflection to get the correct method and to determine what we got back.
let method, arguments =
if resolveDepsExWithScriptInfoAndTimeout.IsSome then
resolveDepsExWithScriptInfoAndTimeout, [| box scriptDir; box scriptName; box scriptExt; box packageManagerTextLines; box tfm; box rid; box timeout |]
resolveDepsExWithScriptInfoAndTimeout,
[|
box scriptDir
box scriptName
box scriptExt
box packageManagerTextLines
box tfm
box rid
box timeout
|]
elif resolveDepsExWithTimeout.IsSome then
resolveDepsExWithTimeout, [| box scriptExt; box packageManagerTextLines; box tfm; box rid; box timeout |]
elif resolveDepsEx.IsSome then
resolveDepsEx, [| box scriptExt; box packageManagerTextLines; box tfm; box rid |]
elif resolveDeps.IsSome then
resolveDeps, [| box scriptDir
box mainScriptName
box scriptName
box (packageManagerTextLines
|> Seq.filter(fun (dv, _) -> dv = "r")
|> Seq.map snd)
box tfm |]
resolveDeps,
[|
box scriptDir
box mainScriptName
box scriptName
box (packageManagerTextLines |> Seq.filter (fun (dv, _) -> dv = "r") |> Seq.map snd)
box tfm
|]
else
None, [||]
......@@ -269,22 +432,25 @@ type ReflectionDependencyManagerProvider(theType: Type,
// Verify the number of arguments returned in the tuple returned by resolvedependencies, it can be:
// 1 - object with properties
// 3 - (bool * string list * string list)
// Support legacy api return shape (bool, string seq, string seq) --- original paket packagemanager
if FSharpType.IsTuple (result.GetType()) then
// Support legacy api return shape (bool, seq<string>, seq<string>) --- original paket packagemanager
if FSharpType.IsTuple(result.GetType()) then
// Verify the number of arguments returned in the tuple returned by resolvedependencies, it can be:
// 3 - (bool * string list * string list)
let success, sourceFiles, packageRoots =
let tupleFields = result |> FSharpValue.GetTupleFields
match tupleFields |> Array.length with
| 3 -> tupleFields[0] :?> bool, tupleFields[1] :?> string list |> List.toSeq, tupleFields[2] :?> string list |> List.distinct |> List.toSeq
| 3 ->
tupleFields[0] :?> bool,
tupleFields[1] :?> string list |> List.toSeq,
tupleFields[2] :?> string list |> List.distinct |> List.toSeq
| _ -> false, seqEmpty, seqEmpty
ReflectionDependencyManagerProvider.MakeResultFromFields(success, Array.empty, Array.empty, Seq.empty, sourceFiles, packageRoots)
ReflectionDependencyManagerProvider.MakeResultFromFields(success, [||], [||], Seq.empty, sourceFiles, packageRoots)
else
ReflectionDependencyManagerProvider.MakeResultFromObject(result)
| None ->
ReflectionDependencyManagerProvider.MakeResultFromFields(false, Array.empty, Array.empty, Seq.empty, Seq.empty, Seq.empty)
| None -> ReflectionDependencyManagerProvider.MakeResultFromFields(false, [||], [||], Seq.empty, Seq.empty, Seq.empty)
/// Provides DependencyManagement functions.
/// Class is IDisposable
......@@ -294,41 +460,47 @@ type DependencyProvider internal (assemblyProbingPaths: AssemblyResolutionProbe
let dllResolveHandler = new NativeDllResolveHandler(nativeProbingRoots)
// Note: creating a AssemblyResolveHandler currently installs process-wide handlers
let assemblyResolveHandler = new AssemblyResolveHandler(assemblyProbingPaths) :> IDisposable
let assemblyResolveHandler =
new AssemblyResolveHandler(assemblyProbingPaths) :> IDisposable
// Resolution Path = Location of FSharp.Compiler.Service.dll
let assemblySearchPaths = lazy (
[
let assemblyLocation = typeof<IDependencyManagerProvider>.GetTypeInfo().Assembly.Location
yield Path.GetDirectoryName assemblyLocation
yield AppDomain.CurrentDomain.BaseDirectory
])
let assemblySearchPaths =
lazy
([
let assemblyLocation =
typeof<IDependencyManagerProvider>.GetTypeInfo().Assembly.Location
yield Path.GetDirectoryName assemblyLocation
yield AppDomain.CurrentDomain.BaseDirectory
])
let enumerateDependencyManagerAssemblies compilerTools (reportError: ResolvingErrorReport) =
getCompilerToolsDesignTimeAssemblyPaths compilerTools
|> Seq.append (assemblySearchPaths.Force())
|> Seq.collect (fun path ->
try
if Directory.Exists(path) then Directory.EnumerateFiles(path, dependencyManagerPattern)
else Seq.empty
with _ -> Seq.empty)
|> Seq.choose (fun path ->
if Directory.Exists(path) then
Directory.EnumerateFiles(path, dependencyManagerPattern)
else
Seq.empty
with _ ->
Seq.empty)
|> Seq.choose (fun path ->
try
Some(Assembly.LoadFrom path)
with
| e ->
with e ->
let e = stripTieWrapper e
let n, m = FSComp.SR.couldNotLoadDependencyManagerExtension(path,e.Message)
let n, m = FSComp.SR.couldNotLoadDependencyManagerExtension (path, e.Message)
reportError.Invoke(ErrorReportType.Warning, n, m)
None)
|> Seq.filter (fun a -> assemblyHasAttribute a dependencyManagerAttributeName)
let mutable registeredDependencyManagers: Map<string, IDependencyManagerProvider> option= None
let mutable registeredDependencyManagers: Map<string, IDependencyManagerProvider> option =
None
let RegisteredDependencyManagers (compilerTools: string seq) (outputDir: string option) (reportError: ResolvingErrorReport) =
let RegisteredDependencyManagers (compilerTools: seq<string>) (outputDir: string option) (reportError: ResolvingErrorReport) =
match registeredDependencyManagers with
| Some managers ->
managers
| Some managers -> managers
| None ->
let managers =
let defaultProviders = []
......@@ -344,98 +516,150 @@ type DependencyProvider internal (assemblyProbingPaths: AssemblyResolutionProbe
|> Seq.map (fun pm -> pm.Key, pm)
|> Map.ofSeq
registeredDependencyManagers <-
if managers.Count > 0 then
Some managers
else
None
registeredDependencyManagers <- if managers.Count > 0 then Some managers else None
managers
let cache = ConcurrentDictionary<_,Result<IResolveDependenciesResult, _>>(HashIdentity.Structural)
let cache =
ConcurrentDictionary<_, Result<IResolveDependenciesResult, _>>(HashIdentity.Structural)
new (assemblyProbingPaths: AssemblyResolutionProbe, nativeProbingRoots: NativeResolutionProbe) = new DependencyProvider(Some assemblyProbingPaths, Some nativeProbingRoots)
new(assemblyProbingPaths: AssemblyResolutionProbe, nativeProbingRoots: NativeResolutionProbe) =
new DependencyProvider(Some assemblyProbingPaths, Some nativeProbingRoots)
new (nativeProbingRoots: NativeResolutionProbe) = new DependencyProvider(None, Some nativeProbingRoots)
new(nativeProbingRoots: NativeResolutionProbe) = new DependencyProvider(None, Some nativeProbingRoots)
new () = new DependencyProvider(None, None)
new() = new DependencyProvider(None, None)
/// Returns a formatted help messages for registered dependencymanagers for the host to present
member _.GetRegisteredDependencyManagerHelpText (compilerTools, outputDir, errorReport) = [|
let managers = RegisteredDependencyManagers compilerTools (Option.ofString outputDir) errorReport
member _.GetRegisteredDependencyManagerHelpText(compilerTools, outputDir, errorReport) =
[|
let managers =
RegisteredDependencyManagers compilerTools (Option.ofString outputDir) errorReport
for kvp in managers do
let dm = kvp.Value
yield! dm.HelpMessages
|]
/// Returns a formatted error message for the host to present
member _.CreatePackageManagerUnknownError (compilerTools: string seq, outputDir: string, packageManagerKey: string, reportError: ResolvingErrorReport) =
let registeredKeys = String.Join(", ", RegisteredDependencyManagers compilerTools (Option.ofString outputDir) reportError |> Seq.map (fun kv -> kv.Value.Key))
member _.CreatePackageManagerUnknownError
(
compilerTools: seq<string>,
outputDir: string,
packageManagerKey: string,
reportError: ResolvingErrorReport
) =
let registeredKeys =
String.Join(
", ",
RegisteredDependencyManagers compilerTools (Option.ofString outputDir) reportError
|> Seq.map (fun kv -> kv.Value.Key)
)
let searchPaths = assemblySearchPaths.Force()
FSComp.SR.packageManagerUnknown(packageManagerKey, String.Join(", ", searchPaths, compilerTools), registeredKeys)
FSComp.SR.packageManagerUnknown (packageManagerKey, String.Join(", ", searchPaths, compilerTools), registeredKeys)
/// Fetch a dependencymanager that supports a specific key
member this.TryFindDependencyManagerInPath (compilerTools: string seq, outputDir: string, reportError: ResolvingErrorReport, path: string): string MaybeNull * IDependencyManagerProvider MaybeNull =
member this.TryFindDependencyManagerInPath
(
compilerTools: seq<string>,
outputDir: string,
reportError: ResolvingErrorReport,
path: string
) : string MaybeNull * IDependencyManagerProvider MaybeNull =
try
if path.Contains ":" && not (Path.IsPathRooted path) then
let managers = RegisteredDependencyManagers compilerTools (Option.ofString outputDir) reportError
let managers =
RegisteredDependencyManagers compilerTools (Option.ofString outputDir) reportError
match managers |> Seq.tryFind (fun kv -> path.StartsWith(kv.Value.Key + ":" )) with
match managers |> Seq.tryFind (fun kv -> path.StartsWith(kv.Value.Key + ":")) with
| None ->
let err, msg = this.CreatePackageManagerUnknownError(compilerTools, outputDir, path.Split(':').[0], reportError)
let err, msg =
this.CreatePackageManagerUnknownError(compilerTools, outputDir, path.Split(':').[0], reportError)
reportError.Invoke(ErrorReportType.Error, err, msg)
null, null
| Some kv ->
path, kv.Value
| Some kv -> path, kv.Value
else
path, null
with
| e ->
with e ->
let e = stripTieWrapper e
let err, msg = FSComp.SR.packageManagerError(e.Message)
let err, msg = FSComp.SR.packageManagerError (e.Message)
reportError.Invoke(ErrorReportType.Error, err, msg)
null, null
/// Fetch a dependencymanager that supports a specific key
member _.TryFindDependencyManagerByKey (compilerTools: string seq, outputDir: string, reportError: ResolvingErrorReport, key: string): IDependencyManagerProvider MaybeNull =
member _.TryFindDependencyManagerByKey
(
compilerTools: seq<string>,
outputDir: string,
reportError: ResolvingErrorReport,
key: string
) : IDependencyManagerProvider MaybeNull =
try
RegisteredDependencyManagers compilerTools (Option.ofString outputDir) reportError
|> Map.tryFind key
|> Option.toObj
with
| e ->
with e ->
let e = stripTieWrapper e
let err, msg = FSComp.SR.packageManagerError(e.Message)
let err, msg = FSComp.SR.packageManagerError (e.Message)
reportError.Invoke(ErrorReportType.Error, err, msg)
null
/// Resolve reference for a list of package manager lines
member _.Resolve (packageManager:IDependencyManagerProvider,
scriptExt: string,
packageManagerTextLines: (string * string) seq,
reportError: ResolvingErrorReport,
executionTfm: string,
[<Optional;DefaultParameterValue(null:string MaybeNull)>]executionRid: string MaybeNull,
[<Optional;DefaultParameterValue("")>]implicitIncludeDir: string,
[<Optional;DefaultParameterValue("")>]mainScriptName: string,
[<Optional;DefaultParameterValue("")>]fileName: string,
[<Optional;DefaultParameterValue(-1)>]timeout: int): IResolveDependenciesResult =
let key = (packageManager.Key, scriptExt, Seq.toArray packageManagerTextLines, executionTfm, executionRid, implicitIncludeDir, mainScriptName, fileName)
let result =
cache.GetOrAdd(key, System.Func<_,_>(fun _ ->
try
let executionRid =
match executionRid with
| Null -> RidHelpers.platformRid
| NonNull executionRid -> executionRid
Ok (packageManager.ResolveDependencies(implicitIncludeDir, mainScriptName, fileName, scriptExt, packageManagerTextLines, executionTfm, executionRid, timeout))
with e ->
let e = stripTieWrapper e
Error (FSComp.SR.packageManagerError(e.Message))
))
member _.Resolve
(
packageManager: IDependencyManagerProvider,
scriptExt: string,
packageManagerTextLines: (string * string) seq,
reportError: ResolvingErrorReport,
executionTfm: string,
[<Optional; DefaultParameterValue(null: string MaybeNull)>] executionRid: string MaybeNull,
[<Optional; DefaultParameterValue("")>] implicitIncludeDir: string,
[<Optional; DefaultParameterValue("")>] mainScriptName: string,
[<Optional; DefaultParameterValue("")>] fileName: string,
[<Optional; DefaultParameterValue(-1)>] timeout: int
) : IResolveDependenciesResult =
let key =
(packageManager.Key,
scriptExt,
Seq.toArray packageManagerTextLines,
executionTfm,
executionRid,
implicitIncludeDir,
mainScriptName,
fileName)
let result =
cache.GetOrAdd(
key,
System.Func<_, _>(fun _ ->
try
let executionRid =
match executionRid with
| Null -> RidHelpers.platformRid
| NonNull executionRid -> executionRid
Ok(
packageManager.ResolveDependencies(
implicitIncludeDir,
mainScriptName,
fileName,
scriptExt,
packageManagerTextLines,
executionTfm,
executionRid,
timeout
)
)
with e ->
let e = stripTieWrapper e
Error(FSComp.SR.packageManagerError (e.Message)))
)
match result with
| Ok res ->
dllResolveHandler.RefreshPathsInEnvironment(res.Roots)
......
......@@ -16,14 +16,17 @@ open FSharp.Compiler.IO
type NativeResolutionProbe = delegate of Unit -> seq<string>
/// Type that encapsulates Native library probing for managed packages
type NativeDllResolveHandlerCoreClr (nativeProbingRoots: NativeResolutionProbe option) =
type NativeDllResolveHandlerCoreClr(nativeProbingRoots: NativeResolutionProbe option) =
let nativeLibraryTryLoad =
let nativeLibraryType: Type = Type.GetType("System.Runtime.InteropServices.NativeLibrary, System.Runtime.InteropServices", false)
nativeLibraryType.GetMethod("TryLoad", [| typeof<string>; typeof<IntPtr>.MakeByRefType() |])
let nativeLibraryType: Type =
Type.GetType("System.Runtime.InteropServices.NativeLibrary, System.Runtime.InteropServices", false)
nativeLibraryType.GetMethod("TryLoad", [| typeof<string>; typeof<IntPtr>.MakeByRefType () |])
let loadNativeLibrary path =
let arguments = [| path:>obj; IntPtr.Zero:>obj |]
let arguments = [| path :> obj; IntPtr.Zero :> obj |]
if nativeLibraryTryLoad.Invoke(null, arguments) :?> bool then
arguments[1] :?> IntPtr
else
......@@ -32,13 +35,20 @@ type NativeDllResolveHandlerCoreClr (nativeProbingRoots: NativeResolutionProbe o
let probingFileNames (name: string) =
// coreclr native library probing algorithm: https://github.com/dotnet/coreclr/blob/9773db1e7b1acb3ec75c9cc0e36bd62dcbacd6d5/src/System.Private.CoreLib/shared/System/Runtime/Loader/LibraryNameVariation.Unix.cs
let isRooted = Path.IsPathRooted name
let useSuffix s = not (name.Contains(s + ".") || name.EndsWith(s)) // linux devs often append version # to libraries I.e mydll.so.5.3.2
let usePrefix = name.IndexOf(Path.DirectorySeparatorChar) = -1 // If name has directory information no add no prefix
&& name.IndexOf(Path.AltDirectorySeparatorChar) = -1
&& name.IndexOf(Path.PathSeparator) = -1
&& name.IndexOf(Path.VolumeSeparatorChar) = -1
let useSuffix s =
not (name.Contains(s + ".") || name.EndsWith(s)) // linux devs often append version # to libraries I.e mydll.so.5.3.2
let usePrefix =
name.IndexOf(Path.DirectorySeparatorChar) = -1 // If name has directory information no add no prefix
&& name.IndexOf(Path.AltDirectorySeparatorChar) = -1
&& name.IndexOf(Path.PathSeparator) = -1
&& name.IndexOf(Path.VolumeSeparatorChar) = -1
let prefix = [| "lib" |]
let suffix = [|
let suffix =
[|
if RuntimeInformation.IsOSPlatform(OSPlatform.Windows) then
".dll"
".exe"
......@@ -49,72 +59,79 @@ type NativeDllResolveHandlerCoreClr (nativeProbingRoots: NativeResolutionProbe o
|]
[|
yield name // Bare name
yield name // Bare name
if not isRooted then
for s in suffix do
if useSuffix s then // Suffix without prefix
if useSuffix s then // Suffix without prefix
yield (sprintf "%s%s" name s)
if usePrefix then
for p in prefix do // Suffix with prefix
for p in prefix do // Suffix with prefix
yield (sprintf "%s%s%s" p name s)
elif usePrefix then
for p in prefix do // Prefix
for p in prefix do // Prefix
yield (sprintf "%s%s" p name)
|]
let resolveUnmanagedDll (_: Assembly) (name: string): IntPtr =
let resolveUnmanagedDll (_: Assembly) (name: string) : IntPtr =
// Enumerate probing roots looking for a dll that matches the probing name in the probed locations
let probeForNativeLibrary root rid name =
// Look for name in root
probingFileNames name |> Array.tryPick(fun name ->
probingFileNames name
|> Array.tryPick (fun name ->
let path = Path.Combine(root, "runtimes", rid, "native", name)
if FileSystem.FileExistsShim(path) then
Some path
else
None)
if FileSystem.FileExistsShim(path) then Some path else None)
let probe =
match nativeProbingRoots with
| None -> None
| Some nativeProbingRoots ->
| Some nativeProbingRoots ->
nativeProbingRoots.Invoke()
|> Seq.tryPick(fun root ->
probingFileNames name |> Seq.tryPick(fun name ->
|> Seq.tryPick (fun root ->
probingFileNames name
|> Seq.tryPick (fun name ->
let path = Path.Combine(root, name)
if FileSystem.FileExistsShim(path) then
Some path
else
RidHelpers.probingRids |> Seq.tryPick(fun rid -> probeForNativeLibrary root rid name)))
RidHelpers.probingRids
|> Seq.tryPick (fun rid -> probeForNativeLibrary root rid name)))
match probe with
| Some path -> loadNativeLibrary(path)
| Some path -> loadNativeLibrary (path)
| None -> IntPtr.Zero
// netstandard 2.1 has this property, unfortunately we don't build with that yet
//public event Func<Assembly, string, IntPtr> ResolvingUnmanagedDll
let assemblyLoadContextType: Type = Type.GetType("System.Runtime.Loader.AssemblyLoadContext, System.Runtime.Loader", false)
let assemblyLoadContextType: Type =
Type.GetType("System.Runtime.Loader.AssemblyLoadContext, System.Runtime.Loader", false)
let eventInfo, handler, defaultAssemblyLoadContext =
assemblyLoadContextType.GetEvent("ResolvingUnmanagedDll"),
Func<Assembly, string, IntPtr> resolveUnmanagedDll,
assemblyLoadContextType.GetProperty("Default", BindingFlags.Static ||| BindingFlags.Public).GetValue(null, null)
assemblyLoadContextType
.GetProperty("Default", BindingFlags.Static ||| BindingFlags.Public)
.GetValue(null, null)
do eventInfo.AddEventHandler(defaultAssemblyLoadContext, handler)
interface IDisposable with
member _x.Dispose() = eventInfo.RemoveEventHandler(defaultAssemblyLoadContext, handler)
member _x.Dispose() =
eventInfo.RemoveEventHandler(defaultAssemblyLoadContext, handler)
type NativeDllResolveHandler (nativeProbingRoots: NativeResolutionProbe option) =
type NativeDllResolveHandler(nativeProbingRoots: NativeResolutionProbe option) =
let handler: IDisposable option =
if isRunningOnCoreClr then
Some (new NativeDllResolveHandlerCoreClr(nativeProbingRoots) :> IDisposable)
Some(new NativeDllResolveHandlerCoreClr(nativeProbingRoots) :> IDisposable)
else
None
let appendPathSeparator (p: string) =
let separator = string Path.PathSeparator
if not(p.EndsWith(separator, StringComparison.OrdinalIgnoreCase)) then
if not (p.EndsWith(separator, StringComparison.OrdinalIgnoreCase)) then
p + separator
else
p
......@@ -124,17 +141,20 @@ type NativeDllResolveHandler (nativeProbingRoots: NativeResolutionProbe option)
let addProbeToProcessPath probePath =
let probe = appendPathSeparator probePath
let path = appendPathSeparator (Environment.GetEnvironmentVariable("PATH"))
if not (path.Contains(probe)) then
Environment.SetEnvironmentVariable("PATH", path + probe)
addedPaths.Add probe
let removeProbeFromProcessPath probePath =
if not(String.IsNullOrWhiteSpace(probePath)) then
if not (String.IsNullOrWhiteSpace(probePath)) then
let probe = appendPathSeparator probePath
let path = appendPathSeparator (Environment.GetEnvironmentVariable("PATH"))
if path.Contains(probe) then Environment.SetEnvironmentVariable("PATH", path.Replace(probe, ""))
new (nativeProbingRoots: NativeResolutionProbe) = new NativeDllResolveHandler(Option.ofObj nativeProbingRoots)
if path.Contains(probe) then
Environment.SetEnvironmentVariable("PATH", path.Replace(probe, ""))
new(nativeProbingRoots: NativeResolutionProbe) = new NativeDllResolveHandler(Option.ofObj nativeProbingRoots)
member internal _.RefreshPathsInEnvironment(roots: string seq) =
for probePath in roots do
......@@ -146,6 +166,7 @@ type NativeDllResolveHandler (nativeProbingRoots: NativeResolutionProbe option)
| None -> ()
| Some handler -> handler.Dispose()
let mutable probe:string = Unchecked.defaultof<string>
let mutable probe: string = Unchecked.defaultof<string>
while (addedPaths.TryTake(&probe)) do
removeProbeFromProcessPath probe
......@@ -754,7 +754,6 @@
<ProjectReference Include="$(MSBuildThisFileDirectory)..\FSharp.DependencyManager.Nuget\FSharp.DependencyManager.Nuget.fsproj" />
</ItemGroup>
<ItemGroup Condition="'$(FSHARPCORE_USE_PACKAGE)' != 'true'">
<ProjectReference Include="$(MSBuildThisFileDirectory)..\FSharp.Core\FSharp.Core.fsproj" />
</ItemGroup>
......
......@@ -15,6 +15,7 @@
<DisableImplicitFSharpCoreReference>true</DisableImplicitFSharpCoreReference>
<UseStandardResourceNames>false</UseStandardResourceNames>
<PackageOutputPath>$(ArtifactsPackagesDir)\$(Configuration)</PackageOutputPath>
<ShouldUnsetParentConfigurationAndPlatform>false</ShouldUnsetParentConfigurationAndPlatform>
</PropertyGroup>
</Project>
......@@ -4,8 +4,7 @@
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework Condition="'$(Configuration)' != 'Proto'">netstandard2.0</TargetFramework>
<TargetFrameworks Condition="'$(Configuration)' == 'Proto'">netstandard2.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<AssemblyName>FSharp.Build</AssemblyName>
<NoWarn>$(NoWarn);75</NoWarn> <!-- InternalCommandLineOption -->
<AllowCrossTargeting>true</AllowCrossTargeting>
......
......@@ -11,6 +11,13 @@
<NoDefaultExcludes>true</NoDefaultExcludes>
</PropertyGroup>
<PropertyGroup>
<ArcadeSdkDir Condition="'$(ArcadeSdkDir)' == ''">$(NuGetPackageRoot)microsoft.dotnet.arcade.sdk\$(ArcadeSdkVersion)\</ArcadeSdkDir>
<_BuildReleasePackagesTargets>$(ArcadeSdkDir)tools\BuildReleasePackages.targets</_BuildReleasePackagesTargets>
</PropertyGroup>
<Import Project="$(_BuildReleasePackagesTargets)" />
<ItemGroup>
<NuspecProperty Include="fSharpCorePreviewPackageVersion=$(FSCorePackageVersionValue)-$(VersionSuffix)" />
<NuspecProperty Include="fSharpCorePackageVersion=$(FSCorePackageVersionValue)" />
......@@ -19,6 +26,36 @@
<NuspecProperty Include="artifactsPackagesDir=$(ArtifactsPackagesDir)" />
</ItemGroup>
<Target Name="Build" />
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)..\fsi\fsiProject\fsi.fsproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)..\fsc\fscProject\fsc.fsproj" />
</ItemGroup>
<ItemGroup>
<DependentProjects Include="$(MSBuildThisFileDirectory)..\FSharp.Core\FSharp.Core.fsproj">
<AdditionalProperties>TargetFrameworks=netstandard2.1;netstandard2.0</AdditionalProperties>
</DependentProjects>
<DependentProjects Include="$(MSBuildThisFileDirectory)..\FSharp.Build\FSharp.Build.fsproj">
<AdditionalProperties>TargetFrameworks=netstandard2.0</AdditionalProperties>
</DependentProjects>
<DependentProjects Include="$(MSBuildThisFileDirectory)..\FSharp.Compiler.Interactive.Settings\FSharp.Compiler.Interactive.Settings.fsproj">
<AdditionalProperties>TargetFrameworks=netstandard2.0</AdditionalProperties>
</DependentProjects>
<DependentProjects Include="$(MSBuildThisFileDirectory)..\FSharp.DependencyManager.Nuget\FSharp.DependencyManager.Nuget.fsproj">
<AdditionalProperties>TargetFrameworks=netstandard2.0</AdditionalProperties>
</DependentProjects>
<DependentProjects Include="$(MSBuildThisFileDirectory)..\Compiler\FSharp.Compiler.Service.fsproj">
<AdditionalProperties>TargetFrameworks=netstandard2.0</AdditionalProperties>
</DependentProjects>
</ItemGroup>
<Target Name="PackDependentProjectsCore">
<MSBuild Projects="@(DependentProjects)" Targets="Pack" Properties="Restore=true;Pack=true;BUILD_PUBLICSIGN=$(BUILD_PUBLICSIGN)" />
</Target>
<Target Name="PackDependentProjects"
BeforeTargets="Build"
DependsOnTargets="PackDependentProjectsCore;PackageReleasePackages">
</Target>
</Project>
// See https://aka.ms/new-console-template for more information
// See https://aka.ms/new-console-template for more information
return 0;
\ No newline at end of file
......@@ -33,7 +33,7 @@
<NgenArchitecture>All</NgenArchitecture>
<NgenPriority>2</NgenPriority>
<Private>True</Private>
<AdditionalProperties>TargetFramework=$(DependencyTargetFramework)</AdditionalProperties>
<AdditionalProperties>TargetFramework=netstandard2.0</AdditionalProperties>
</ProjectReference>
<ProjectReference Include="$(FSharpSourcesRoot)\FSharp.Compiler.Interactive.Settings\FSharp.Compiler.Interactive.Settings.fsproj">
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册