通过 tup 为命令 运行 设置 pipefail
set pipefail for commands run by tup
在大量的 Tupfile 中,我使用了大量的流水线,例如
: input |> < %f command1 | command2 > %o |> output
这个问题是 Tup 调用 system
,它在 sh
中执行这些 :-rules,它不支持 set -o pipefail
。因此,如果只有 command1
失败,tup 仍会将其标记为成功,因为它的退出代码为 0。这是很成问题的。
我知道有两种解决方案,但都不理想。
一个。我可以放弃流水线,而是做:
: input |> < %f command1 > %o |> intermediate
: intermediate |> < %f command2 > %o |> output
这可行,但需要重写大量规则,更重要的是,每次更新时都会使用更多的磁盘 space 和磁盘写入。
b) 我可以将每个命令包装在 bash
中,例如:
: input |> bash -c 'set -o pipefail && < %f command1 | command2 > %o' |> output
这似乎稍微好一点,因为它涉及更少的重写,并且避免了 io,但仍然非常麻烦。它还需要在我的 :-rules.
中转义任何 '
理想情况下,Tup 配置可以指定使用什么 shell / 解释器来读取 :-rules。理想情况下,还会有一个通用前缀的配置,因此所有脚本都可以是 运行 和 set -o pipefail &&
或我想要的任何其他内容。据我所知,这不是立即可行的。每当 tup 调用规则时,都需要编写围绕 system
的包装器。但是,也许我错过了 Tup 的某些方面,这些方面比所提出的两个解决方案更优雅。
编辑:
虽然对 system 的调用确实允许我 "inject" pipefail 进入对 system 的调用。我错过了一个事实,即程序 运行 使用 system.在邮件列表的一些帮助下,事实证明他们实际上是 运行 使用 execle
。下面是我用来插入的代码,以防有人想完成同样的事情。
解决方案
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
int execle(const char* path, const char* arg0, ...) {
/* We're going to interpose this function, modify the arguments if we need
* to, and then convert it into a call to execve. Due to a weirdness in the
* consts of the api, we need to discard a const qualifier on the
* characters in the arguments. The call is `int execve(const char*
* filename, char* const argv[], char* const envp[]);` but it should
* probably be `int execve(const char* filename, const char* const argv[],
* char* const envp[]);` at the very least, e.g. arguments shouldn't be
* modified. These aren't actually modified by the call, so in order to
* avoid the inefficiency of copying the strings into memory we don't need,
* we just do this unsafely and compile with `-Wno-discarded-qualifiers`.
* */
// Count the number of variable arguments for malloc
unsigned int num_args;
va_list ap;
va_start(ap, arg0);
if (arg0) {
num_args = 1;
while(va_arg(ap, const char*)) {
num_args++;
}
} else {
num_args = 0;
}
char* const* env = va_arg(ap, char* const*); // Also grab env
va_end(ap);
// Test for specific tup execle call
va_start(ap, arg0);
int intercept = num_args == 4
&& strcmp(path, "/bin/sh") == 0
&& strcmp(arg0, "/bin/sh") == 0
&& strcmp(va_arg(ap, const char*), "-e") == 0
&& strcmp(va_arg(ap, const char*), "-c") == 0;
va_end(ap);
// Switch on whether to intercept the call, or pass it on
/*const*/ char** args;
if (intercept) { // We want to switch to bash with pipefail enabled
args = malloc(7 * sizeof(args));
path = "/bin/bash";
args[0] = "/bin/bash";
args[1] = "-e";
args[2] = "-o";
args[3] = "pipefail";
args[4] = "-c";
va_start(ap, arg0);
va_arg(ap, const char*);
va_arg(ap, const char*);
args[5] = va_arg(ap, const char*); // command
va_end(ap);
args[6] = NULL;
} else { // Just copy args into a null terminated array for execve
args = malloc((num_args + 1) * sizeof(*args));
char** ref = args;
if (arg0) {
*ref++ = arg0;
const char* arg;
va_start(ap, arg0);
while ((arg = va_arg(ap, const char*))) {
*ref++ = arg;
}
va_end(ap);
}
*ref = NULL;
}
int error_code = execve(path, args, env);
free(args);
return error_code;
}
您可以实现自己的 system
作为
switch(pid = fork()) {
case 0:
// Modify command to prepend "set -o pipefail &&" to it.
execl("/bin/bash", "bash", "-c", command, (char *) 0);
case -1: // handle fork error
default:
waitpid(pid, ...);
}
和 LD_PRELOAD
system
在您的 tup
流程中实施。
如果你不想做底层的进程管理,你可以插入system
到只是把命令包在bash -c "set -o pipefail && "
里然后转义引号,然后调用原始 system
。请参阅 this article 库插入。
在大量的 Tupfile 中,我使用了大量的流水线,例如
: input |> < %f command1 | command2 > %o |> output
这个问题是 Tup 调用 system
,它在 sh
中执行这些 :-rules,它不支持 set -o pipefail
。因此,如果只有 command1
失败,tup 仍会将其标记为成功,因为它的退出代码为 0。这是很成问题的。
我知道有两种解决方案,但都不理想。
一个。我可以放弃流水线,而是做:
: input |> < %f command1 > %o |> intermediate
: intermediate |> < %f command2 > %o |> output
这可行,但需要重写大量规则,更重要的是,每次更新时都会使用更多的磁盘 space 和磁盘写入。
b) 我可以将每个命令包装在 bash
中,例如:
: input |> bash -c 'set -o pipefail && < %f command1 | command2 > %o' |> output
这似乎稍微好一点,因为它涉及更少的重写,并且避免了 io,但仍然非常麻烦。它还需要在我的 :-rules.
中转义任何'
理想情况下,Tup 配置可以指定使用什么 shell / 解释器来读取 :-rules。理想情况下,还会有一个通用前缀的配置,因此所有脚本都可以是 运行 和 set -o pipefail &&
或我想要的任何其他内容。据我所知,这不是立即可行的。每当 tup 调用规则时,都需要编写围绕 system
的包装器。但是,也许我错过了 Tup 的某些方面,这些方面比所提出的两个解决方案更优雅。
编辑:
虽然对 system 的调用确实允许我 "inject" pipefail 进入对 system 的调用。我错过了一个事实,即程序 运行 使用 system.在邮件列表的一些帮助下,事实证明他们实际上是 运行 使用 execle
。下面是我用来插入的代码,以防有人想完成同样的事情。
解决方案
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
int execle(const char* path, const char* arg0, ...) {
/* We're going to interpose this function, modify the arguments if we need
* to, and then convert it into a call to execve. Due to a weirdness in the
* consts of the api, we need to discard a const qualifier on the
* characters in the arguments. The call is `int execve(const char*
* filename, char* const argv[], char* const envp[]);` but it should
* probably be `int execve(const char* filename, const char* const argv[],
* char* const envp[]);` at the very least, e.g. arguments shouldn't be
* modified. These aren't actually modified by the call, so in order to
* avoid the inefficiency of copying the strings into memory we don't need,
* we just do this unsafely and compile with `-Wno-discarded-qualifiers`.
* */
// Count the number of variable arguments for malloc
unsigned int num_args;
va_list ap;
va_start(ap, arg0);
if (arg0) {
num_args = 1;
while(va_arg(ap, const char*)) {
num_args++;
}
} else {
num_args = 0;
}
char* const* env = va_arg(ap, char* const*); // Also grab env
va_end(ap);
// Test for specific tup execle call
va_start(ap, arg0);
int intercept = num_args == 4
&& strcmp(path, "/bin/sh") == 0
&& strcmp(arg0, "/bin/sh") == 0
&& strcmp(va_arg(ap, const char*), "-e") == 0
&& strcmp(va_arg(ap, const char*), "-c") == 0;
va_end(ap);
// Switch on whether to intercept the call, or pass it on
/*const*/ char** args;
if (intercept) { // We want to switch to bash with pipefail enabled
args = malloc(7 * sizeof(args));
path = "/bin/bash";
args[0] = "/bin/bash";
args[1] = "-e";
args[2] = "-o";
args[3] = "pipefail";
args[4] = "-c";
va_start(ap, arg0);
va_arg(ap, const char*);
va_arg(ap, const char*);
args[5] = va_arg(ap, const char*); // command
va_end(ap);
args[6] = NULL;
} else { // Just copy args into a null terminated array for execve
args = malloc((num_args + 1) * sizeof(*args));
char** ref = args;
if (arg0) {
*ref++ = arg0;
const char* arg;
va_start(ap, arg0);
while ((arg = va_arg(ap, const char*))) {
*ref++ = arg;
}
va_end(ap);
}
*ref = NULL;
}
int error_code = execve(path, args, env);
free(args);
return error_code;
}
您可以实现自己的 system
作为
switch(pid = fork()) {
case 0:
// Modify command to prepend "set -o pipefail &&" to it.
execl("/bin/bash", "bash", "-c", command, (char *) 0);
case -1: // handle fork error
default:
waitpid(pid, ...);
}
和 LD_PRELOAD
system
在您的 tup
流程中实施。
如果你不想做底层的进程管理,你可以插入system
到只是把命令包在bash -c "set -o pipefail && "
里然后转义引号,然后调用原始 system
。请参阅 this article 库插入。