在 Visual Studio 中的 MSBuild 社区任务 BeforeBuild 步骤期间,在路径中找不到 xsd.exe
Can't find xsd.exe in the path during MSBuild Community Tasks BeforeBuild step in Visual Studio
我正在使用 MSBuild 社区任务 运行 Xsd.exe 作为我在 Visual Studio 中构建的一部分,因此:
<Import Project="$(SolutionDir)Common.targets" />
<PropertyGroup>
<MSBuildCommunityTasksPath>$(SolutionDir)TeamBuildTypes</MSBuildCommunityTasksPath>
</PropertyGroup>
<Import Project="$(MSBuildCommunityTasksPath)\MSBuild.Community.Tasks.Targets" />
<UsingTask TaskName="XSD" AssemblyFile="$(VCTargetsPath)$(CPPTasks)" />
<Target Name="BeforeBuild">
<!--Exec Command="'$(DevEnvDir)..\Tools\vsvars32.bat'" /-->
<XSD Sources="MySchema.xsd" GenerateFromSchema="classes" Language="CS" />
</Target>
Common.targets 根据 here.
但是,我在构建过程中遇到以下错误:
The "XSD" task failed unexpectedly.
Microsoft.Build.Shared.InternalErrorException: MSB0001: Internal MSBuild Error: xsd.exe unexpectedly not a rooted path
MSDN论坛here上已经给出了一个解决方案,就是在路径xsd.exe中添加一个路径环境变量。但是,正如 Pico Ohms 的回答所表明的那样,这是不可维护的,因为它依赖于版本并且需要每个开发人员执行额外的步骤才能构建,只是因为 Xsd.exe.
我找到了另一个解决方案 here which is to call vsvars32.bat beforehand. This is the commented-out line in the code above. This didn't work, so I found a solution here,我希望它能起作用,即使用 /useenv
参数调用 DEVENV。
然后我找到了另一个解决方案here,就是在Microsoft.Build.xsd上加上<xs:include schemaLocation="MSBuild\MSBuild.Community.Tasks.xsd"/>
。也没用。
所以,现在我没主意了。如何通过使用 /useenv
解决方案或其他一些解决方案,让 MSBuild Community Tasks XSD 任务在不需要开发人员更新其 path 变量的情况下工作?我知道我可以将 xsd.exe 放在 Visual Studio 解决方案中,但这对我来说似乎是一个廉价的解决方法。
我不得不做一些研究来记住我在另一个线程上发布的关于此的 comment,但是您可以通过从 XSD 任务派生来为您的任务实现自定义搜索路径,并且覆盖 GenerateFullPathToTool() 方法。
当您查看 DotPeek 中的 XSD 任务时,您会发现它派生自 Microsoft.Build.Utilities.ToolTask。 ToolTask 包含一个名为 "GenerateFullPathToTool()" 的方法。顾名思义,调用此方法是为了 return 工具的完整路径。
Microsoft.Build.Utilities class 中的 ToolLocationHelper 可用于为您的安装 xsd.exe 找到合适的位置。
它包含一个名为 GetPathToDotNetFrameworkSdkFile 的方法,它将为您提供 dot net sdk 的文件名位置。将 "xsd.exe" 作为参数,你应该可以开始了。
如果该方法没有 return 您需要的路径,还有其他方法可以 return 获取各种路径及其基于注册表的方法,因此它比显式设置更便携小路。
我多年来一直使用的解决方案是使用 inline task 在项目文件中设置路径。
<ItemGroup>
<!-- Configure XSD files by using the XsdClasses custom item type. All files of this type are converted by the GenerateXsdClasses target. -->
<XsdClasses Include="Data\XsdFile1.xsd">
<SubType>Designer</SubType>
</XsdClasses>
<XsdClasses Include="Data\XsdFile2.xsd">
<SubType>Designer</SubType>
</XsdClasses>
</ItemGroup>
<!-- In-line task to add a folder to the current path if not already in it. -->
<UsingTask TaskName="AddToPath" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<FolderPath ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="c#"><![CDATA[
const string PathEnvironmentVariableName = "PATH";
string currentPath = Environment.GetEnvironmentVariable(PathEnvironmentVariableName);
if(!currentPath.ToLower().Contains(FolderPath.ToLower()))
{
currentPath = currentPath.TrimEnd(';');
string newPath = string.Format("{0};{1}", currentPath, FolderPath);
Environment.SetEnvironmentVariable(PathEnvironmentVariableName, newPath);
}
]]></Code>
</Task>
</UsingTask>
<!-- Reference the Xsd task. -->
<UsingTask TaskName="XSD" AssemblyFile="$(VCTargetsPath)Microsoft.Build.CppTasks.Common.dll" />
<!-- Set the default path to Xsd.exe - this will be added to the current path using the above inline task. Can be overridden per client by setting a user or machine environment variable with the same name. -->
<PropertyGroup>
<NetFxToolsPath Condition="'$(NetFxToolsPath)' == ''">C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.7 Tools\</NetFxToolsPath>
</PropertyGroup>
<!-- Generate classes from xsd files configured with 'XsdClasses' item type. -->
<Target Name="GenerateXsdClasses" Inputs="@(XsdClasses)" Outputs="@(XsdClasses->'%(RelativeDir)%(Filename).cs')">
<Message Importance="high" Text="Building %(XsdClasses.Filename)" />
<AddToPath FolderPath="$(NetFxToolsPath)" />
<!-- Note the trailing . in the outputdir because the folder ends with a / -->
<XSD MinimalRebuildFromTracking="true" Sources="%(XsdClasses.Identity)" GenerateFromSchema="classes" Namespace="XsdClassesNamespace" SuppressStartupBanner="true" AdditionalOptions="/outputdir:"%(XsdClasses.RelativeDir)."" />
</Target>
<!-- Configure class generation from xsd to occur before build. -->
<Target Name="BeforeBuild" DependsOnTargets="GenerateXsdClasses"/>
我正在使用 MSBuild 社区任务 运行 Xsd.exe 作为我在 Visual Studio 中构建的一部分,因此:
<Import Project="$(SolutionDir)Common.targets" />
<PropertyGroup>
<MSBuildCommunityTasksPath>$(SolutionDir)TeamBuildTypes</MSBuildCommunityTasksPath>
</PropertyGroup>
<Import Project="$(MSBuildCommunityTasksPath)\MSBuild.Community.Tasks.Targets" />
<UsingTask TaskName="XSD" AssemblyFile="$(VCTargetsPath)$(CPPTasks)" />
<Target Name="BeforeBuild">
<!--Exec Command="'$(DevEnvDir)..\Tools\vsvars32.bat'" /-->
<XSD Sources="MySchema.xsd" GenerateFromSchema="classes" Language="CS" />
</Target>
Common.targets 根据 here.
但是,我在构建过程中遇到以下错误:
The "XSD" task failed unexpectedly.
Microsoft.Build.Shared.InternalErrorException: MSB0001: Internal MSBuild Error: xsd.exe unexpectedly not a rooted path
MSDN论坛here上已经给出了一个解决方案,就是在路径xsd.exe中添加一个路径环境变量。但是,正如 Pico Ohms 的回答所表明的那样,这是不可维护的,因为它依赖于版本并且需要每个开发人员执行额外的步骤才能构建,只是因为 Xsd.exe.
我找到了另一个解决方案 here which is to call vsvars32.bat beforehand. This is the commented-out line in the code above. This didn't work, so I found a solution here,我希望它能起作用,即使用 /useenv
参数调用 DEVENV。
然后我找到了另一个解决方案here,就是在Microsoft.Build.xsd上加上<xs:include schemaLocation="MSBuild\MSBuild.Community.Tasks.xsd"/>
。也没用。
所以,现在我没主意了。如何通过使用 /useenv
解决方案或其他一些解决方案,让 MSBuild Community Tasks XSD 任务在不需要开发人员更新其 path 变量的情况下工作?我知道我可以将 xsd.exe 放在 Visual Studio 解决方案中,但这对我来说似乎是一个廉价的解决方法。
我不得不做一些研究来记住我在另一个线程上发布的关于此的 comment,但是您可以通过从 XSD 任务派生来为您的任务实现自定义搜索路径,并且覆盖 GenerateFullPathToTool() 方法。
当您查看 DotPeek 中的 XSD 任务时,您会发现它派生自 Microsoft.Build.Utilities.ToolTask。 ToolTask 包含一个名为 "GenerateFullPathToTool()" 的方法。顾名思义,调用此方法是为了 return 工具的完整路径。
Microsoft.Build.Utilities class 中的 ToolLocationHelper 可用于为您的安装 xsd.exe 找到合适的位置。
它包含一个名为 GetPathToDotNetFrameworkSdkFile 的方法,它将为您提供 dot net sdk 的文件名位置。将 "xsd.exe" 作为参数,你应该可以开始了。
如果该方法没有 return 您需要的路径,还有其他方法可以 return 获取各种路径及其基于注册表的方法,因此它比显式设置更便携小路。
我多年来一直使用的解决方案是使用 inline task 在项目文件中设置路径。
<ItemGroup>
<!-- Configure XSD files by using the XsdClasses custom item type. All files of this type are converted by the GenerateXsdClasses target. -->
<XsdClasses Include="Data\XsdFile1.xsd">
<SubType>Designer</SubType>
</XsdClasses>
<XsdClasses Include="Data\XsdFile2.xsd">
<SubType>Designer</SubType>
</XsdClasses>
</ItemGroup>
<!-- In-line task to add a folder to the current path if not already in it. -->
<UsingTask TaskName="AddToPath" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<FolderPath ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="c#"><![CDATA[
const string PathEnvironmentVariableName = "PATH";
string currentPath = Environment.GetEnvironmentVariable(PathEnvironmentVariableName);
if(!currentPath.ToLower().Contains(FolderPath.ToLower()))
{
currentPath = currentPath.TrimEnd(';');
string newPath = string.Format("{0};{1}", currentPath, FolderPath);
Environment.SetEnvironmentVariable(PathEnvironmentVariableName, newPath);
}
]]></Code>
</Task>
</UsingTask>
<!-- Reference the Xsd task. -->
<UsingTask TaskName="XSD" AssemblyFile="$(VCTargetsPath)Microsoft.Build.CppTasks.Common.dll" />
<!-- Set the default path to Xsd.exe - this will be added to the current path using the above inline task. Can be overridden per client by setting a user or machine environment variable with the same name. -->
<PropertyGroup>
<NetFxToolsPath Condition="'$(NetFxToolsPath)' == ''">C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.7 Tools\</NetFxToolsPath>
</PropertyGroup>
<!-- Generate classes from xsd files configured with 'XsdClasses' item type. -->
<Target Name="GenerateXsdClasses" Inputs="@(XsdClasses)" Outputs="@(XsdClasses->'%(RelativeDir)%(Filename).cs')">
<Message Importance="high" Text="Building %(XsdClasses.Filename)" />
<AddToPath FolderPath="$(NetFxToolsPath)" />
<!-- Note the trailing . in the outputdir because the folder ends with a / -->
<XSD MinimalRebuildFromTracking="true" Sources="%(XsdClasses.Identity)" GenerateFromSchema="classes" Namespace="XsdClassesNamespace" SuppressStartupBanner="true" AdditionalOptions="/outputdir:"%(XsdClasses.RelativeDir)."" />
</Target>
<!-- Configure class generation from xsd to occur before build. -->
<Target Name="BeforeBuild" DependsOnTargets="GenerateXsdClasses"/>