dotnet 职业技术学院 发布于 2019-03-01
我之前写过一些改变 MSBuild 编译过程的一些博客,包括利用 Microsoft.NET.Sdk 中各种自带的 Task 来执行各种各样的编译任务。更复杂的任务难以直接利用自带的 Task 实现,需要自己写 Task。
本文将编写一个内联的编译任务,获取当前编译环境下的所有编译目标(Target)。获取所有的这些 Target 对我们调试一些与 MSBuild 或编译相关的问题时可能带来一些帮助。
编写纯 C# 版本编译任务获取所有编译目标(Target)的代码是这样的:
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;
public class WalterlvGetAllTargets : Task
{
public string ProjectFile { get; set; }
public ITaskItem[] WalterlvTargets { get; set; }
public override bool Execute()
{
var project = new Project(ProjectFile);
var taskItems = new List<ITaskItem>(project.Targets.Count);
foreach (KeyValuePair<string, ProjectTargetInstance> pair in project.Targets)
{
var target = pair.Value;
var metadata = new Dictionary<string, string>
{
{ "Condition", target.Condition },
{ "Inputs", target.Inputs },
{ "Outputs", target.Outputs },
{ "DependsOnTargets", target.DependsOnTargets }
};
taskItems.Add(new TaskItem(pair.Key, metadata));
}
WalterlvTargets = taskItems.ToArray();
return true;
}
}
那么转换成内联版本下面这样。为了方便验证,我直接把完整的 csproj 文件贴出来了。如果你希望在你的项目中去使用,可以只复制 UsingTask
和 Target
两个部分。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net472</TargetFramework>
</PropertyGroup>
<UsingTask TaskName="WalterlvGetAllTargets" TaskFactory="CodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >
<ParameterGroup>
<!-- 内联 C# 代码的输入参数(Task 的输入属性),相当于 public string ProjectFile { get; set; } -->
<ProjectFile ParameterType="System.String" Required="true"/>
<!-- 内联 C# 代码的输出参数(Task 的输入属性),相当于 public ITaskItem[] WalterlvTargets { get; set; } -->
<WalterlvTargets ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true"/>
</ParameterGroup>
<Task>
<!-- 引用程序集。 -->
<Reference Include="System.Xml"/>
<Reference Include="Microsoft.Build"/>
<Reference Include="Microsoft.Build.Framework"/>
<!-- 编写 C# 代码所用到的 using。 -->
<Using Namespace="Microsoft.Build.Evaluation"/>
<Using Namespace="Microsoft.Build.Execution"/>
<Using Namespace="Microsoft.Build.Utilities"/>
<Using Namespace="Microsoft.Build.Framework"/>
<!-- 开始插入 C# 代码。 -->
<Code Type="Fragment" Language="cs">
<![CDATA[
var project = new Project(ProjectFile);
var taskItems = new List<ITaskItem>(project.Targets.Count);
foreach (KeyValuePair<string, ProjectTargetInstance> pair in project.Targets)
{
var target = pair.Value;
var metadata = new Dictionary<string, string>
{
{ "Condition", target.Condition },
{ "Inputs", target.Inputs },
{ "Outputs", target.Outputs },
{ "DependsOnTargets", target.DependsOnTargets }
};
taskItems.Add(new TaskItem(pair.Key, metadata));
}
WalterlvTargets = taskItems.ToArray();
]]>
</Code>
</Task>
</UsingTask>
<Target Name="WalterlvOutputAllTargets" AfterTargets="Build">
<!-- 执行刚刚写的内联 Task,然后获取它的输出参数 WalterlvTargets 并填充到 TargetItems 集合中。 -->
<WalterlvGetAllTargets ProjectFile="$(MSBuildProjectFile)">
<Output ItemName="TargetItems" TaskParameter="WalterlvTargets"/>
</WalterlvGetAllTargets>
<!-- 用一个 Message 输出刚刚生成的 TargetItems 集合中每一项的 Identity 属性(集合中每一项都会输出。) -->
<Message Text="输出的 Target:%(TargetItems.Identity)"/>
</Target>
<Project>
现在使用 msbuild
命令进行编译,我们将看到所有 Target 的输出:
WalterlvOutputAllTargets:
输出的 Target:OutputAll
输出的 Target:_CheckForUnsupportedTargetFramework
输出的 Target:_CollectTargetFrameworkForTelemetry
输出的 Target:_CheckForUnsupportedNETCoreVersion
输出的 Target:_CheckForUnsupportedNETStandardVersion
输出的 Target:_CheckForUnsupportedAppHostUsage
输出的 Target:_CheckForMismatchingPlatform
输出的 Target:_CheckForNETCoreSdkIsPreview
输出的 Target:AdjustDefaultPlatformTargetForNetFrameworkExeWithNoNativeCopyLocalItems
输出的 Target:CreateManifestResourceNames
输出的 Target:ResolveCodeAnalysisRuleSet
输出的 Target:XamlPreCompile
输出的 Target:ShimReferencePathsWhenCommonTargetsDoesNotUnderstandReferenceAssemblies
输出的 Target:_BeforeVBCSCoreCompile
输出的 Target:InitializeSourceRootMappedPaths
输出的 Target:_InitializeSourceRootMappedPathsFromSourceControl
输出的 Target:_SetPathMapFromSourceRoots
输出的 Target:CoreCompile
输出的 Target:ResolvePackageDependenciesDesignTime
输出的 Target:CollectSDKReferencesDesignTime
输出的 Target:CollectResolvedSDKReferencesDesignTime
输出的 Target:CollectPackageReferences
输出的 Target:_CheckCompileDesignTimePrerequisite
输出的 Target:CollectAnalyzersDesignTime
输出的 Target:CollectResolvedCompilationReferencesDesignTime
输出的 Target:CollectUpToDateCheckInputDesignTime
输出的 Target:CollectUpToDateCheckOutputDesignTime
输出的 Target:CollectUpToDateCheckBuiltDesignTime
输出的 Target:CompileDesignTime
输出的 Target:_FixVCLibs120References
输出的 Target:_AddVCLibs140UniversalCrtDebugReference
输出的 Target:InitializeSourceControlInformation
输出的 Target:_CheckForInvalidConfigurationAndPlatform
输出的 Target:Build
输出的 Target:BeforeBuild
输出的 Target:AfterBuild
输出的 Target:CoreBuild
输出的 Target:Rebuild
输出的 Target:BeforeRebuild
输出的 Target:AfterRebuild
输出的 Target:BuildGenerateSources
输出的 Target:BuildGenerateSourcesTraverse
输出的 Target:BuildCompile
输出的 Target:BuildCompileTraverse
输出的 Target:BuildLink
输出的 Target:BuildLinkTraverse
输出的 Target:CopyRunEnvironmentFiles
输出的 Target:Run
输出的 Target:BuildOnlySettings
输出的 Target:PrepareForBuild
输出的 Target:GetFrameworkPaths
输出的 Target:GetReferenceAssemblyPaths
输出的 Target:GetTargetFrameworkMoniker
输出的 Target:GetTargetFrameworkMonikerDisplayName
输出的 Target:GetTargetFrameworkDirectories
输出的 Target:AssignLinkMetadata
输出的 Target:PreBuildEvent
输出的 Target:UnmanagedUnregistration
输出的 Target:GetTargetFrameworkVersion
输出的 Target:ResolveReferences
输出的 Target:BeforeResolveReferences
输出的 Target:AfterResolveReferences
输出的 Target:AssignProjectConfiguration
输出的 Target:_SplitProjectReferencesByFileExistence
输出的 Target:_GetProjectReferenceTargetFrameworkProperties
输出的 Target:GetTargetFrameworks
输出的 Target:GetTargetFrameworkProperties
输出的 Target:PrepareProjectReferences
输出的 Target:ResolveProjectReferences
输出的 Target:ResolveProjectReferencesDesignTime
输出的 Target:ExpandSDKReferencesDesignTime
输出的 Target:GetTargetPath
输出的 Target:GetTargetPathWithTargetPlatformMoniker
输出的 Target:GetNativeManifest
输出的 Target:ResolveNativeReferences
输出的 Target:ResolveAssemblyReferences
输出的 Target:FindReferenceAssembliesForReferences
输出的 Target:GenerateBindingRedirects
输出的 Target:GenerateBindingRedirectsUpdateAppConfig
输出的 Target:GetInstalledSDKLocations
输出的 Target:ResolveSDKReferences
输出的 Target:ResolveSDKReferencesDesignTime
输出的 Target:FindInvalidProjectReferences
输出的 Target:GetReferenceTargetPlatformMonikers
输出的 Target:ExpandSDKReferences
输出的 Target:ExportWindowsMDFile
输出的 Target:ResolveAssemblyReferencesDesignTime
输出的 Target:DesignTimeResolveAssemblyReferences
输出的 Target:ResolveComReferences
输出的 Target:ResolveComReferencesDesignTime
输出的 Target:PrepareResources
输出的 Target:PrepareResourceNames
输出的 Target:AssignTargetPaths
输出的 Target:GetItemTargetPaths
输出的 Target:SplitResourcesByCulture
输出的 Target:CreateCustomManifestResourceNames
输出的 Target:ResGen
输出的 Target:BeforeResGen
输出的 Target:AfterResGen
输出的 Target:CoreResGen
输出的 Target:CompileLicxFiles
输出的 Target:ResolveKeySource
输出的 Target:Compile
输出的 Target:_GenerateCompileInputs
输出的 Target:GenerateTargetFrameworkMonikerAttribute
输出的 Target:GenerateAdditionalSources
输出的 Target:BeforeCompile
输出的 Target:AfterCompile
输出的 Target:_TimeStampBeforeCompile
输出的 Target:_GenerateCompileDependencyCache
输出的 Target:_TimeStampAfterCompile
输出的 Target:_ComputeNonExistentFileProperty
输出的 Target:GenerateSerializationAssemblies
输出的 Target:CreateSatelliteAssemblies
输出的 Target:_GenerateSatelliteAssemblyInputs
输出的 Target:GenerateSatelliteAssemblies
输出的 Target:ComputeIntermediateSatelliteAssemblies
输出的 Target:SetWin32ManifestProperties
输出的 Target:_SetExternalWin32ManifestProperties
输出的 Target:_SetEmbeddedWin32ManifestProperties
输出的 Target:_GenerateResolvedDeploymentManifestEntryPoint
输出的 Target:GenerateManifests
输出的 Target:GenerateApplicationManifest
输出的 Target:_DeploymentComputeNativeManifestInfo
输出的 Target:_DeploymentComputeClickOnceManifestInfo
输出的 Target:_DeploymentGenerateTrustInfo
输出的 Target:GenerateDeploymentManifest
输出的 Target:PrepareForRun
输出的 Target:CopyFilesToOutputDirectory
输出的 Target:_CopyFilesMarkedCopyLocal
输出的 Target:_CopySourceItemsToOutputDirectory
输出的 Target:GetCopyToOutputDirectoryItems
输出的 Target:GetCopyToPublishDirectoryItems
输出的 Target:_CopyOutOfDateSourceItemsToOutputDirectory
输出的 Target:_CopyOutOfDateSourceItemsToOutputDirectoryAlways
输出的 Target:_CopyAppConfigFile
输出的 Target:_CopyManifestFiles
输出的 Target:_CheckForCompileOutputs
输出的 Target:_SGenCheckForOutputs
输出的 Target:UnmanagedRegistration
输出的 Target:IncrementalClean
输出的 Target:_CleanGetCurrentAndPriorFileWrites
输出的 Target:Clean
输出的 Target:BeforeClean
输出的 Target:AfterClean
输出的 Target:CleanReferencedProjects
输出的 Target:CoreClean
输出的 Target:_CleanRecordFileWrites
输出的 Target:CleanPublishFolder
输出的 Target:PostBuildEvent
输出的 Target:Publish
输出的 Target:_DeploymentUnpublishable
输出的 Target:SetGenerateManifests
输出的 Target:PublishOnly
输出的 Target:BeforePublish
输出的 Target:AfterPublish
输出的 Target:PublishBuild
输出的 Target:_CopyFilesToPublishFolder
输出的 Target:_DeploymentGenerateBootstrapper
输出的 Target:_DeploymentSignClickOnceDeployment
输出的 Target:AllProjectOutputGroups
输出的 Target:BuiltProjectOutputGroup
输出的 Target:DebugSymbolsProjectOutputGroup
输出的 Target:DocumentationProjectOutputGroup
输出的 Target:SatelliteDllsProjectOutputGroup
输出的 Target:SourceFilesProjectOutputGroup
输出的 Target:GetCompile
输出的 Target:ContentFilesProjectOutputGroup
输出的 Target:SGenFilesOutputGroup
输出的 Target:GetResolvedSDKReferences
输出的 Target:CollectReferencedNuGetPackages
输出的 Target:PriFilesOutputGroup
输出的 Target:SDKRedistOutputGroup
输出的 Target:AllProjectOutputGroupsDependencies
输出的 Target:BuiltProjectOutputGroupDependencies
输出的 Target:DebugSymbolsProjectOutputGroupDependencies
输出的 Target:SatelliteDllsProjectOutputGroupDependencies
输出的 Target:DocumentationProjectOutputGroupDependencies
输出的 Target:SGenFilesOutputGroupDependencies
输出的 Target:ReferenceCopyLocalPathsOutputGroup
输出的 Target:SetCABuildNativeEnvironmentVariables
输出的 Target:RunCodeAnalysis
输出的 Target:RunNativeCodeAnalysis
输出的 Target:RunSelectedFileNativeCodeAnalysis
输出的 Target:RunMergeNativeCodeAnalysis
输出的 Target:ImplicitlyExpandDesignTimeFacades
输出的 Target:GetWinFXPath
输出的 Target:DesignTimeMarkupCompilation
输出的 Target:PrepareResourcesForSatelliteAssemblies
输出的 Target:_AfterCompileWinFXInternal
输出的 Target:AfterCompileWinFX
输出的 Target:AfterMarkupCompilePass1
输出的 Target:AfterMarkupCompilePass2
输出的 Target:MarkupCompilePass1
输出的 Target:MarkupCompilePass2
输出的 Target:_CompileTemporaryAssembly
输出的 Target:MarkupCompilePass2ForMainAssembly
输出的 Target:GenerateTemporaryTargetAssembly
输出的 Target:CleanupTemporaryTargetAssembly
输出的 Target:AddIntermediateAssemblyToReferenceList
输出的 Target:SatelliteOnlyMarkupCompilePass2
输出的 Target:HostInBrowserValidation
输出的 Target:SplashScreenValidation
输出的 Target:ResignApplicationManifest
输出的 Target:SignDeploymentManifest
输出的 Target:FileClassification
输出的 Target:MainResourcesGeneration
输出的 Target:SatelliteResourceGeneration
输出的 Target:GenerateResourceWithCultureItem
输出的 Target:CheckUid
输出的 Target:UpdateUid
输出的 Target:RemoveUid
输出的 Target:MergeLocalizationDirectives
输出的 Target:AssignWinFXEmbeddedResource
输出的 Target:EntityDeploy
输出的 Target:EntityDeploySplit
输出的 Target:EntityDeployNonEmbeddedResources
输出的 Target:EntityDeployEmbeddedResources
输出的 Target:EntityClean
输出的 Target:EntityDeploySetLogicalNames
输出的 Target:DesignTimeXamlMarkupCompilation
输出的 Target:InProcessXamlMarkupCompilePass1
输出的 Target:CleanInProcessXamlGeneratedFiles
输出的 Target:XamlMarkupCompileReadGeneratedFileList
输出的 Target:XamlMarkupCompilePass1
输出的 Target:XamlMarkupCompileAddFilesGenerated
输出的 Target:XamlMarkupCompileReadPass2Flag
输出的 Target:XamlTemporaryAssemblyGeneration
输出的 Target:CompileTemporaryAssembly
输出的 Target:XamlMarkupCompilePass2
输出的 Target:XamlMarkupCompileAddExtensionFilesGenerated
输出的 Target:GetCopyToOutputDirectoryXamlAppDefs
输出的 Target:ExpressionBuildExtension
输出的 Target:ValidationExtension
输出的 Target:GenerateCompiledExpressionsTempFile
输出的 Target:AddDeferredValidationErrorsFileToFileWrites
输出的 Target:ReportValidationBuildExtensionErrors
输出的 Target:DeferredValidation
输出的 Target:ResolveTestReferences
输出的 Target:CleanAppxPackage
输出的 Target:GetPackagingOutputs
输出的 Target:Restore
输出的 Target:GenerateRestoreGraphFile
输出的 Target:_LoadRestoreGraphEntryPoints
输出的 Target:_FilterRestoreGraphProjectInputItems
输出的 Target:_GenerateRestoreGraph
输出的 Target:_GenerateRestoreGraphProjectEntry
输出的 Target:_GenerateRestoreSpecs
输出的 Target:_GenerateDotnetCliToolReferenceSpecs
输出的 Target:_GetProjectJsonPath
输出的 Target:_GetRestoreProjectStyle
输出的 Target:EnableIntermediateOutputPathMismatchWarning
输出的 Target:_GetRestoreTargetFrameworksOutput
输出的 Target:_GetRestoreTargetFrameworksAsItems
输出的 Target:_GetRestoreSettings
输出的 Target:_GetRestoreSettingsCurrentProject
输出的 Target:_GetRestoreSettingsAllFrameworks
输出的 Target:_GetRestoreSettingsPerFramework
输出的 Target:_GenerateRestoreProjectSpec
输出的 Target:_GenerateProjectRestoreGraph
输出的 Target:_GenerateRestoreDependencies
输出的 Target:_GenerateProjectRestoreGraphAllFrameworks
输出的 Target:_GenerateProjectRestoreGraphCurrentProject
输出的 Target:_GenerateProjectRestoreGraphPerFramework
输出的 Target:_GenerateRestoreProjectPathItemsCurrentProject
输出的 Target:_GenerateRestoreProjectPathItemsPerFramework
输出的 Target:_GenerateRestoreProjectPathItems
输出的 Target:_GenerateRestoreProjectPathItemsAllFrameworks
输出的 Target:_GenerateRestoreProjectPathWalk
输出的 Target:_GetAllRestoreProjectPathItems
输出的 Target:_GetRestoreSettingsOverrides
输出的 Target:_GetRestorePackagesPathOverride
输出的 Target:_GetRestoreSourcesOverride
输出的 Target:_GetRestoreFallbackFoldersOverride
输出的 Target:_IsProjectRestoreSupported
输出的 Target:DesktopBridgeCopyLocalOutputGroup
输出的 Target:DesktopBridgeComFilesOutputGroup
输出的 Target:GetDeployableContentReferenceOutputs
输出的 Target:DockerResolveAppType
输出的 Target:DockerUpdateComposeVsGeneratedFiles
输出的 Target:DockerResolveTargetFramework
输出的 Target:DockerComposeBuild
输出的 Target:DockerPackageService
输出的 Target:ImplicitlyExpandNETStandardFacades
输出的 Target:_RemoveZipFileSuggestedRedirect
输出的 Target:SetARM64AppxPackageInputsForInboxNetNative
输出的 Target:_CleanMdbFiles
输出的 Target:PreXsdCodeGen
输出的 Target:XsdCodeGen
输出的 Target:XsdResolveReferencePath
输出的 Target:CleanXsdCodeGen
输出的 Target:_SetTargetFrameworkMonikerAttribute
输出的 Target:ResolvePackageDependenciesForBuild
输出的 Target:RunResolvePackageDependencies
输出的 Target:ResolvePackageAssets
输出的 Target:FilterSatelliteResources
输出的 Target:RunProduceContentAssets
输出的 Target:ReportAssetsLogMessages
输出的 Target:ResolveLockFileReferences
输出的 Target:IncludeTransitiveProjectReferences
输出的 Target:ResolveLockFileAnalyzers
输出的 Target:_ComputeLockFileCopyLocal
输出的 Target:ResolveLockFileCopyLocalProjectDeps
输出的 Target:CheckForImplicitPackageReferenceOverrides
输出的 Target:CheckForDuplicateItems
输出的 Target:GenerateBuildDependencyFile
输出的 Target:GenerateBuildRuntimeConfigurationFiles
输出的 Target:AddRuntimeConfigFileToBuiltProjectOutputGroupOutput
输出的 Target:_SdkBeforeClean
输出的 Target:_SdkBeforeRebuild
输出的 Target:_ComputeNETCoreBuildOutputFiles
输出的 Target:_ComputeReferenceAssemblies
输出的 Target:CoreGenerateSatelliteAssemblies
输出的 Target:_GetAssemblyInfoFromTemplateFile
输出的 Target:_DefaultMicrosoftNETPlatformLibrary
输出的 Target:GetAllRuntimeIdentifiers
输出的 Target:GenerateAssemblyInfo
输出的 Target:AddSourceRevisionToInformationalVersion
输出的 Target:GetAssemblyAttributes
输出的 Target:CreateGeneratedAssemblyInfoInputsCacheFile
输出的 Target:CoreGenerateAssemblyInfo
输出的 Target:GetAssemblyVersion
输出的 Target:ComposeStore
输出的 Target:StoreWorkerMain
输出的 Target:StoreWorkerMapper
输出的 Target:StoreResolver
输出的 Target:StoreWorkerPerformWork
输出的 Target:StoreFinalizer
输出的 Target:_CopyResolvedOptimizedFiles
输出的 Target:PrepareForComposeStore
输出的 Target:PrepforRestoreForComposeStore
输出的 Target:RestoreForComposeStore
输出的 Target:ComputeAndCopyFilesToStoreDirectory
输出的 Target:CopyFilesToStoreDirectory
输出的 Target:_CopyResolvedUnOptimizedFiles
输出的 Target:_ComputeResolvedFilesToStoreTypes
输出的 Target:_SplitResolvedFiles
输出的 Target:_GetResolvedFilesToStore
输出的 Target:ComputeFilesToStore
输出的 Target:PrepRestoreForStoreProjects
输出的 Target:PrepOptimizer
输出的 Target:_RunOptimizer
输出的 Target:RunCrossGen
输出的 Target:_InitializeBasicProps
输出的 Target:_GetCrossgenProps
输出的 Target:_SetupStageForCrossgen
输出的 Target:_RestoreCrossgen
输出的 Target:_CheckForObsoleteDotNetCliToolReferences
输出的 Target:_PublishBuildAlternative
输出的 Target:_PublishNoBuildAlternative
输出的 Target:_PreventProjectReferencesFromBuilding
输出的 Target:PrepareForPublish
输出的 Target:ComputeAndCopyFilesToPublishDirectory
输出的 Target:CopyFilesToPublishDirectory
输出的 Target:_CopyResolvedFilesToPublishPreserveNewest
输出的 Target:_CopyResolvedFilesToPublishAlways
输出的 Target:_ComputeResolvedFilesToPublishTypes
输出的 Target:ComputeFilesToPublish
输出的 Target:_ComputeNetPublishAssets
输出的 Target:RunResolvePublishAssemblies
输出的 Target:FilterPublishSatelliteResources
输出的 Target:_ComputeCopyToPublishDirectoryItems
输出的 Target:DefaultCopyToPublishDirectoryMetadata
输出的 Target:GeneratePublishDependencyFile
输出的 Target:_ComputeExcludeFromPublishPackageReferences
输出的 Target:_ParseTargetManifestFiles
输出的 Target:GeneratePublishRuntimeConfigurationFile
输出的 Target:DeployAppHost
输出的 Target:PackTool
输出的 Target:GenerateToolsSettingsFileFromBuildProperty
输出的 Target:ResolveApphostAsset
输出的 Target:ComputeDependencyFileCompilerOptions
输出的 Target:ComputeRefAssembliesToPublish
输出的 Target:_CopyReferenceOnlyAssembliesForBuild
输出的 Target:_HandlePackageFileConflicts
输出的 Target:_HandlePublishFileConflicts
输出的 Target:_GetOutputItemsFromPack
输出的 Target:_GetTargetFrameworksOutput
输出的 Target:_PackAsBuildAfterTarget
输出的 Target:_CleanPackageFiles
输出的 Target:_CalculateInputsOutputsForPack
输出的 Target:Pack
输出的 Target:_IntermediatePack
输出的 Target:GenerateNuspec
输出的 Target:_InitializeNuspecRepositoryInformationProperties
输出的 Target:_LoadPackInputItems
输出的 Target:_GetProjectReferenceVersions
输出的 Target:_GetProjectVersion
输出的 Target:_WalkEachTargetPerFramework
输出的 Target:_GetFrameworksWithSuppressedDependencies
输出的 Target:_GetFrameworkAssemblyReferences
输出的 Target:_GetBuildOutputFilesWithTfm
输出的 Target:_GetTfmSpecificContentForPackage
输出的 Target:_GetDebugSymbolsWithTfm
输出的 Target:_AddPriFileToPackBuildOutput
输出的 Target:_GetPackageFiles
参考资料
dotnet 职业技术学院 发布于 2019-03-01
我之前写过一些改变 MSBuild 编译过程的一些博客,包括利用 Microsoft.NET.Sdk 中各种自带的 Task 来执行各种各样的编译任务。更复杂的任务难以直接利用自带的 Task 实现,需要自己写 Task。
本文介绍非常简单的 Task 的编写方式 —— 在 csproj 文件中写内联的 Task。
在阅读本文之前,你至少需要懂得:
所以如果你不懂或者理不清,则请先阅读:
关于 Task 的理解,我有一些介绍自带 Task 的博客以及如何编写 Task 的教程:
如果你阅读了前面的博客,那么大致知道如何写一个在编译期间执行的 Task。不过,默认你需要编写一个额外的项目来写 Task,然后将这个项目生成 dll 供编译过程通过 UsingTask
来使用。然而如果 Task 足够简单,那么依然需要那么复杂的过程显然开发成本过高。
于是现在可以编写内联的 Task:
Microsoft.Build.Tasks.v4.0.dll
;<![CDATA[ ]]>
来内嵌 C# 代码;UsingTask
编写内联的 Task 外,我们需要额外编写一个 Target
来验证我们的内联 Task 能正常工作。下面是一个最简单的内联编译任务:
<Project Sdk="Microsoft.NET.Sdk">
<UsingTask TaskName="WalterlvDemoTask" TaskFactory="CodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<Task>
<Code Type="Fragment" Language="cs">
<![CDATA[
Console.WriteLine("Hello Walterlv!");
]]>
</Code>
</Task>
</UsingTask>
<Project>
为了能够测试,我把完整的 csproj 文件贴出来:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net472</TargetFramework>
</PropertyGroup>
<UsingTask TaskName="WalterlvDemoTask" TaskFactory="CodeTaskFactory"
AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<Task>
<Code Type="Fragment" Language="cs">
<![CDATA[
Console.WriteLine("Hello Walterlv!");
]]>
</Code>
</Task>
</UsingTask>
<Target Name="WalterlvDemoTarget" AfterTargets="Build">
<WalterlvDemoTask />
</Target>
</Project>
目前内联编译仅适用于 MSBuild,而 dotnet build
尚不支持。现在在项目目录输入命令进行编译,可以在输出窗口看到我们内联编译中的输出内容:
msbuild
阅读我的另一篇博客了解如何编写一个更复杂的内联编译任务:
dotnet 职业技术学院 发布于 2019-02-27
大型旧项目可能存在大量的 Warning,在编译之后 Visual Studio 会给出大量的警告。Visual Studio 中可以直接点掉警告,然而如果是通过命令行 msbuild 编译的,那如何不要让警告输出呢?
在使用 msbuild 命令编译项目的时候,如果存在大量的警告,输出量会非常多。如果我们使用 msbuild 命令编译来定位项目的编译错误,那么这些警告将会导致我们准确查找错误的效率明显降低。
当然,这种问题的首选解决方案是 —— 真的修复掉这些警告!!!
那么可以用什么方式临时关闭 msbuild 命令编译时的警告呢?可以输入如下命令:
msbuild /p:WarningLevel=0
这样在调试编译问题的时候,因警告而造成的大量输出信息就会少很多。
不过需要注意的是,这种方式不会关闭所有的警告,实际上这关闭的是 csc 命令的警告(CS
开头)。关于 csc 命令的警告可以参见:-warn (C# Compiler Options) - Microsoft Docs。于是,如果项目中存在 msbuild 的警告(MSB
开头),此方法依然还会输出,只不过如果是为了调试编译问题,那么依然会方便很多,因为 MSB
开头的警告会少非常多。
关于警告等级:
0
关闭所有的警告。1
仅显示严重警告。2
显示 1 级的警告以及某些不太严重的警告,例如有关隐藏类成员的警告。3
显示级别 2 警告以及某些不太严重的警告,例如关于始终评估为 true
或 false
的表达式的警告。4
默认值 显示所有 3 级警告和普通信息警告。参考资料
dotnet 职业技术学院 发布于 2019-02-27
在昏暗的夜晚,一个人躺在房间的床上,静静的思考着什么。突然间电脑屏幕亮了!什么鬼!到底是谁唤醒了我的电脑!!!
本文将介绍如何寻找唤醒电脑的真凶。
使用命令查看上一次是谁唤醒了电脑。
powercfg -lastwake
从图中可知上一次唤醒我计算机的是 英特尔® 以太网连接 I219-V 82186。
使用命令查看所有可以唤醒电脑的设备。
powercfg -devicequery wake_armed
发现能唤醒我电脑的设备是键盘鼠标以及刚刚的以太网。
使用命令可以查看下一次计划的唤醒。
powercfg -waketimers
当然这只能查到计划的唤醒,类似鼠标键盘还有以太网这种根据硬件状态触发的唤醒是看不到的。
由于我不知道到底是谁通过以太网唤醒了我的电脑,所以我直接关掉以太网的唤醒即可。
前往设备管理器,找到刚刚发现的硬件设备,查看属性。
然后我关闭了此设备唤醒电脑的设置。
参考资料
dotnet 职业技术学院 发布于 2019-02-27
友盟(cnzz)可以帮助我们分析站点的访问数据。不过如果有更多的决策者,则需要更多人可以访问到友盟的数据。
本文介绍两种将自己站点的访问数据报告分享给其他人的方法。
首先,你必须有一个友盟产品的账号,如果还没有,去 注册吧!
进入 站点列表 页面。
点击网站右侧的“设置”。
然后进入查看密码标签,开启查看密码服务,然后输入查看密码。
这种方式的好处在于非常简单,你只需要告诉他人你的查看密码,其他人随时可以点开你网站的数据统计链接查看站点的访问数据。
在 Web 端点开站点底部的访问数据即可进入数据报表页面。输入查看密码即可查看。
cnzz 移动端下载安装后首页有四个按钮。点击“查看密码”后输入站点 Id 或扫码,然后输入密码即可查看数据。
不过移动端的 cnzz 做得很烂,如果登录过自己的站点查看数据,那么使用密码查看必崩,而且至今未修复。
本来使用密码查看是非常方便的,但是为了解决崩溃问题,还是需要使用授权账号来查看数据。
进入 站点列表 页面。
点击网站右上角的“授权”。
如果还没有授权给其他人,则可以点击“添加授权账号”。
然后输入对方的友盟账号和邮箱,添加对方的权限。
进入 站点列表 页面,可以在被授权站点看到授权查看的站点数据了。
cnzz 移动端下载安装后首页有四个按钮。点击“网站统计”后可以看到自己的站点和被授权的站点。
dotnet 职业技术学院 发布于 2019-02-27
本文介绍如何添加自定义的 NuGet 源。包括全局所有项目生效的 NuGet 源和仅在某些特定项目中生效的 NuGet 源。
你可以前往 我收集的各种公有 NuGet 源 以发现更多的 NuGet 源,然后使用本文的方法添加到你自己的配置中。
在使用命令行之前,你需要先在 https://www.nuget.org/downloads 下载最新的 nuget.exe 然后加入到环境变量中。
现在,我们使用命令行来添加一个包含各种日构建版本的 NuGet 源 MyGet:
nuget sources add -Name "MyGet" -Source "https://dotnet.myget.org/F/dotnet-core/api/v3/index.json"
如果你添加的只是一个镜像源(比如华为云 huaweicloud),那么其功能和官方源是重合的,可以禁用掉官方源:
nuget sources Disable -Name "nuget.org"
nuget sources add -Name "huaweicloud" -Source "https://mirrors.huaweicloud.com/repository/nuget/v3/index.json"
在 Visual Studio 中打开 工具
-> 选项
-> NuGet 包管理器
-> 包源
:
然后在界面上添加、删除、启用和禁用 NuGet 源。
值得注意的是:
nuget.org
的,无论你如何取消勾选,实际都不会生效。
NuGet 的全局配置文件在 %AppData\NuGet\NuGet.config
,例如:
C:\Users\lvyi\AppData\Roaming\NuGet\NuGet.Config
直接修改这个文件的效果跟使用命令行和 Visual Studio 的界面配置是等价的。
<configuration>
<packageSources>
<add key="huaweicloud" value="https://repo.huaweicloud.com/repository/nuget/v3/index.json" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="Walterlv.Debug" value="C:\Users\lvyi\Debug\Walterlv.NuGet" />
<add key="Microsoft Visual Studio Offline Packages" value="C:\Program Files (x86)\Microsoft SDKs\NuGetPackages\" />
<add key="MyGet" value="https://dotnet.myget.org/F/dotnet-core/api/v3/index.json" />
</packageSources>
<disabledPackageSources>
<add key="Microsoft Visual Studio Offline Packages" value="true" />
<add key="Walterlv.Debug" value="true" />
<add key="nuget.org" value="true" />
</disabledPackageSources>
</configuration>
NuGet.config 文件是有优先级的。nuget.exe 会先把全局配置加载进来;然后从当前目录中寻找 NuGet.config 文件,如果没找到就去上一级目录找,一直找到驱动器的根目录;找到后添加到已经加载好的全局配置中成为一个合并的配置。
所以我们只需要在项目的根目录放一个 NuGet.config 文件并填写相比于全局 NuGet.config 新增的配置即可为单独的项目添加 NuGet 配置。
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!-- 下一行的 clear 如果取消了注释,那么就会清除掉全局的 NuGet 源,而注释掉可以继承全局 NuGet 源,只是额外添加。 -->
<!-- <clear /> -->
<add key="MyGet" value="https://dotnet.myget.org/F/dotnet-core/api/v3/index.json" />
</packageSources>
</configuration>
dotnet 职业技术学院 发布于 2019-02-24
从 C# 7.0 开始,C# 支持弃元,这是一种在应用程序代码中人为取消使用的临时虚拟变量。
使用过ConcurrentDictionary<T,T>
的同学肯定经历过这样的痛苦
var dict = new ConcurrentDictionary<int,int>
dict[1]=1;
var result = dict.TryRemove(1, out var value);
Console.WriteLine(result);
我明明没有用到字典中删除的值,但是还是非要将这个值赋给某个变量。
有了弃元之后,你就可以写出这样的代码
var dict = new ConcurrentDictionary<int,int>();
dict[1]=1;
var result = dict.TryRemove(1,out _);
Console.WriteLine(result);
我们看到,不但没有变量赋值,连类型声明都不需要了,代替的只是使用了一个下划线_
当然弃元不只是书写和语义上的提升,它还可以减少内存分配。
除了out
参数,我们还可以再元组析构,switch
等语句中使用弃元写出优雅代码
例如,下面的例子我们只希望使用到日期中的年份
var (year,_,_) = GetDate();
private (string,string,string) GetDate()
{
//...
return (year,month,day);
}
例如,我们在switch
的模式匹配中不期望对指定类型的对象进行使用
Foo("10");
void Foo(object item)
{
switch (item)
{
case int val:
Console.WriteLine(val);
break;
case string _:
Console.WriteLine("Error");
break;
}
}
参考链接:
dotnet 职业技术学院 发布于 2019-02-24
使用过命名元组的同学都知道,命名元组可以使用“有意义的字段名”来代替Item
,用于表示元组的成员。在C#7.1中引入了“元组投影初始值设定项”(tuple projection initializers)提升了命名元组的编程体验
一般情况下,我们会采用下面这种方式进行命名元组的创建
var firstName = "Huang";
var secendName = "Tengxiao";
var fullName = (firstName:firstName,secendName:secendName);
Console.WriteLine(fullName.firstName);
Console.WriteLine(fullName.secendName);
但是在C#7.1之后可以使用如下写法,在式子中,元组采用构造时的变量名称对字段进行初始化
var firstName = "Huang";
var secendName = "Tengxiao";
var fullName = (firstName,secendName);
Console.WriteLine(fullName.firstName);
Console.WriteLine(fullName.secendName);
另外对于没有提供变量名称的初始化方式,元组会采用匿名元组默认的Item
名称对字段名称进行初始化。例如
另外在以下两种情况下,不会将候选字段名称投影到元组字段:
ItemX
、ToString
、 或 Rest
。如下面的例子,在使用保留字,或者出现重名的时候,都会采用匿名元组默认的Item
名称对字段名称进行初始化。
var Item3 = "Huang";
var Rest = "Tengxiao";
var fullName = (Item3,Rest);
Console.WriteLine(fullName.Item1);
Console.WriteLine(fullName.Item2);
var pt1 = (X: 3, Y: 0);
var pt2 = (X: 3, Y: 4);
var xCoords = (pt1.X, pt2.X);
Console.WriteLine(xCoords.Item1);
Console.WriteLine(xCoords.Item2);
不过有个小tips,对于c#这种区分大小写命名的语言,只要采用如下的小写命名就不会同保留字产生冲突。
(快去统一你们团队的命名元组编程规范吧~)
var item3 = "Huang";
var rest = "Tengxiao";
var fullName = (item3,rest);
Console.WriteLine(fullName.item3);
Console.WriteLine(fullName.rest);
参考链接:
dotnet 职业技术学院 发布于 2019-02-24
元组作为轻量级的数据结构,在c#中具有广泛的引用。但是元组的比较一直以来都是对于成员的依次比较。好在C#7.3开始,引入了元素的相等性比较,让元组的易用性有了大幅提升。
微软对此的介绍是“从 C# 7.3 开始,元组类型支持 ==
和 !=
运算符。 这些运算符按顺序将左边参数的每个成员与右边参数的每个成员进行比较,且比较是短路计算”
所以我们可以写出这样的代码,对元组进行比较
var left = (5, 10);
var right = (5, 10);
Console.WriteLine(left == right);
此外,元组的比较也支持可空类型的提升转换,以及类型的隐式转换,例如下面代码中可空类型与非空类型的比较,(int,int)和(long,long)之间的比较
var left = (5, 10);
var right = (5, 10);
(int a, int b)? nullableTuple = right;
Console.WriteLine(left == nullableTuple.Value);
Console.WriteLine(left == nullableTuple);
(long a, long b) longTuple = (5, 10);
Console.WriteLine(left == longTuple);
参考链接:
dotnet 职业技术学院 发布于 2019-02-21
当你的编写的是一个多进程的程序的时候,调试起来可能会比较困难,因为 Visual Studio 默认只会把你当前设置的启动项目的启动调试。
本文将介绍几种用 Visual Studio 调试多进程程序的方法,然后给出每种方法的适用条件和优劣。
在 Visual Studio 的解决方案上点击右键,属性。在公共属性节点中选择启动项目。
在这里,你可以给多个项目都设置成启动项目,就像下图这样:
当然,这些项目都必须要是能够启动的才行(不一定是可执行程序)。
此方案的好处是 Visual Studio 原生支持。但此方案的使用必须满足两个前提:
请先安装 Microsoft Child Process Debugging Power Tool 插件。
安装插件后启动 Visual Studio,可以在 Debug -> Other Debugging Targets 中找到 Child Process Debugging Settings。
然后你可以按照下图的设置开启此项目的子进程调试:
但是,子进程要能够调试,你还必须开启混合模式调试,开启方法请参见我的另一篇博客:在 Visual Studio 新旧不同的 csproj 项目格式中启用混合模式调试程序(开启本机代码调试) - walterlv。
现在,你只需要开始调试你的程序,那么你程序中启动的新的子进程都将可以自动加入调试。
现在,我们拿下面这段代码作为例子来尝试子进程的调试。下面的代码中,if
中的代码会运行在子进程中,而 else
中的代码会运行在主进程中。
using System;
using System.Diagnostics;
using System.Linq;
namespace Walterlv.Debugging
{
class Program
{
static void Main(string[] args)
{
if (args.Any())
{
Console.WriteLine("Walterlv child application");
Console.WriteLine(string.Join(Environment.NewLine, args));
Console.ReadLine();
}
else
{
Console.WriteLine("Walterlv main application");
var process = new Process
{
StartInfo = new ProcessStartInfo(Process.GetCurrentProcess().MainModule.FileName, "--child"),
};
process.Start();
process.WaitForExit();
}
}
}
}
我们在 if
和 else
中都打上断点。正常情况下运行,只有 else
中的代码可以进断点;而如果以上子进程调试配置正确,那么两边你都可以进入断点(如下图)。
值得注意的是,只要启动了本机代码调试,就不能在程序暂停之后修改代码了(像平时调试纯托管代码那样)。
调用 Debugger.Launch()
可以启动一个调试器来调试此进程。于是我们可以在我们被调试的程序中写下如下代码:
#if DEBUG
if (!Debugger.IsAttached)
{
Debugger.Launch();
}
#endif
仅在 DEBUG
条件下,如果当前没有附加任何调试器,那么就启动一个新的调试器来调试它。
当存在以上代码时,运行会弹出一个对话框,用于选择调试器。
这里选择的调试器有个不太方便的地方,如果调试器已经在使用,那么就不能选择。对于我们目前的场景,我们的主进程已经在调试了,所以子进程选择调试器的时候不能再选择主进程调试所用的 Visual Studio 了,而只能选择一个新的 Visual Studio;这一点很不方便。
对于此方法,我的建议是平常不要在团队项目中使用(这会让团队中的其他人不方便)。但是由于代码简单不需要配置,所以临时使用的话还是非常建议的。
编写中……
综上,虽然我给出了 4 种不同的方法,但实际上没有任何一种方法能够像我们调试单个原生托管程序那样方便。每一种方法都各有优劣,一般情况下建议你使用我标注了“推荐”的方法;不过也建议针对不同的情况采用不同的方案。
参考资料
dotnet 职业技术学院 发布于 2019-02-20
我找到了一个很久很久以前编写的项目,然而当时是使用 svn 进行版本管理的。然而现在的版本管理全部是 git,不愿意再装一个 svn 工具来管理这些古老的项目,于是打算将其迁移到 git 中。
本文介绍如何将古老的 svn 项目迁移到 git。
如果你能记得你 svn 仓库的 url,或者这个仓库是一个纯本地仓库,那么你直接复制这个 url 就好了。
然而如果这是一个有 svn 远程服务器的仓库,那么你可能依然需要临时安装一下 svn 工具。我们只是为了拿回 url 而已。
这里我使用当时使用的小乌龟 TortoiseSVN。在 svn 仓库空白处右击选择版本库浏览器(Repo-browser),小乌龟会自动定位到当前仓库所在的远程 svn 服务器的对应文件夹。
我们所要做的只有一件事——复制顶部那个 url。
得到了这个 url 后,像我这种洁癖就卸载 TortoiseSVN 了。
在一个新的文件夹中,我们输入如下命令:
git.exe svn clone "https://svn.walterlv.com/LvYi/Timer" ".\Walterlv.RepoFromSvn"
如果那个 svn 目录中包含 trunk
、branches
和 tags
结构,那么可以在后面添加相应的参数以便在 clone 完成后保留分支和标签信息。
git.exe svn clone "https://svn.walterlv.com/LvYi/Timer" ".\Walterlv.RepoFromSvn" -T trunk -b branches -t tags
需要注意的是,上面的 Walterlv.RepoFromSvn
文件夹是不允许提前存在的,如果存在将无法迁移成功。
这里特地照顾一下从 TortoiseSVN 迁移来继续考虑 TortoiseGit 的小伙伴。在 TortoiseGit 中的操作是:
参考资料
dotnet 职业技术学院 发布于 2019-02-19
我们通常得到的命令行参数是一个字符串数组 string[] args
,以至于很多的命令行解析库也是使用数组作为解析的参数来源。
然而如我我们得到了一整个命令行字符串呢?这个时候可能我们原有代码中用于解析命令行的库或者其他辅助函数不能用了。那么如何转换成数组呢?
在 Windows 系统中有函数 CommandLineToArgvW 可以直接将一个字符串转换为命令行参数数组,我们可以直接使用这个函数。
LPWSTR * CommandLineToArgvW(
LPCWSTR lpCmdLine,
int *pNumArgs
);
此函数在 shell32.dll 中,于是我们可以在 C# 中调用此函数。
为了方便使用,我将其封装成了一个静态方法。
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace Walterlv
{
public static class CommandLineExtensions
{
public static string[] ConvertCommandLineToArgs(string commandLine)
{
var argv = CommandLineToArgvW(commandLine, out var argc);
if (argv == IntPtr.Zero)
{
throw new Win32Exception("在转换命令行参数的时候出现了错误。");
}
try
{
var args = new string[argc];
for (var i = 0; i < args.Length; i++)
{
var p = Marshal.ReadIntPtr(argv, i * IntPtr.Size);
args[i] = Marshal.PtrToStringUni(p);
}
return args;
}
finally
{
Marshal.FreeHGlobal(argv);
}
}
[DllImport("shell32.dll", SetLastError = true)]
static extern IntPtr CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, out int pNumArgs);
}
}
参考资料
dotnet 职业技术学院 发布于 2019-02-15
不清楚 git 冲突的表示方法,不了解 git 的合并原理,不知道 git 解冲突的多种策略。即便如此,大多数人依然可以正常使用 git 完成合并、拉取操作,并且解一些冲突。这得益于 git 默认情况下的合并方式可以处理大多数情况下的正常合并。
然而,你是否遭遇 git 自动合并炸掉的情况?命名提示没有冲突,代码却早已无法编译通过。
本文将介绍 git 的合并策略,你可能可以更好的使用不同的策略来解决冲突。
典型的使用指定 git 合并策略的命令这么写:
$ git merge 要合并进来的分支名 --strategy=合并策略
例如:
$ git merge origin/master --strategy=resolve
或者使用简写 -s
,例如:
$ git merge origin/master -s resolve
可以指定的合并策略有:
这使用的是三路合并算法。不过我们在 git 的合并原理(递归三路合并算法) 中说过,普通的三路合并算法会存在发现多个共同祖先的问题。此策略会“仔细地”寻找其中一个共同祖先。
由于不需要递归合并出虚拟节点,所以此方法合并时会比较快速,但也可能会带来更多冲突。不敢说带来更多冲突是好事还是坏事,因为自动合并成功并不一定意味着在代码含义上也算是正确的合并。所以如果自动合并总是成功但代码含义上会失败,可以考虑此合并策略,这将让更多的冲突变成手工合并而不是自动合并。
这是默认的合并策略,如果你不指定策略参数,那么将使用这个合并策略。这将直接使用递归三路合并算法进行合并,详见:git 的合并原理(递归三路合并算法)。
当指定为此策略时,可以额外指定下面的这些参数,方法是:
$ git merge 要合并进来的分支名 --strategy=合并策略 -X diff-algorithm=参数
例如:
$ git merge origin/master -s recursive -X diff-algorithm=patience
由于 recursive
是默认的合并策略,所以可以简化成:
$ git merge origin/master -X diff-algorithm=patience
如果不冲突,那么与默认的合并方式相同。但如果发生冲突,将自动应用自己这一方的修改。
注意策略里面也有一个 ours,与这个不同的。
这与 ours 相反。如果不冲突,那么与默认的合并方式相同。但如果发生冲突,将自动应用来自其他人的修改(也就是 merge 参数中指定的那个分支的修改)。
此策略的名称叫“耐心”,因为 git 将话费更多的时间来进行合并一些看起来不怎么重要的行,合并的结果也更加准确。当然,使用的算法是 recursive 即递归三路合并算法。
不过此名称也难以准确描述到底如何准确,不过可以举一个例子来说明:
int Foo()
{
// 一些省略的代码。
}
int Baz()
{
// 一些省略的代码。
}
然后在这两个函数中增加另一个函数:
int Bar()
{
// 一些省略的代码。
}
默认情况下 git 会认为修改是这样的:
+ }
+
+ int Bar()
+ {
+ // 一些省略的代码。
然而使用 patience
策略后,git 将认为修改是这样的:
+ int Bar()
+ {
+ // 一些省略的代码。
+ }
+
如果你经常合并出现这些括号丢失或者符号不再匹配的问题,可以考虑使用 patience
策略进行合并。
默认情况下 git 会识别出你重命名或者移动了文件,以便在你移动了文件之后依然可以与原文件进行合并。如果指定此策略,那么 git 将不再识别重命名,而是当作增加和删除了文件。
diff-algorithm=[patience|minimal|histogram|myers]
renormalize
no-renormalize
find-renames[=<n>]
rename-threshold=<n>
subtree[=<path>]
又是一个奇怪的名字——章鱼。章鱼有很多的触手,此合并策略就像这么多的触手一样。
此策略允许合并多个 git 提交节点(分支)。不过,如果会出现需要手工解决的冲突,那么此策略将不会执行。
此策略就是用来把多个分支聚集在一起的。
$ git merge t/lvyi t/walterlv -s octopus
error: Merge requires file-level merging
Trying really trivial in-index merge...
Nope.
Merge with strategy octopus failed.
在合并的时候,无论有多少个合并分支,当前分支就直接是最终的合并结果。无论其他人有多少修改,在此次合并之后,都将不存在(当然历史里面还有)。
你可能觉得这种丢失改动的合并策略没有什么用。但如果你准备重新在你的仓库中进行开发(程序员最喜欢的重构),那么当你的修改与旧分支合并时,采用此合并策略就非常有用,你新的重构代码将完全不会被旧分支的改动所影响。
注意 recursive 策略中也有一个 ours 参数,与这个不同的。
此策略使用的是修改后的递归三路合并算法。与 recursive 不同的是,此策略会将合并的两个分支的其中一个视为另一个的子树,就像 git subtree 中使用的子树一样。
参考资料
-X patience
vs -X diff-algorithm=patience
with git merge-recursive
- Stack Overflowdotnet 职业技术学院 发布于 2019-02-14
如果 git 只是一行行比较,然后把不同的行报成冲突,那么你在合并的时候可能会遇到大量的冲突;这显然不是一个好的版本管理工具。
本文介绍 git 合并分支的原理。
例如我们有这样的三个提交 a、b、c。a、b 是在 master 上的其他修改,c 是我自己基于 master 上的 a 的修改。
现在,将 master 分支合并到我自己的 t/walterlv 分支:
a 提交:
Console.WriteLine("Hello World!");
b 提交:
Console.WriteLine("Hello Master!");
c 提交:
Console.WriteLine("Hello Walterlv!");
于是现在将 c 提交合并到 master 的时候就会出现冲突。冲突的表示会是这样:
<<<<<<< HEAD
Console.WriteLine("Hello Walterlv!");
=======
Console.WriteLine("Hello Master!");
>>>>>>> master
以 <<<<<<<
表示冲突开头,>>>>>>>
表示冲突结尾,=======
分隔冲突的不同修改。上面是 HEAD,也就是在合并之前的工作目录上的最近提交;下面是合并进来的分支,通常是来自其他人的修改。
加入上面的 b 提交修改的是其他文件。然后依然按照前面的方式进行合并。
当出现冲突时,如果你只能看到不同的两行,那么你根本不知道究竟应该如何修改的。就像下面这样:
<<<<<<< HEAD
Console.WriteLine("Hello Walterlv!");
=======
Console.WriteLine("Hello World!");
>>>>>>> master
只看这点你怎么知道两行应该采用哪一行?这是二路合并算法带来的问题。在此算法下,你的每次拉取代码可能都会带来大量的冲突;这显然是不能接受的。
三路合并算法会找到合并的这两个提交的共同祖先。在这里也就是 a 提交。master 的此文件对 a 没有修改,而当前分支 t/walterlv 对此文件有修改,于是就会应用此分支的修改。
当然,前一节的问题依然会冲突,因为两个分支相对于共同的祖先节点 a 对同一个文件都有修改。
从上面我们可以看到三路合并解决了二路合并中对于相同行不知道用哪一个的问题。不过实际的 git 提交树会更加复杂,就像下图那样纵横交错:
相比于本文一开始,我们只是新增了两个提交而已,现在 f 提交是我们正在合并的提交。
如果现在找 e 和 d 的共同祖先,你会发现并不唯一,b 和 c 都是。那么此时怎么合并呢?
我们这里的 a、b、c 只是个比较简单的例子,实际上提交树往往更加复杂,这就需要不断重复以上操作以便找到一个真实存在的共同祖先,而这个操作是递归的。这便是“递归三路合并”的含义。
这是 git 合并时默认采用的策略。
git 还有非常简单的快进式(Fast-Forward)合并。快进式合并要求合并的两个分支(或提交)必须是祖孙/父子关系。例如上面的 e 和 d 并不满足此关系,所以无法进行快进式合并。
在上面的例子合并出了 f 之后,如果将 t/walterlv 合并到 master,那么就可以使用快进式合并。这时,直接将 master 分支的 HEAD 指向 f 提交即完成了合并。当然,可以生成也可以不生成新的 g 提交,但内容与 f 的内容完全一样。
参考资料
dotnet 职业技术学院 发布于 2019-01-30
一个不小心,我的 SSD 又满了。到底是谁占用了那么多的空间!如果你是 ReSharper 的重度用户,那么可能你的调查结果会直指 JetBrains ReSharper。
本文将告诉你如何安全地删除这些文件来释放你的 C 盘空间,然后在 ReSharper 中设置其他的缓存目录。
SSD 很贵的,看看都满成什么样儿了……我一个 SSD 分成了 C 和 D 两个分区,都满了。
你可以使用 SpaceSniffer 来快速调查占用你大量 C 盘空间的到底是些什么文件。我之前写过一篇文章介绍如何使用它:
当你是 ReSharper 的重度用户的时候,你很有可能会看到如下的场景:
是的,JetBrains 家的软件竟然占用了 17.2GB 的 C 盘空间!他们一定认为所有的用户都是土豪,能够买 500GB 以上的 SSD 全部分配给 C 盘。
好的,吐槽就到这里,我们进入正题——删除这些文件。
注意:只有 Transient 文件夹是可以删除的!
ReSharper 安装时的目录都在 %LocalAppData%\JetBrains
中。虽然运行时的缓存也在这里,但是如果你直接把这个目录删掉了,那么 ReSharper 插件以及 JetBrains 全家桶也就不能正常使用了。
Transient 意思跟 Temporary 差不多,就是短暂使用的文件。不过 ReSharper 竟然在这里堆了这么多。
删除掉这个文件夹不影响 ReSharper 及其他 JetBrains 全家桶的正常运行。
ReSharper 在设置中提供了清除缓存的按钮,但那个按钮点了其实释放不了多少空间的,本文最后一句将说明这个问题。
在这里可以修改 ReSharper 缓存文件的存储位置。
不过可得提醒你一下,ReSharper 这么耗性能的插件,还是老老实实放 SSD 里面吧,SSD 再怎么贵比起你的时间来说可便宜多了呀!
可以在这个界面中看到,ReSharper 其实是提供了清除缓存的按钮(Clear)的,但是这个按钮点击之后其实只是会删除当前项目的缓存。而实际上 ReSharper 在你的电脑上积攒久了是众多缓存文件一起占用的太多空间,只删除最近正在使用的这个项目其实根本释放不了多少空间的。(比如我打开我的 Walterlv.CloudKeyboard 项目清除结果只删掉了不到 100M 的空间。)
参考资料