大量の Tupfile では、大規模なパイプライン処理を使用します。
: input |> < %f command1 | command2 > %o |> output
system
これに関する問題は、でこれらの :-rules を実行するTup 呼び出しがsh
、 をサポートしていないことset -o pipefail
です。結果として、command1
失敗しただけの場合でも、終了コードが 0 だったので、tup はこれを成功としてマークします。これは非常に問題があります。
私はこれに対する 2 つの解決策を知っていますが、どちらも理想的ではありません。
を。パイプライン処理を放棄して、代わりに次のことを行うことができます。
: input |> < %f command1 > %o |> intermediate
: intermediate |> < %f command2 > %o |> output
これは機能しますが、一連のルールを面倒に書き直す必要があり、さらに重要なことに、更新のたびにディスク容量とディスク書き込みが大幅に多く使用されます。
b) すべてのコマンドを次のbash
ようにラップできます。
: input |> bash -c 'set -o pipefail && < %f command1 | command2 > %o' |> output
これは、書き換えが少なく、io を回避できるため、わずかに優れているように見えますが、それでも非常に面倒です。また'
、:-rules で any をエスケープする必要があります。
理想的には、:-rules を読み取るために使用するシェル/インタープリターを指定するだけの Tup 構成が存在します。理想的には、共通のプレフィックスの構成もあり、すべてのスクリプトを実行したり、他の必要なset -o pipefail &&
ものを実行したりできます。私の知る限り、これはすぐには可能ではありません。system
tup がルールを呼び出すたびに、ラッパー アラウンドを記述する必要があります。ただし、提案された 2 つのソリューションよりも洗練されたものを可能にする Tup のいくつかの側面を見落としている可能性があります。
編集:システムへの呼び出しにより、パイプフェイルをシステムへの呼び出しに「注入」することができました。プログラムがシステムを使用して実行されるという事実を誤って述べました。メーリングリストの助けを借りて、それらが実際に を使用して実行されていることが判明しました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;
}