提交 a490bbe5 编写于 作者: D Don Syme 提交者: GitHub

support .NET Standard 2.0 code generation (#3215)

上级 d1ba9ff8
......@@ -52,15 +52,18 @@ let lazyMap f (x:Lazy<_>) =
[<RequireQualifiedAccess>]
type PrimaryAssembly =
| Mscorlib
| DotNetCore
| System_Runtime
| NetStandard
member this.Name =
match this with
| Mscorlib -> "mscorlib"
| DotNetCore -> "System.Runtime"
| System_Runtime -> "System.Runtime"
| NetStandard -> "netstandard"
static member IsSomePrimaryAssembly n =
n = PrimaryAssembly.Mscorlib.Name
|| n = PrimaryAssembly.DotNetCore.Name
|| n = PrimaryAssembly.System_Runtime.Name
|| n = PrimaryAssembly.NetStandard.Name
// --------------------------------------------------------------------
// Utilities: type names
......
......@@ -14,7 +14,8 @@ open System.Collections.Generic
[<RequireQualifiedAccess>]
type PrimaryAssembly =
| Mscorlib
| DotNetCore
| System_Runtime
| NetStandard
member Name: string
......
......@@ -1708,6 +1708,7 @@ let DefaultReferencesForScriptsAndOutOfProjectSources(assumeDotNetFramework) =
let SystemAssemblies () =
HashSet
[ yield "mscorlib"
yield "netstandard"
yield "System.Runtime"
yield GetFSharpCoreLibraryName()
yield "System"
......@@ -2187,7 +2188,7 @@ type TcConfigBuilder =
failwith "Expected a valid defaultFSharpBinariesDir"
{
#if COMPILER_SERVICE_ASSUMES_DOTNETCORE_COMPILATION
primaryAssembly = PrimaryAssembly.DotNetCore // defaut value, can be overridden using the command line switch
primaryAssembly = PrimaryAssembly.System_Runtime // defaut value, can be overridden using the command line switch
#else
primaryAssembly = PrimaryAssembly.Mscorlib // defaut value, can be overridden using the command line switch
#endif
......@@ -2600,13 +2601,13 @@ type TcConfig private (data : TcConfigBuilder,validate:bool) =
AssemblyReference(range0, GetDefaultFSharpCoreReference(), None), None
| _ -> res
// If either mscorlib.dll/System.Runtime.dll or FSharp.Core.dll are explicitly specified then we require the --noframework flag.
// If either mscorlib.dll/System.Runtime.dll/netstandard.dll or FSharp.Core.dll are explicitly specified then we require the --noframework flag.
// The reason is that some non-default frameworks may not have the default dlls. For example, Client profile does
// not have System.Web.dll.
do if ((primaryAssemblyExplicitFilenameOpt.IsSome || fslibExplicitFilenameOpt.IsSome) && data.framework) then
error(Error(FSComp.SR.buildExplicitCoreLibRequiresNoFramework("--noframework"),rangeStartup))
let clrRootValue, (mscorlibMajorVersion,targetFrameworkVersionValue), primaryAssemblyIsSilverlight =
let clrRootValue, targetFrameworkVersionValue =
match primaryAssemblyExplicitFilenameOpt with
| Some(primaryAssemblyFilename) ->
let filename = ComputeMakePathAbsolute data.implicitIncludeDir primaryAssemblyFilename
......@@ -2614,12 +2615,9 @@ type TcConfig private (data : TcConfigBuilder,validate:bool) =
use ilReader = OpenILBinary(filename,data.optimizeForMemory,data.openBinariesInMemory,None,None, data.shadowCopyReferences)
let ilModule = ilReader.ILModuleDef
match ilModule.ManifestOfAssembly.Version with
| Some(v1,v2,v3,_) ->
if v1 = 1us then
warning(Error(FSComp.SR.buildRequiresCLI2(filename),rangeStartup))
| Some(v1,v2,_,_) ->
let clrRoot = Some(Path.GetDirectoryName(FileSystem.GetFullPathShim(filename)))
clrRoot, (int v1, sprintf "v%d.%d" v1 v2), (v1=5us && v2=0us && v3=5us) // SL5 mscorlib is 5.0.5.0
clrRoot, (sprintf "v%d.%d" v1 v2)
| _ ->
failwith (FSComp.SR.buildCouldNotReadVersionInfoFromMscorlib())
with e ->
......@@ -2628,48 +2626,15 @@ type TcConfig private (data : TcConfigBuilder,validate:bool) =
#if !ENABLE_MONO_SUPPORT
// TODO: we have to get msbuild out of this
if data.useSimpleResolution then
None, (0, ""), false
None, ""
else
#endif
None, (4, data.legacyReferenceResolver.HighestInstalledNetFrameworkVersion()), false
None, data.legacyReferenceResolver.HighestInstalledNetFrameworkVersion()
// Note: anycpu32bitpreferred can only be used with .Net version 4.5 and above
// but now there is no way to discriminate between 4.0 and 4.5,
// so here we minimally validate if .Net version >= 4 or not.
do if data.prefer32Bit && mscorlibMajorVersion < 4 then
error(Error(FSComp.SR.invalidPlatformTargetForOldFramework(),rangeCmdArgs))
let systemAssemblies = SystemAssemblies ()
// Check that the referenced version of FSharp.Core.dll matches the referenced version of mscorlib.dll
let checkFSharpBinaryCompatWithMscorlib filename (ilAssemblyRefs: ILAssemblyRef list) explicitFscoreVersionToCheckOpt m =
let isfslib = fileNameOfPath filename = GetFSharpCoreLibraryName() + ".dll"
match ilAssemblyRefs |> List.tryFind (fun aref -> aref.Name = data.primaryAssembly.Name) with
| Some aref ->
match aref.Version with
| Some(v1,_,_,_) ->
if isfslib && ((v1 < 4us) <> (mscorlibMajorVersion < 4)) then
// the versions mismatch, however they are allowed to mismatch in one case:
if primaryAssemblyIsSilverlight && mscorlibMajorVersion=5 // SL5
&& (match explicitFscoreVersionToCheckOpt with
| Some(2us,3us,5us,_) // silverlight is supported for FSharp.Core 2.3.5.x and 3.47.x.y
| Some(3us,47us,_,_)
| None -> true // the 'None' code path happens after explicit FSCore was already checked, from now on SL5 path is always excepted
| _ -> false)
then
()
else
error(Error(FSComp.SR.buildMscorLibAndFSharpCoreMismatch(filename),m))
// If you're building an assembly that references another assembly built for a more recent
// framework version, we want to raise a warning
elif not(isfslib) && ((v1 = 4us) && (mscorlibMajorVersion < 4)) then
warning(Error(FSComp.SR.buildMscorlibAndReferencedAssemblyMismatch(filename),m))
else
()
| _ -> ()
| _ -> ()
// Look for an explicit reference to FSharp.Core and use that to compute fsharpBinariesDir
// FUTURE: remove this, we only read the binary for the exception it raises
let fsharpBinariesDirValue =
#if FX_NO_SIMPLIFIED_LOADER
data.defaultFSharpBinariesDir
......@@ -2680,7 +2645,7 @@ type TcConfig private (data : TcConfigBuilder,validate:bool) =
if fslibReference.ProjectReference.IsNone then
try
use ilReader = OpenILBinary(filename,data.optimizeForMemory,data.openBinariesInMemory,None,None, data.shadowCopyReferences)
checkFSharpBinaryCompatWithMscorlib filename ilReader.ILAssemblyRefs ilReader.ILModuleDef.ManifestOfAssembly.Version rangeStartup;
()
with e ->
error(Error(FSComp.SR.buildErrorOpeningBinaryFile(filename, e.Message), rangeStartup))
......@@ -2690,7 +2655,6 @@ type TcConfig private (data : TcConfigBuilder,validate:bool) =
data.defaultFSharpBinariesDir
#endif
member x.MscorlibMajorVersion = mscorlibMajorVersion
member x.primaryAssembly = data.primaryAssembly
member x.autoResolveOpenDirectivesToDlls = data.autoResolveOpenDirectivesToDlls
member x.noFeedback = data.noFeedback
......@@ -3020,10 +2984,6 @@ type TcConfig private (data : TcConfigBuilder,validate:bool) =
member tcConfig.ResolveSourceFile(m, nm, pathLoadedFrom) =
data.ResolveSourceFile(m, nm, pathLoadedFrom)
member tcConfig.CheckFSharpBinary (filename, ilAssemblyRefs, m) =
use unwindBuildPhase = PushThreadBuildPhaseUntilUnwind BuildPhase.Parameter
checkFSharpBinaryCompatWithMscorlib filename ilAssemblyRefs None m
// NOTE!! if mode=Speculative then this method must not report ANY warnings or errors through 'warning' or 'error'. Instead
// it must return warnings and errors as data
//
......@@ -4165,7 +4125,7 @@ type TcImports(tcConfigP:TcConfigProvider, initialResolutions:TcAssemblyResoluti
if providerAssemblies.Count > 0 then
// Find the SystemRuntimeAssemblyVersion value to report in the TypeProviderConfig.
let systemRuntimeAssemblyVersion =
let primaryAssemblyVersion =
let primaryAssemblyRef = tcConfig.PrimaryAssemblyDllReference()
let resolution = tcConfig.ResolveLibWithDirectories (CcuLoadFailureAction.RaiseError, primaryAssemblyRef) |> Option.get
// MSDN: this method causes the file to be opened and closed, but the assembly is not added to this domain
......@@ -4192,7 +4152,7 @@ type TcImports(tcConfigP:TcConfigProvider, initialResolutions:TcAssemblyResoluti
let providers =
[ for assemblyName in providerAssemblies do
yield ExtensionTyping.GetTypeProvidersOfAssembly(fileNameOfRuntimeAssembly, ilScopeRefOfRuntimeAssembly, assemblyName, typeProviderEnvironment,
tcConfig.isInvalidationSupported, tcConfig.isInteractive, systemRuntimeContainsType, systemRuntimeAssemblyVersion, m) ]
tcConfig.isInvalidationSupported, tcConfig.isInteractive, systemRuntimeContainsType, primaryAssemblyVersion, m) ]
let providers = providers |> List.concat
// Note, type providers are disposable objects. The TcImports owns the provider objects - when/if it is disposed, the providers are disposed.
......@@ -4273,7 +4233,6 @@ type TcImports(tcConfigP:TcConfigProvider, initialResolutions:TcAssemblyResoluti
member tcImports.PrepareToImportReferencedILAssembly (ctok, m, filename, dllinfo:ImportedBinary) =
CheckDisposed()
let tcConfig = tcConfigP.Get(ctok)
tcConfig.CheckFSharpBinary (filename,dllinfo.ILAssemblyRefs,m)
assert dllinfo.RawMetadata.TryGetRawILModule().IsSome
let ilModule = dllinfo.RawMetadata.TryGetRawILModule().Value
let ilScopeRef = dllinfo.ILScopeRef
......@@ -4310,9 +4269,9 @@ type TcImports(tcConfigP:TcConfigProvider, initialResolutions:TcAssemblyResoluti
member tcImports.PrepareToImportReferencedFSharpAssembly (ctok, m, filename, dllinfo:ImportedBinary) =
CheckDisposed()
#if EXTENSIONTYPING
let tcConfig = tcConfigP.Get(ctok)
tcConfig.CheckFSharpBinary (filename, dllinfo.ILAssemblyRefs, m)
#endif
let ilModule = dllinfo.RawMetadata
let ilScopeRef = dllinfo.ILScopeRef
let ilShortAssemName = getNameOfScopeRef ilScopeRef
......@@ -4636,7 +4595,7 @@ type TcImports(tcConfigP:TcConfigProvider, initialResolutions:TcAssemblyResoluti
// OK, now we have both mscorlib.dll and FSharp.Core.dll we can create TcGlobals
let tcGlobals = TcGlobals(tcConfig.compilingFslib,ilGlobals,fslibCcu,
tcConfig.implicitIncludeDir,tcConfig.mlCompatibility,
tcConfig.isInteractive,tryFindSysTypeCcu, tcConfig.emitDebugInfoInQuotations, (tcConfig.primaryAssembly.Name = "mscorlib"), tcConfig.noDebugData )
tcConfig.isInteractive,tryFindSysTypeCcu, tcConfig.emitDebugInfoInQuotations, tcConfig.noDebugData )
#if DEBUG
// the global_g reference cell is used only for debug printing
......
......@@ -746,8 +746,12 @@ let cliRootFlag (_tcConfigB : TcConfigBuilder) =
let SetTargetProfile tcConfigB v =
tcConfigB.primaryAssembly <-
match v with
// Indicates we assume "mscorlib.dll", i.e .NET Framework, Mono and Profile 47
| "mscorlib" -> PrimaryAssembly.Mscorlib
| "netcore" -> PrimaryAssembly.DotNetCore
// Indicates we assume "System.Runtime.dll", i.e .NET Standard 1.x, .NET Core App 1.x and above, and Profile 7/78/259
| "netcore" -> PrimaryAssembly.System_Runtime
// Indicates we assume "netstandard.dll", i.e .NET Standard 2.0 and above
| "netstandard" -> PrimaryAssembly.NetStandard
| _ -> error(Error(FSComp.SR.optsInvalidTargetProfile(v), rangeCmdArgs))
let advancedFlagsBoth tcConfigB =
......
......@@ -44,11 +44,8 @@ buildProductNameCommunity,"F# Compiler for F# %s"
213,buildInvalidAssemblyName,"'%s' is not a valid assembly name"
214,buildInvalidPrivacy,"Unrecognized privacy setting '%s' for managed resource, valid options are 'public' and 'private'"
215,buildMultipleReferencesNotAllowed,"Multiple references to '%s.dll' are not permitted"
216,buildRequiresCLI2,"The file '%s' is a CLI 1.x version of mscorlib. F# requires CLI version 2.0 or greater."
buildCouldNotReadVersionInfoFromMscorlib,"Could not read version from mscorlib.dll"
217,buildMscorlibAndReferencedAssemblyMismatch,"The referenced or default base CLI library 'mscorlib' is binary-incompatible with the referenced library '%s'. Consider recompiling the library or making an explicit reference to a version of this library that matches the CLI version you are using."
218,buildCannotReadAssembly,"Unable to read assembly '%s'"
219,buildMscorLibAndFSharpCoreMismatch,"The referenced or default base CLI library 'mscorlib' is binary-incompatible with the referenced F# core library '%s'. Consider recompiling the library or making an explicit reference to a version of this library that matches the CLI version you are using."
220,buildAssemblyResolutionFailed,"Assembly resolution failure at or near this location"
221,buildImplicitModuleIsNotLegalIdentifier,"The declarations in this file will be placed in an implicit module '%s' based on the file name '%s'. However this is not a valid F# identifier, so the contents will not be accessible from other files. Consider renaming the file or adding a 'module' or 'namespace' declaration at the top of the file."
222,buildMultiFileRequiresNamespaceOrModule,"Files in libraries or multiple-file applications must begin with a namespace or module declaration, e.g. 'namespace SomeNamespace.SubNamespace' or 'module SomeNamespace.SomeModule'. Only the last source file of an application may omit such a declaration."
......@@ -906,12 +903,12 @@ optsDCLOHtmlDoc,"The command-line option '%s' has been deprecated. HTML document
optsConsoleColors,"Output warning and error messages in color"
optsUseHighEntropyVA,"Enable high-entropy ASLR"
optsSubSystemVersion,"Specify subsystem version of this assembly"
optsTargetProfile,"Specify target framework profile of this assembly. Valid values are mscorlib or netcore. Default - mscorlib"
optsTargetProfile,"Specify target framework profile of this assembly. Valid values are mscorlib, netcore or netstandard. Default - mscorlib"
optsEmitDebugInfoInQuotations,"Emit debug information in quotations"
optsPreferredUiLang," Specify the preferred output language culture name (e.g. es-ES, ja-JP)"
optsNoCopyFsharpCore,"Don't copy FSharp.Core.dll along the produced binaries"
1051,optsInvalidSubSystemVersion,"Invalid version '%s' for '--subsystemversion'. The version must be 4.00 or greater."
1052,optsInvalidTargetProfile,"Invalid value '%s' for '--targetprofile', valid values are 'mscorlib' or 'netcore'."
1052,optsInvalidTargetProfile,"Invalid value '%s' for '--targetprofile', valid values are 'mscorlib', 'netcore' or 'netstandard'."
typeInfoFullName,"Full name"
# typeInfoType,"type"
# typeInfoInherits,"inherits"
......@@ -1275,7 +1272,6 @@ typeInfoCallsWord,"Calls"
3151,tcThisValueMayNotBeInlined,"This member, function or value declaration may not be declared 'inline'"
3152,etErasedTypeUsedInGeneration,"The provider '%s' returned a non-generated type '%s' in the context of a set of generated types. Consider adjusting the type provider to only return generated types."
3153,tcUnrecognizedQueryBinaryOperator,"Arguments to query operators may require parentheses, e.g. 'where (x > y)' or 'groupBy (x.Length / 10)'"
3154,invalidPlatformTargetForOldFramework,"The 'anycpu32bitpreferred' platform flag may only be used with .NET Framework versions 4.5 and greater."
3155,crefNoSetOfHole,"A quotation may not involve an assignment to or taking the address of a captured local variable"
nicePrintOtherOverloads1,"+ 1 overload"
nicePrintOtherOverloadsN,"+ %d overloads"
......
......@@ -35,9 +35,15 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and
<_ExplicitReference Include="$(FrameworkPathOverride)\mscorlib.dll" Condition=" '$(NoStdLib)' != 'true' " />
</ItemGroup>
<PropertyGroup>
<_TargetFrameworkVersionWithoutV>$(TargetFrameworkVersion)</_TargetFrameworkVersionWithoutV>
<_TargetFrameworkVersionWithoutV Condition="$(TargetFrameworkVersion.StartsWith('v'))">$(TargetFrameworkVersion.Substring(1))</_TargetFrameworkVersionWithoutV>
</PropertyGroup>
<PropertyGroup>
<TargetProfile Condition=" '$(TargetFrameworkIdentifier)' == '.NETFramework' " >mscorlib</TargetProfile>
<TargetProfile Condition=" '$(TargetFrameworkIdentifier)' != '.NETFramework' " >netcore</TargetProfile>
<TargetProfile Condition=" '$(TargetFrameworkIdentifier)' == '.NETStandard' and '$(_TargetFrameworkVersionWithoutV)' >= '2.0' " >netstandard</TargetProfile>
<OtherFlags>$(OtherFlags) --simpleresolution --nocopyfsharpcore</OtherFlags>
</PropertyGroup>
......@@ -52,8 +58,14 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and
</PropertyGroup>
<PropertyGroup Condition=" '$(DisableImplicitSystemValueTupleReference)' != 'true' ">
<_FrameworkNeedsValueTupleReference Condition=" $(TargetFramework.StartsWith(netcoreapp1.)) or $(TargetFramework.StartsWith(netstandard1.)) ">true</_FrameworkNeedsValueTupleReference>
<_FrameworkNeedsValueTupleReference Condition=" '$(TargetFramework)' == 'net40' or '$(TargetFramework)' == 'net45' or '$(TargetFramework)' == 'net46' or '$(TargetFramework)' == 'net461' or '$(TargetFramework)' == 'net462' or '$(TargetFramework)' == 'net47' ">true</_FrameworkNeedsValueTupleReference>
<_FrameworkNeedsValueTupleReference Condition=" ('$(TargetFrameworkIdentifier)' == '.NETStandard' or '$(TargetFrameworkIdentifier)' == '.NETCoreApp') and !('$(_TargetFrameworkVersionWithoutV)' >= '2.0') ">true</_FrameworkNeedsValueTupleReference>
<_FrameworkNeedsValueTupleReference Condition=" '$(TargetFrameworkIdentifier)' == '.NETFramework' and
('$(_TargetFrameworkVersionWithoutV)' == '4.0' or
'$(_TargetFrameworkVersionWithoutV)' == '4.5' or
'$(_TargetFrameworkVersionWithoutV)' == '4.6' or
'$(_TargetFrameworkVersionWithoutV)' == '4.6.1' or
'$(_TargetFrameworkVersionWithoutV)' == '4.6.2' or
'$(_TargetFrameworkVersionWithoutV)' == '4.7') ">true</_FrameworkNeedsValueTupleReference>
</PropertyGroup>
<ItemGroup>
......
......@@ -158,7 +158,7 @@ type public TcGlobals(compilingFslib: bool, ilg:ILGlobals, fslibCcu: CcuThunk, d
mlCompatibility: bool, isInteractive:bool,
// The helper to find system types amongst referenced DLLs
tryFindSysTypeCcu,
emitDebugInfoInQuotations: bool, usesMscorlib: bool, noDebugData: bool) =
emitDebugInfoInQuotations: bool, noDebugData: bool) =
let vara = NewRigidTypar "a" envRange
let varb = NewRigidTypar "b" envRange
......@@ -1234,7 +1234,6 @@ type public TcGlobals(compilingFslib: bool, ilg:ILGlobals, fslibCcu: CcuThunk, d
member __.suppressed_types = v_suppressed_types
/// Are we assuming all code gen is for F# interactive, with no static linking
member __.isInteractive=isInteractive
member __.usesMscorlib = usesMscorlib
member __.FindSysTyconRef path nm = findSysTyconRef path nm
member __.TryFindSysTyconRef path nm = tryFindSysTyconRef path nm
......
......@@ -763,9 +763,9 @@ module MainModuleBuilder =
CustomAttrs = mkILCustomAttrs List.empty<ILAttribute> }) |>
Seq.toList
let createSystemNumericsExportList (tcGlobals: TcGlobals) (tcImports:TcImports) =
let createSystemNumericsExportList (tcConfig: TcConfig) (tcImports:TcImports) =
let refNumericsDllName =
if tcGlobals.usesMscorlib then "System.Numerics"
if (tcConfig.primaryAssembly.Name = "mscorlib") then "System.Numerics"
else "System.Runtime.Numerics"
let numericsAssemblyRef =
match tcImports.GetImportedAssemblies() |> List.tryFind<ImportedAssembly>(fun a -> a.FSharpViewOfMetadata.AssemblyName = refNumericsDllName) with
......@@ -844,7 +844,7 @@ module MainModuleBuilder =
let exportedTypesList =
if (tcConfig.compilingFslib && tcConfig.compilingFslib40) then
(List.append (createMscorlibExportList tcGlobals)
(if tcConfig.compilingFslibNoBigInt then [] else (createSystemNumericsExportList tcGlobals tcImports))
(if tcConfig.compilingFslibNoBigInt then [] else (createSystemNumericsExportList tcConfig tcImports))
)
else
[]
......
......@@ -102,8 +102,8 @@ Copyright (c) Microsoft Corporation. All Rights Reserved.
directory-based rules rather than MSBuild
resolution
--targetprofile:<string> Specify target framework profile of this
assembly. Valid values are mscorlib or netcore.
Default - mscorlib
assembly. Valid values are mscorlib, netcore or
netstandard. Default - mscorlib
--baseaddress:<address> Base address for the library to be built
--noframework Do not reference the default CLI assemblies by
default
......
// #Regression #NoMT #CompilerOptions
//<Expects id="FS3154" status="error">The 'anycpu32bitpreferred' platform flag may only be used with \.NET Framework versions 4\.5 and greater\.</Expects>
exit 1
......@@ -59,8 +59,8 @@ Usage: fsharpi <options> [script.fsx [<arguments>]]
directory-based rules rather than MSBuild
resolution
--targetprofile:<string> Specify target framework profile of this
assembly. Valid values are mscorlib or netcore.
Default - mscorlib
assembly. Valid values are mscorlib, netcore or
netstandard. Default - mscorlib
--noframework Do not reference the default CLI assemblies by
default
--exec Exit fsi after loading the files or running the
......
......@@ -59,8 +59,8 @@ Usage: fsi.exe <options> [script.fsx [<arguments>]]
directory-based rules rather than MSBuild
resolution
--targetprofile:<string> Specify target framework profile of this
assembly. Valid values are mscorlib or netcore.
Default - mscorlib
assembly. Valid values are mscorlib, netcore or
netstandard. Default - mscorlib
--noframework Do not reference the default CLI assemblies by
default
--exec Exit fsi after loading the files or running the
......
......@@ -61,8 +61,8 @@ Usage: fsi.exe <options> [script.fsx [<arguments>]]
directory-based rules rather than MSBuild
resolution
--targetprofile:<string> Specify target framework profile of this
assembly. Valid values are mscorlib or netcore.
Default - mscorlib
assembly. Valid values are mscorlib, netcore or
netstandard. Default - mscorlib
--noframework Do not reference the default CLI assemblies by
default
--exec Exit fsi after loading the files or running the
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册