【问题标题】:Can a single TFS 2010 build definition be used for multiple branches?单个 TFS 2010 构建定义可以用于多个分支吗?
【发布时间】:2023-03-10 22:41:01
【问题描述】:

我负责我们产品的构建,并被要求想出一种自定义现有构建定义的方法,以便在需要时构建不同的分支。

该产品的构建过程已经有几个自定义步骤和操作,并且该产品有大量的项目文件要构建,因此为每个创建的新分支设置新的构建定义是无效的。

构建定义设置为从主分支构建。目的是进入一个特定的分支(使用可以在构建排队时输入的工作流参数),然后将构建该分支而不是默认的 Main 分支,而无需编辑构建定义。

我有一个单独的测试程序,用于测试我的所有自定义构建活动和程序。在此构建定义的工作流程中,我添加了相当多的构建消息用于记录目的,以便我可以查看构建过程中使用的变量的值。

我还基于这个测试程序创建了一个分支,准备好测试一个可用于构建多个分支的构建定义

首先,我从原始分支为原始测试解决方案的项目文件运行构建,然后更改构建定义,以便使用新分支完成相同操作并运行另一个构建。在比较 2 个分支之间的构建日志时,它们之间只有一些细微差别。 (记录详细程度设置为诊断)

第一个区别 - 我查看了 Workspace 变量,并且构建的 Folders 属性引用了它们各自的分支,特别是 Folders 属性的 ServerItem 属性

第二个区别 - 正在构建的项目文件 (BuildSettings.ProjectsToBuild) 来自各自的分支

除了这些之外,我没有看到 2 个构建日志之间的任何其他差异

这里的主要问题

是否有一种标准方法可以将正在构建的分支交换为单个构建定义?

如果没有,是否可以在排队构建时简单地将自定义工作流模板(在 Workspace 和 BuildSettings.ProjectsToBuild 中)中对默认 Main 分支的所有引用更改为输入的分支?

一如既往,在此先感谢您提供的所有帮助

【问题讨论】:

    标签: msbuild build


    【解决方案1】:

    我已经设法修改了我的构建工作流模板,以便可以为解决方案的多个分支构建。为此,我必须更改工作流中的某些内容,并为运行构建的服务帐户创建一些额外的工作区。

    1. 我在工作流中添加了一个参数,以便在构建排队时可以输入所需的分支。

    2. 我更改了构建的放置位置,使其与输入的分支相关(可选)

    3. 在构建机器上为分支构建创建了一个单独的源目录

    4. 初始化 WorkspaceName 和 SourcesDirectory,使其与新的工作区名称和源目录文件夹匹配

    5. 我创建了一个自定义活动来更改 BuildSettings.ProjectsToBuild 中的项目/解决方案列表,以便它们从输入的分支中引用它们

    6. 我在测试阶段注意到,即使我的输入分支的新工作区最初设置为使用输入的分支作为其 ServerItem,它仍会使用在构建中输入的 ServerItem 创建工作区定义。为了解决这个问题,我创建了另一个自定义活动来重新映射分支工作区,以便 ServerItem 指向正确的分支

    我的工作流程中还涉及其他一些事情,但它们是我的开发人员要求的更多调整,上述步骤导致单个构建定义的多个分支构建。

    【讨论】:

    • 如果你能详细说明你是如何解决这个问题的,那么我会投赞成票。
    • @Vermin:同意...我也想看看你的解决方案。
    【解决方案2】:

    我还希望在我的构建定义中获得更多的分支敏捷性,因此我实施了一个代码活动来更改 ProjectsToBuild(如上面 Vermin 的第 5 步)。这是代码(删除了项目特定的东西):

    [BuildActivity(HostEnvironmentOption.All)]
    public sealed class ConvertProjectsAccordingToBranch : CodeActivity
    {
        public static string s_MainBranch = "$/MyTeamProject/Main";
    
        public InArgument<IBuildDetail> BuildDetail { get; set; }
        public InArgument<BuildSettings> BuildSettingsOriginal { get; set; }
        public OutArgument<BuildSettings> BuildSettingsConverted { get; set; }
    
        protected override void Execute(CodeActivityContext context)
        {
            Logger.Instance.Init(context);
            IBuildDetail buildDetail = BuildDetail.Get(context);
            BuildSettingsConverted.Set(context, ConvertProjects(BuildSettingsOriginal.Get(context), buildDetail.BuildDefinition));
        }
    
        /// <summary>Returns a BuildSettings with ProjectsToBuild converted according to the build definition's workspace</summary>
        public BuildSettings ConvertProjects(BuildSettings settingsOriginal, IBuildDefinition buildDefinition)
        {
            var mappings = buildDefinition.Workspace.Mappings;
            if (mappings.Count() != 1)
            {
                throw new BuildProcessException(string.Format(CultureInfo.InvariantCulture,
                    "Build definition must have exactly one workspace mapping. Build definition ID:{0} has {1} mappings",
                    buildDefinition.Id,   // IBuildDefinition doesn't have any Name property, seriously!
                    mappings.Count()));
            }
    
            string definitionBranch = mappings.First().ServerItem;
            var settingsConverted = new BuildSettings()
            {
                PlatformConfigurations = settingsOriginal.PlatformConfigurations,
            };
    
            foreach (string projectOriginal in settingsOriginal.ProjectsToBuild)
            {
                var projectConverted = projectOriginal.Replace(s_MainBranch, definitionBranch);
                if (!projectConverted.StartsWith(definitionBranch))
                {
                    throw new BuildProcessException(string.Format(CultureInfo.InvariantCulture,
                        "Project {0} is not under main branch {1},  Definition branch: {2}",
                        projectOriginal, s_MainBranch, definitionBranch));
                }
    
                settingsConverted.ProjectsToBuild.Add(projectConverted);
                Logger.Instance.Log("Converted ProjectToBuild: {0},  original: {1}", projectConverted, projectOriginal);
            }
    
            return settingsConverted;
        }
    }
    

    我也有ConvertProjects() 的单元测试,但它依赖于本地的东西,所以我没有在这里发布。这是微不足道的,绝对值得写!

    【讨论】:

      【解决方案3】:

      我知道这已经很老了,但万一有人发现这篇文章。

      我们有相同的要求:能够构建功能分支,以在合并到开发之前提供用于测试的特定功能的构建。

      这就是我们的做法:

      创建了一组 Visual Studio 扩展:

      • 创建分支构建
      • 启动分支构建
      • 删除分支

      创建分支构建将克隆用作模板的构建定义。根据分支更改构建定义名称和工作区。构建定义将在 Team Explorer (VS2012) 中可见。

      启动分支构建将找到构建定义并启动构建。

      删除分支会找到构建定义,删除所有构建,删除构建定义,然后删除分支(清理)。构建和分支的生命周期很短,因此清理它们很重要。

      【讨论】:

        【解决方案4】:

        简而言之:否和是:-)

        对这些答案进行更详细的说明,TFS 系统默认模板更喜欢为您要构建的每个分支使用单个构建定义,或者指定需要一次构建的所有分支,但在单个构建定义中。 Queue new build 对话框中的可用分支中没有标准方法可供选择。

        下一个答案是肯定的,因为您确实可以自定义构建模板,而且我知道您已经开始这样做了。根据您的分支结构,应该很容易将属性添加到您的模板中,该属性显示在“队列新构建”对话框中,并且有一个(例如)半列分隔的字符串,其中包含您想要在运行期间构建的所有分支,并为该列表中的每个元素添加一个解决方案,以便在模板实际开始迭代该列表之前在工作流过程中构建。

        这应该可以完成这项工作,但是,我对您最终如何想要这种能力感兴趣?你不使用VS解决方案文件吗?

        【讨论】:

        • 您好,感谢您的帮助。我已经开始改变我的工作流程以(希望)实现我的目标。在回答您的问题时,没有设置构建定义来构建解决方案的项目文件,而不是解决方案文件本身。
        • 另外,它更多的是替换构建定义中构建的默认分支而不是向构建添加分支,所以我目前正在为所选分支使用不同的工作区(输入通过排队构建时的工作流参数),然后将致力于更改 BuildSetting.ProjectsToBuild 以引用所选分支而不是默认分支
        猜你喜欢
        • 2014-07-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-05-19
        • 2012-09-13
        • 1970-01-01
        • 2013-07-25
        • 1970-01-01
        相关资源
        最近更新 更多