代码之家  ›  专栏  ›  技术社区  ›  Shire

可以将多个工作文件夹与TFS一起使用吗?

  •  2
  • Shire  · 技术社区  · 16 年前

    在工作区只有一个工作文件夹的项目中,我的构建脚本工作得很好。现在我正在处理一个需要2个工作文件夹的新项目,我以前脚本的所有签出和签入命令都失败,没有找到任何文件。

    显然,我不理解这里工作区实现的关键部分…我有一个依赖于其他项目的项目,第二个工作文件夹基本上是第三方文件夹,其中引用了编译我的项目所需的各种已发布的dll和headers文件。有2个活动文件夹,本地文件夹为:

    $(SourceDir)\TEAM-MAIN\Address Finalizer
    $(SourceDir)\TEAM-MAIN\HH-CAHPS Project\MAINLINE\3rd Party
    

    生成的代码工作正常,但自定义afterget在以下条目上失败:

    <!-- Check out all of the assemblyInfo files -->
    <Exec Command="$(TfCommand) checkout AssemblyInfo.cs /recursive"
          WorkingDirectory="$(MSBuildProjectDirectory)\..\sources"
          ContinueOnError="false"/>
    

    如果我有一个单独的工作文件夹,并将源文件移动到足够高的位置以获取所有需要的文件,那么这个项目当然可以工作,但是我不想翻阅其他43个项目来获得我想要的,更不用说处理它们的汇编文件了…

    我也尝试过:

    <!-- Check out all of the assemblyInfo files -->
    <Exec Command="$(TfCommand) checkout AssemblyInfo.cs /recursive"
          WorkingDirectory="$(SolutionRoot)"
          ContinueOnError="false"/>
    

    同样的问题,找不到任何程序集文件…我已经检查了生成日志,并且在生成阶段我确实看到了签出的程序集文件…

    Task "Get"
      Get TeamFoundationServerUrl="http://pgpd-team01:8080/" BuildUri="vstfs:///Build/Build/1430" Force=True Overwrite=False PopulateOutput=False Preview=False Recursive=True Version="C7564" Workspace="SBN01P-TFS03_61"
    <snip>
      Getting C:\Users\tfsservice\AppData\Local\Temp\InfoTurn\Address Finalizer\Sources\Address Finalizer\Address Finalizer\Properties\AssemblyInfo.cs;C7525.
    

    如果有人有任何想法或能给我指一篇文章来更好地解释多个工作文件夹是如何工作的,我会很感激的。

    某些生成变量的值:

    MSBuildProjectDirectory: C:\Users\tfsservice\AppData\Local\Temp\InfoTurn\Address Finalizer\BuildType
    
    SolutionRoot: C:\Users\tfsservice\AppData\Local\Temp\InfoTurn\Address Finalizer\Sources
    

    为了提供更多信息,我添加了以下命令:

        <!-- Report what our working folders are -->
        <Exec 
          Command='$(TfCommand) workfold'
          WorkingDirectory="$(SolutionRoot)\TEAM-MAIN\Address Finalizer"/>
    

    结果是:

    Task "Exec"
      Command:
      "C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies\..\tf.exe" workfold
      ===============================================================================
      Workspace: SBN01P-TFS03_61 (tfsservice)
      Server   : http://pgpd-team01:8080/
       $/InfoTurn/TEAM-MAIN/Address Finalizer: C:\Users\tfsservice\AppData\Local\Temp\InfoTurn\Address Finalizer\Sources\TEAM-MAIN\Address Finalizer
       $/InfoTurn/TEAM-MAIN/HH-CAHPS Project/MAINLINE/3rd Party: C:\Users\tfsservice\AppData\Local\Temp\InfoTurn\Address Finalizer\Sources\TEAM-MAIN\HH-CAHPS Project\MAINLINE\3rd Party
    

    我发现以下工作目录可以工作:

    WorkingDirectory="$(SolutionRoot)\TEAM-MAIN\Address Finalizer"
    

    但是下面两个没有,注意第二个是我的第二个工作文件夹:

    WorkingDirectory="$(SolutionRoot)"
    WorkingDirectory="$(SolutionRoot)\TEAM-MAIN\HH-CAHPS Project\MAINLINE\3rd Party"
    

    我为标签任务得到的错误是最有用的:

    Using "Label" task from assembly "C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies\Microsoft.TeamFoundation.Build.Tasks.VersionControl.dll".
    Task "Label"
      Label TeamFoundationServerUrl="http://pgpd-team01:8080/" BuildUri="vstfs:///Build/Build/1507" Name="Address Finalizer 2.0.1 Build 039" Recursive=True Comments="Automated build: Address Finalizer 2.0.1 Build 039" Version="W" Child="replace" Files="C:\Users\tfsservice\AppData\Local\Temp\InfoTurn\Address Finalizer\Sources"
    C:\Users\tfsservice\AppData\Local\Temp\InfoTurn\Address Finalizer\BuildType\TFSBuild.proj(310,5,310,5): error : Error: Unable to determine the workspace.
    

    签出的实际错误是:

    Task "Exec"
      Command:
      "C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies\..\tf.exe" checkout AssemblyInfo.cs /recursive
      No matching items found in C:\Users\tfsservice\AppData\Local\Temp\InfoTurn\Address Finalizer\Sources\AssemblyInfo.cs in your workspace.
    C:\Users\tfsservice\AppData\Local\Temp\InfoTurn\Address Finalizer\BuildType\TFSBuild.proj(280,5): error MSB3073: The command ""C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies\..\tf.exe" checkout AssemblyInfo.cs /recursive" exited with code 1.
    
    2 回复  |  直到 16 年前
        1
  •  2
  •   Richard Berg    16 年前

    可以同时使用不相交的工作区映射+递归。你得小心点。

    这将获取foo&bar中的所有c_文件:

    $/project/dir1/foo -> c:\code\foo
    $/project/dir2/bar -> c:\code\bar
    
    tf get c:\code\*.cs -recursive
    

    这通常在foo&bar下获取所有内容,但如果工作目录无法明确解析为工作区,则会失败:

    $/project/foo -> c:\code1\foo
    $/project/bar -> c:\code2\bar
    
    tf get $/project/* -recursive
    

    这可能有效(同样的警告如上),但它可能不会达到你期望的效果!

    $/project/foo -> c:\code1\foo
    $/project/bar -> c:\code2\bar
    
    tf get c:\code* -recursive
    

    还有更多的变化;行为取决于工作区的定义方式,以及同一台机器上具有类似本地路径的其他工作区的存在。你能准确地说出你的构建定义吗?您是否可以添加一些调试语句来验证在执行目标时,哪些$(solutionRoot)和其他msbuild属性是相等的?

    编辑/

    如我所说,不相交的映射+递归很难纠正。结果发现,行为不仅随文件规范和工作区定义的细微细节而变化,而且随命令的不同而变化!与我的第一个例子不同,这失败了:

    $/project/dir1/foo -> c:\code\foo
    $/project/dir2/bar -> c:\code\bar
    
    tf checkout c:\code\*.cs -recursive
    

    迷惑了吗?我是,我在TFS团队工作了几年!

    让我从各种评论主题中总结我的建议:

    • 要格外小心。在将任何内容放入msbuild脚本之前,请先登录到生成计算机并测试它!交互式测试时,您将获得更好的错误消息和更快的反馈。这很重要,因为你要做的事情很脆弱。将脚本转换为面值,下面是一些可以让您开始的更正:
      • 签出任务应使用服务器路径以获得最佳结果。一旦解决了这个问题,您可以从$(solutionroot)\team-main\address finalizer或$(solutionroot)\team-main\hh-cahps project\mainline\3rd party运行它,不会有任何区别。[不是普通的旧$(solutionroot),不幸的是--如上所示,签出不如get聪明]使用本地项规范时,它似乎在地址终结器中工作的原因是因为在该本地目录下实际上有一些匹配的文件。您可能在第三方下没有任何assemblyinfo.cs文件。相反,使用诸如$/infoturn/team-main/assemblyinfo.cs之类的服务器路径将扩大范围,以便tfs搜索所有team-main以查找要签出的匹配文件。您仍然需要从一个明确确定工作区的工作目录中运行它,或者同时指定/server和/workspace选项,这样tfs就可以将查询限制在工作区中的实际项目上,并知道将它们签出给谁。
      • 同样,您编写的标签任务需要使用直接映射文件夹中的服务器路径+运行。或者可以使用完全限定的版本规范(wwsname;domain\wsowner)而不是缩写(w)。
    • 还是更好 ,首先不要使用不相交的工作空间。看看所有被映射的本地路径:它们的共同祖先本身应该是一个映射的文件夹。否则(a)从中运行的命令将不明确,需要额外的参数,和/或根本不起作用(b)从单独映射的子文件夹中运行的命令不会在整个工作区中查询[除非使用服务器路径]。为了避免所有这些,选择一个映射作为“根”,然后确保所有后续映射都指向它下面的某个地方。一旦你建立了一个单一的、统一的根,事情就会变得容易得多。tf命令从其内部的任何位置运行都将更具可预测性(对新手和专家都是如此!)。有几种方法可以设置符合您目标的统一工作区:

      • 切换回一个大映射,然后隐藏不需要的文件夹。请注意,您可以在斗篷下创建正映射(例如,斗篷$/infoturn/team-main/hh-cahps项目,同时仍然映射$/infoturn/team-main/hh-cahps项目/主线/第三方)。这样你就有了一个干净的工作空间,没有任何奇怪的副作用。缺点是所涉及的工作量。如果有大量的文件夹,您可以使用脚本来创建斗篷,但即使如此,在添加新文件夹或重命名现有文件夹时,仍然存在使它们保持最新的问题。
      • 找到当前映射的父文件夹,并创建到该文件夹的一级映射。例如,可以创建一个一级映射$/infoturn/team-main/*-> $(solutionroot)\team-main。这将下拉team-main的所有直接子级,但不会再进行更深层次的递归,除了已经用完全递归添加的两个文件夹。缺点是有一些额外的文件需要下载并贴上标签。
      • 更改当前映射,使一个文件夹映射到另一个文件夹的下面。例如,可以映射$/infoturn/team-main/address finalizer->$(solutionroot)和$/infoturn/team-main/hh-cahps project/mainline/3rd party->$(solutionroot)\3rd party。现在$(solutionRoot)是您所需要的所有代码的统一根。这个解决方案的问题是,它可能会破坏任何依赖于相对路径的生成文件,这些路径在您的构建配置和其他尝试使用时髦映射构建地址终结器w/o之间保持不变。
      • 将第三方文件夹移到源代码管理结构中更自然的位置。毕竟,您可能不是在TeamMain中工作的唯一需要参考第三方组件的人。如果大家都同意在根目录下保留共享资源并适当地构建makefile,那么将深层路径映射到工作区中的大部分问题就会消失。
    • 更棒的是 ,在生成过程中不签出/签入文件。说真的。对于人类来说,很容易处理的常见情况(如锁、版本冲突)是在所有可能的边缘情况下实现自动化的噩梦。如果你尝试使用持续整合,上帝会帮助你…同时,您所有重要的构建都在中断,而您调试的功能,我真的没有看到任何好处。而是:

      • 将所有系统范围的程序集信息(包括生成编号)合并到一个assembly info.cs文件中。
      • 从每个项目导入的常用msbuild*.targets文件中引用此c文件。(如果您还没有重构项目级任务,那么这是一个很好的机会)。
      • 让team build根据需要修改这个c文件。例如,您可以在corecompile任务之前随时填写内部版本号。请注意,我们只是在重写磁盘上的内容,而不涉及源代码管理。
      • 如果您希望您的桌面构建也有增量编号(我个人看不到这一点),请向您的公共*.targets文件添加一个类似的任务,条件是将其从团队构建中排除。
        2
  •  1
  •   Shire    16 年前

    我的脚本现在正在工作,有几个小问题导致了这个问题。一个是对于许多命令,您必须引用精确的工作目录(本地文件夹),以便命令工作。另一个问题是设置属性需要移动到自定义任务中,以便另一个任务能够正确使用它们。所以希望下面的完整构建脚本能够帮助其他人。在任何编辑器中粘贴它都会使阅读更容易。

      <!-- Generate all of the project specific variables we will need for versioning -->
      <PropertyGroup>
        <VersioningFile>$(SolutionRoot)\TEAM-MAIN\Address Finalizer\Address Finalizer\versionNumber.txt</VersioningFile>
        <TfCommand>"$(TeamBuildRefPath)\..\tf.exe"</TfCommand>
        <WorkingDirectory>$(SolutionRoot)\TEAM-MAIN\Address Finalizer</WorkingDirectory>
        <WorkingDirectory2>$(SolutionRoot)\TEAM-MAIN\HH-CAHPS Project\MAINLINE\3rd Party</WorkingDirectory2>
        <DllName>Address_Finalizer.dll</DllName>
        <PublishedFolder>$(SolutionRoot)\TEAM-MAIN\HH-CAHPS Project\MAINLINE\3rd Party\bin\PG File Import</PublishedFolder>
        <LabelPath>unknown</LabelPath>
      </PropertyGroup>
    
      <!-- Only edit the properties when usign this as a template for other builds -->
    
      <Import Project="$(MSBuildExtensionsPath)\ExtensionPack\MSBuild.ExtensionPack.tasks"/>
      <Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
    
      <Target Name="AfterGet">   
        <Message Text="FOOBAR Version File: $(VersioningFile)" />
        <Message Text="FOOBAR Working Dir : $(WorkingDirectory)" />
    
        <!-- Report what our working folders are -->
        <Exec
          Command='$(TfCommand) workfold'
          WorkingDirectory="$(WorkingDirectory)"/>
    
        <!-- Generate the version # -->
        <CallTarget Targets="GenerateVersion"/>
    
        <!-- Version the assemblies -->
        <CallTarget Targets="VersionAssemblies"/>      
    
        <!-- Configure the deployment for QA -->
        <CallTarget Targets="ConfigureForQA"/>
    
        <!-- Apply a label to everything -->
        <CallTarget Targets="LabelWorkingDirectory"/>
        <CallTarget Targets="LabelWorkingDirectory2"/>
      </Target>
    
      <Target Name="AfterDropBuild">
        <!-- Generate all of the project specific variables we will need for deployment, dependant on versioning -->
        <PropertyGroup>
          <ReleaseDLL>$(DropLocation)\$(BuildNumber)\Release\$(DllName)</ReleaseDLL>
          <PublishedDLL>$(PublishedFolder)\$(DllName)</PublishedDLL>
        </PropertyGroup>
    
        <!-- Check out the published DLL -->
        <Exec 
          Command='$(TfCommand) checkout /lock:checkout "$(PublishedDLL)"'
          WorkingDirectory="$(WorkingDirectory)" />
    
        <!-- Copy release to published -->
        <Copy 
          SourceFiles="$(ReleaseDLL)" 
          DestinationFolder="$(PublishedFolder)"/>
    
        <!-- Check in the published DLL -->
        <Exec 
          Command='$(TfCommand) checkin /override:Automated /noprompt /comment:"$(VersionComment)" "$(PublishedDLL)"'
          WorkingDirectory="$(WorkingDirectory)" />
      </Target>
    
      <Target Name="GenerateVersion">
        <!-- Check out the version file -->
        <Exec
          Command='$(TfCommand) checkout /lock:checkout "$(VersioningFile)"'
          WorkingDirectory="$(SolutionRoot)"/>
    
        <!-- Increment the revision # in the file and save it -->
        <Version VersionFile="$(VersioningFile)" BuildType="None" RevisionType="Increment">
          <Output TaskParameter="Major" PropertyName="Major" />
          <Output TaskParameter="Minor" PropertyName="Minor" />
          <Output TaskParameter="Build" PropertyName="Build" />
          <Output TaskParameter="Revision" PropertyName="Revision" />
        </Version>
    
        <!-- Pad the revision out to 3 digits. -->
        <PropertyGroup>
          <PaddedRevision Condition="$(Revision) < 1000">$(Revision)</PaddedRevision>
          <PaddedRevision Condition="$(Revision) < 100">0$(Revision)</PaddedRevision>
          <PaddedRevision Condition="$(Revision) < 10">00$(Revision)</PaddedRevision>
        </PropertyGroup>
    
        <!-- Generate what our version string looks like for the logfile -->
        <CreateProperty Value="Address Finalizer $(Major).$(Minor).$(Build) Build $(PaddedRevision)">
          <Output TaskParameter="Value" PropertyName="ProjectVersion" />
        </CreateProperty>
    
        <CreateProperty Value="Automated build: $(ProjectVersion)">
          <Output TaskParameter="Value" PropertyName="VersionComment" />
        </CreateProperty>
    
        <Message Text="New Version is: $(ProjectVersion)"/>
    
        <!-- Check in the version file -->
        <Exec
          Command='$(TfCommand) checkin /noprompt /comment:"$(VersionComment)" "$(VersioningFile)"'
          WorkingDirectory="$(SolutionRoot)"/>
      </Target>
    
      <Target Name="VersionAssemblies">
        <!-- Get all of the assemblyinfo files from all of the directories (should only be one) -->
        <ItemGroup>
          <AssemblyInfoList Include="$(SolutionRoot)\**\assemblyinfo.cs"/>
        </ItemGroup>
    
        <Message Text="FOOBAR Build Dir    : $(MSBuildProjectDirectory)"/>
        <Message Text="FOOBAR Solution Root: $(SolutionRoot)"/>
        <Message Text="FOOBAR Working Dir  : $(WorkingDirectory)"/>
    
        <!-- Check out all of the assemblyInfo files -->
        <Exec 
          Command="$(TfCommand) checkout AssemblyInfo.cs /recursive"
          WorkingDirectory="$(WorkingDirectory)"
          ContinueOnError="false"/>
    
        <!-- Change the version # on all of the assemblyInfo files -->
        <MSBuild.ExtensionPack.Framework.AssemblyInfo AssemblyFileVersion="$(Major).$(Minor).$(Build).$(Revision)" AssemblyInfoFiles="@(AssemblyInfoList)"/>
        <MSBuild.ExtensionPack.Framework.AssemblyInfo AssemblyVersion="$(Major).$(Minor).$(Build).$(Revision)" AssemblyInfoFiles="@(AssemblyInfoList)"/>
    
        <!-- Check in all of the assemblyInfo files -->
        <Exec 
          Command='$(TfCommand) checkin /override:Automated /comment:"$(VersionComment)" /noprompt AssemblyInfo.cs /recursive'
          WorkingDirectory="$(WorkingDirectory)"
          ContinueOnError="false"/> 
      </Target>
    
      <Target Name="ConfigureForQA">
        <!-- Grab all of the QA app.config files (should only be one) -->
        <CreateItem Include="$(SolutionRoot)\**\app-qa.config">
          <Output TaskParameter ="Include" ItemName ="AppConfigFiles"/>
        </CreateItem>
    
        <!-- Copy the QA app.config to app.config to make it the driving config file -->
        <Copy
          SourceFiles="@(AppConfigFiles)"
          OverwriteReadOnlyFiles="True"
          DestinationFiles="@(AppConfigFiles->'$(SolutionRoot)\%(RecursiveDir)app%(Extension)')"/>
      </Target>
    
      <Target Name="LabelWorkingDirectory">
        <Message Text="FOOBAR Version: $(ProjectVersion)" />
        <Message Text="FOOBAR Comment: $(VersionComment)" />
        <Message Text="FOOBAR Path   : $(LabelPath)" />
    
        <!-- Apply Label on workspace -->
        <Label
          TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
          BuildUri="$(BuildUri)"
          Name="$(ProjectVersion)"
          Files="$(WorkingDirectory)"
          Comments="$(VersionComment)"
          ContinueOnError="False"
          Recursive="True" />
      </Target>
    
      <Target Name="LabelWorkingDirectory2">
        <Message Text="FOOBAR Version: $(ProjectVersion)" />
        <Message Text="FOOBAR Comment: $(VersionComment)" />
        <Message Text="FOOBAR Path   : $(LabelPath)" />
    
        <!-- Apply Label on workspace -->
        <Label
          TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
          BuildUri="$(BuildUri)"
          Name="$(ProjectVersion)"
          Files="$(WorkingDirectory2)"
          Comments="$(VersionComment)"
          ContinueOnError="False"
          Recursive="True" />
      </Target>