要以适合大多数人的方式在 msbuild 脚本中正确引用 sn 或 sqlmetal(我所追求的)等工具,您必须考虑操作环境和框架的不同方面执行。主要有两种情况:Microsoft Windows 和 Microsoft 对框架的实现,然后是其他一切(我指的是 Mono/unix)。最后列出了支持我能想到的情况的正确方法的示例。
微软
找到sn 或其他类似工具在Windows 中的位置的正确方法是以GetFrameworkSdkPath task 开头,如already mentioned。
但是,正如问题所暗示的那样,无法直接确定 sn 或其他工具在 FrameworkSdkPath 中的确切位置。引用的答案表明,FrameworkSdkPath 下唯一可能的文件夹是工具所在的 bin 和 bin/NETFX 4.0 Tools。但是,其他值也是可能的(Visual Studio 2013 Preview 使用 bin/NETFX 4.5.1 Tools)。因此,搜索 sn 的唯一正确方法是使用 glob 表达式或递归搜索它。我无法弄清楚如何使用 MSBuild 进行全局扩展,并且内置的 MSBuild 任务似乎不支持在 FrameworkSdkPath 下搜索特定实用程序。但是,cmd 的WHERE 有这个功能,可以用来做搜索。结果类似于以下 msbuild 代码:
<Target Name="GetSNPath" BeforeTargets="AfterBuild">
<GetFrameworkSdkPath>
<Output TaskParameter="Path" PropertyName="WindowsSdkPath" />
</GetFrameworkSdkPath>
<Exec Command="WHERE /r "$(WindowsSdkPath.TrimEnd('\\'))" sn > sn-path.txt" />
<ReadLinesFromFile File="sn-path.txt">
<Output TaskParameter="Lines" PropertyName="SNPath"/>
</ReadLinesFromFile>
<Delete Files="sn-path.txt" />
<PropertyGroup>
<SNPath>$([System.Text.RegularExpressions.Regex]::Replace('$(SNPath)', ';.*', ''))</SNPath>
</PropertyGroup>
</Target>
(请参阅 Property Functions 了解为什么我可以在此处使用 String.TrimEnd。WHERE 不喜欢尾部斜杠。编辑:我添加了使用属性函数来访问 Regex.Replace() 以删除除第一个找到的路径之外的所有路径在SNPath 属性中。我朋友的一台机器的WHERE 调用将为某些命令输出多个结果,并破坏了对喜欢的工具<Exec/> 的任何尝试。此更改确保只找到一个结果并且<Exec/>s实际上成功了。)
现在您可以使用<Exec Command="&quot;$(SNPath)&quot;" /> 调用sn。
便携
不出所料,在 Windows 以外的任何操作系统上解析到 sn 的路径都要简单得多。在 Mac OSX 和任何 Linux 发行版上,我在 PATH 中找到 sn。在这种情况下使用 GetFrameworkSdkPath 并没有帮助;事实上,这似乎返回了一条无法找到sn 的路径,至少对于我在使用 xbuild 时测试的 mono-2.10 的旧版本而言:
- 在 Mac OSX 上,
FrameworkSdkPath 是 /Library/Frameworks/Mono.framework/Versions/2.10.5/lib/mono/2.0,/usr/bin/sn 是指向 /Library/Frameworks/Mono.framework/Commands/sn 的符号链接。
- 在某个 Linux 安装中,
FrameworkSdkPath 是 /usr/lib64/mono/2.0,sn 是 /usr/bin/sn(这是一个使用 mono 调用 /usr/lib64/mono/4.0/sn.exe 的 shell 脚本)。
因此,我们需要做的就是尝试执行sn。任何将他们的sn 实现放在非标准位置的 unix 用户都已经知道要适当地更新 PATH,因此构建脚本不需要搜索它。此外,WHERE 在 unix 中不存在。因此,在 unix 案例中,我们希望将第一个 <Exec/> 调用替换为在 unix 上仅输出 sn 并且在 Windows 上运行时仍执行完整搜索的内容。为了区分类 unix 和 Windows 环境,我们使用了一个技巧,该技巧利用了 unix shell 的 true 命令和 cmd 标签语法的快捷方式。作为一个简短的示例,以下脚本将在 unix shellout 中输出 I’m unix!,在 Windows shellout 中输出 I’m Windows :-/。
:; echo 'I’m unix!'; exit $?
echo I’m Windows :-/
利用这一点,我们生成的 GetSNPath 任务看起来像:
<Target Name="GetSNPath" BeforeTargets="AfterBuild">
<GetFrameworkSdkPath>
<Output TaskParameter="Path" PropertyName="WindowsSdkPath" />
</GetFrameworkSdkPath>
<Exec Command=":; echo sn > sn-path.txt; exit $?
WHERE /r "$(WindowsSdkPath.TrimEnd('\\'))" sn > sn-path.txt" />
<ReadLinesFromFile File="sn-path.txt">
<Output TaskParameter="Lines" PropertyName="SNPath"/>
</ReadLinesFromFile>
<Delete Files="sn-path.txt" />
<PropertyGroup>
<SNPath>$([System.Text.RegularExpressions.Regex]::Replace('$(SNPath)', ';.*', ''))</SNPath>
</PropertyGroup>
</Target>
结果是一种可移植的方法,用于查找调用sn 所需的字符串。最后一个解决方案让您支持 Microsoft 及其 msbuild 以及使用 xbuild 的所有其他平台。它还克服了将bin\NETFX 4.0 Tools 硬编码到 .csproj 文件中的问题,以同时支持未来和当前版本的 Microsoft 工具。