当有多个作者使用版本控制时,您如何跟踪库的构建次数?

4
我不知道这是否是人们普遍做的事情,但我个人一直跟踪我构建代码的次数。也就是说,我调用make的次数以及构建成功的次数。

我的当前解决方案

我有一个简单的代码,它以文件为参数,打开文件,递增其中的数字并覆盖它。当调用make时,首先编译此代码。

紧接着,调用./increase_build build.txt将增加我调用make构建库的次数。

然后,代码被编译并制作lib文件(使用ar cq ...)。之后,调用./increase_build libbuild.txt将增加成功构建的次数。最后构建测试。

是我的一个Makefile示例。

我为什么会感到担忧

这在使用版本控制前一直运行良好。似乎没有问题:我是自己库的唯一作者,逐个添加功能。
但有一天,我在测试分支和合并(我在工作中使用svn,在个人项目中使用git),所以我在一个分支中添加了一个特性并更改了主分支中的内容,然后将两者合并。现在,构建计数文件具有不同的值。
问题是,假设在分支时,构建计数为100。然后我在分支中写了一些东西,构建计数达到110。我在主分支中写了一些东西,构建计数达到120。当我合并它们时,我看到一个是110,一个是120(顺便说一下,这是一个冲突)。正确的解决方案是将构建设置为130。
但是,我不能(也不想)回到分支起点的提交,并发现它是100,因此我计算100 +(110-100)+(120-100)= 130!我希望这是自动的。
问题很明显:我该如何做到这一点?当我使用版本控制时,如何跟踪我的构建计数(而不是提交计数)?我不想要一个基于版本控制功能的答案,因为如果我更改了版本控制系统,问题会重新出现。
我认为可以在每个构建文件中添加一行,例如日期和时间。然后构建号将是构建计数文件中行数的数量。此外,除非我在两个分支上得到了两个完全相同时的构建,否则合并文件只是两个文件的并集。
我想知道是否有更好的解决方案?我想要的(构建计数)是否值得努力?
P.S. 如果你想知道为什么我同时使用构建数量和成功构建数量,那只是个人喜好。我喜欢看到自己编写代码时由于小错误和拼写错误而需要重建多少次。
编辑:我使用C和C++进行编程,因此任何一种语言的解决方案都适用于我。

2
我个人总是追踪我编译代码的次数。- 你难道没有一些“真正”的工作要做吗? :-) - paxdiablo
@paxdiablo,我确实需要自动化处理,这就是为什么我想要它自动化的原因! - Shahbaz
@paxdiablo,另外,有一天你看着文件会说:“哇,我建了这么多次?”。开玩笑的,但主要是因为当我发布时,我会在版本中包含构建计数。 - Shahbaz
5
三年后:拥有构建次数真的失去了它的酷感。如果有人正在阅读此内容尝试做同样的事情,我会说不要浪费你的时间。@paxdiablo 一直是对的 ;) - Shahbaz
4个回答

2
因为构建编号不是您所在分支的功能,因此应以不同的方式进行跟踪。我不使用git,但对于SVN,我们在工作中有一个系统,通过将特定分支复制到特定标记来构建特定分支,并添加一些特定于该标记的工件(您的构建编号将是要添加的典型示例),仅在构建成功后提交。
换句话说,在SVN中有一个指定的位置(标记名称),您只能在其中进行构建,这也是您唯一进行构建的地方,构建编号信息存储并更新在那里。您的构建脚本可能如下所示:
# I don't know git -- this is all very much pseudocode

# Where did you commit the code you want to build?
source=git://server/path/to/my/branch

# Replace builddir tree with yours
git replace git://server/special/place/build/thisproject with code from $source

cd /tmp
git checkout git://sever/special/place/build/thisproject into new builddir
cd builddir

update local version-controlled file buildnumber+=1

if make
    # Build was successful
    git commit buildnumber
    copy build artefacts to where-ever
endif

cd /tmp
rm -rf /tmp/builddir      

存在竞态条件;如果有人在您之后提交构建请求,但某种方式先到达服务器,则最终会生成他们的提交。

通过使用类似于Hudson/Jenkins的指定构建主机,可以使这个过程变得更简单。


我的意思是,想象一下你们两个人去构建项目,每个人都在自己的分支上。你们俩都在第10次构建时得到了该分支,但是你在不断更改后达到了第50次构建,而你的同事在不断更改后达到了第40次构建。现在,基本上,在分支之前已经构建了10次项目,在你的分支上构建了40次,在你的同事分支上构建了30次。这导致构建编号为80。我无法理解你的方法如何处理这种情况。 - Shahbaz
请注意,我的问题不是关于标签或工作提交,而是关于保持构建编号。在我之前的例子中,80是成功的构建编号。想象一下相同的过程,但是有失败的构建。您的方法完全忽略了它。 - Shahbaz
我看到了你的编辑。它很有道理,但是有一些问题。首先,竞态条件可以被消除,因为你可以在本地切换分支而不是在远程上,构建然后提交。然而,这引入了另一个问题,即两个人同时构建,他们各自在本地构建,然后想要提交。然而,最大的问题是,每次构建都需要做所有这些事情。当然,你可以把它们放在一个脚本中,但是执行起来需要很长时间。我不知道你怎么想,但我可能每10~30分钟就会构建我的项目! - Shahbaz
没错,它更适合于生产构建而不是跟踪您的私有构建。不过也许您可以将两者结合起来? - tripleee
我会考虑一下。感谢你的努力。 - Shahbaz

1

我打算发布并接受我自己想法的实现作为答案,因为它似乎是最实用的。

所以这就是解决方案:

  • 每次构建时,在构建文件中添加一行包含以下数据的内容:
    • 日期
    • 时间
    • 一个随机数
  • 每次合并时,保留来自两个构建文件的行
  • 构建数量是构建文件中行的总数。

构建文件的每一行都需要是唯一的。日期和时间使其几乎唯一。很少有两个人在同一时间在自己的分支上发出构建的情况。但是,这种情况可能会发生。因此,生成一个随机数并将其添加以减少该机会。

然而,存在一个问题。如果使用srand(time(NULL))进行种子处理,那么由于两个构建被认为是在同一时间,生成的数字也可能相同。因此,可以使用不同的数字对随机数生成器进行种子处理,例如clock()gettimeofday()的毫秒部分。即使没有随机生成,这些数字本身也可以放置在随机数的位置。

如果仍然有两行相同,我将应用鸵鸟算法

更新:

我已经实现了它,一切都运行良好。最终,我使用clock_gettime(CLOCK_MONOTONIC, ...)并打印此函数获得的纳秒作为随机数。我没有使用clock()的原因是,由于程序非常短,它运行时间少于clock()的分辨率,因此我一直得到0。

更新:

这是我编写的最终代码(其中一些部分从其他地方窃取!)。在某些平台上,您可能需要-lrt

/*
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <time.h>
#include <stdio.h>
#include <stdlib.h>

#ifdef _WIN32
#include <windows.h>

struct timespec
{
    long tv_sec;
    long tv_nsec;
};

/* Note: I copy-pasted this from internet (https://dev59.com/N2035IYBdhLWcg3weP3f#5404467)
 * I tweaked it to return nanoseconds instead of microseconds
 * It is much more complete than just finding tv_nsec, but I'm keeping it for possible future use. */
LARGE_INTEGER getFILETIMEoffset(void)
{
    SYSTEMTIME s;
    FILETIME f;
    LARGE_INTEGER t;

    s.wYear = 1970;
    s.wMonth = 1;
    s.wDay = 1;
    s.wHour = 0;
    s.wMinute = 0;
    s.wSecond = 0;
    s.wMilliseconds = 0;
    SystemTimeToFileTime(&s, &f);
    t.QuadPart = f.dwHighDateTime;
    t.QuadPart <<= 32;
    t.QuadPart |= f.dwLowDateTime;
    return t;
}

int clock_gettime(int X, struct timespec *tv)
{
    LARGE_INTEGER t;
    FILETIME f;
    double microseconds;
    static LARGE_INTEGER offset;
    static double frequencyToNanoseconds;
    static int initialized = 0;
    static BOOL usePerformanceCounter = 0;

    if (!initialized)
    {
        LARGE_INTEGER performanceFrequency;
        initialized = 1;
        usePerformanceCounter = QueryPerformanceFrequency(&performanceFrequency);
        if (usePerformanceCounter)
        {
            QueryPerformanceCounter(&offset);
            frequencyToNanoseconds = (double)performanceFrequency.QuadPart/1000000000.0;
        }
        else
        {
            offset = getFILETIMEoffset();
            frequencyToNanoseconds = 0.010;
        }
    }
    if (usePerformanceCounter)
        QueryPerformanceCounter(&t);
    else
    {
        GetSystemTimeAsFileTime(&f);
        t.QuadPart = f.dwHighDateTime;
        t.QuadPart <<= 32;
        t.QuadPart |= f.dwLowDateTime;
    }

    t.QuadPart -= offset.QuadPart;
    microseconds = (double)t.QuadPart/frequencyToNanoseconds;
    t.QuadPart = microseconds;
    tv->tv_sec = t.QuadPart/1000000000;
    tv->tv_nsec = t.QuadPart%1000000000;
    return 0;
}

#ifndef CLOCK_MONOTONIC
#define CLOCK_MONOTONIC 0       /* not used anyway */
#endif
#endif

int main(int argc, char **argv)
{
    time_t now_sec;
    struct tm *now;
    FILE *bout;
    struct timespec now_clk;
    if (argc < 2)
    {
        printf("Usage: %s build_file_name\n\n", argv[0]);;
        return EXIT_FAILURE;
    }
    bout = fopen(argv[1], "a");
    if (!bout)
    {
        printf("Could not open file: %s\n\n", argv[1]);
        return EXIT_FAILURE;
    }
    time(&now_sec);
    now = gmtime(&now_sec);
    fprintf(bout, "%02d/%02d/%04d %02d:%02d:%02d", now->tm_mday, now->tm_mon+1, now->tm_year+1900, now->tm_hour, now->tm_min, now->tm_sec);
    clock_gettime(CLOCK_MONOTONIC, &now_clk);
    fprintf(bout, " %ld\n", now_clk.tv_nsec);
    return EXIT_SUCCESS;
}

希望这对某些人有所帮助。

更新

使用了约9个月后,我可以说这非常有用。一些观察结果是:

  • 在Windows上,clock_gettime的实现给出的最后一个元素非常小,同样的值半数以上出现。然而,它仍然使其更随机一些。
  • 在Linux上,最后一个元素确实相当随机。
  • 不时需要进行“构建”提交才能提交构建文件中的行以进行合并。但是,可以使用git stash避免此问题。
  • 几乎每次使用此方法合并都会导致冲突,但解决它非常简单(只需将差异标记作为两个文件的行删除即可)。
  • wc -l是你的好朋友。

1

你的解决方案,使用构建日志(每次构建一行)似乎相当聪明。你可以添加执行构建的机器的IP地址(或Mac地址)到时间戳中,这样就可以消除重复行的风险。但是,根据你的版本控制系统,你可能需要手动合并构建日志文件。使用Git,你可以配置它,使合并始终保留两个版本(并最终按日期排序等)。


说到时间戳,我在 git pull 之后只需运行 git log -n1 "--pretty=format:%at"。这将给我一个每次递增的数字。虽然它比我想要的稍微长一些,但您可以从前面和/或后面修剪几个数字,仍然没问题。 - MarkHu

-1
我不能(读作我不想)回到分支起点的提交,找到它是100,然后计算...我希望这是自动的。
一个不太好的想法:每次构建都向版本文件(或一对PASS/FAIL中的一个)添加字符串,每个分支略有不同。合并分支将需要手动合并此信号文件,其中字符串装饰的差异使此任务更容易。稍后使用wc -l将计算数字。

这不就是我自己发现的吗?我说的那部分“我认为可能有效的方法是…” - Shahbaz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接