Created
June 24, 2020 17:56
-
-
Save buserror/227a7e64c92acece821ec8ee58776276 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/tcc -run | |
/* | |
* ^^ If you don't know tcc, you should. | |
* | |
* This program is a wrapper around make(1), it parses recursive make | |
* output and try to generate valid pathnames for errors generated by | |
* the compiler for relative pathnames. | |
* It keep tracks (well it tries to) of parallel make jobs and try to | |
* look in the directory for all the running jobs for a matching | |
* pathnames, then doctor the error message with the 'real' pathname. | |
* | |
* This was made to go around stuff like Visual Studio Code | |
* and KDE's Kate not being able to parse recursive make messages. | |
* | |
* (C) 2020 Michel Pollet <[email protected]> | |
* MIT Licence | |
*/ | |
#include <sys/types.h> | |
#include <sys/stat.h> | |
#include <sys/wait.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <stdint.h> | |
#include <stdbool.h> | |
#include <unistd.h> | |
#include <strings.h> | |
#include <string.h> | |
#include <poll.h> | |
#include <regex.h> | |
struct stream_t { | |
uint8_t buf[128 * 1024]; | |
size_t len; | |
}; | |
#define AS(__a) ((sizeof(__a) / sizeof((__a)[0]))) | |
struct { | |
char dir[4096]; | |
int idx; | |
} current_pwd[32]; | |
int max_pwd = -1; | |
int debug = 0; | |
/* | |
* Use make(1) 'Entering/leaving' directory messages to populate a | |
* table of running sub-makes; that's pretty much it really | |
*/ | |
static void | |
process_stdout( | |
const char * line) | |
{ | |
static bool init = false; | |
static regex_t r_make, r_emake; | |
// we don't doctor the normal output, might as well plonk it out now | |
fprintf(stdout, "%s\n", line); | |
if (!init) { | |
if (regcomp(&r_make, | |
"^make\\[([0-9]+)\\]: Entering directory '(.*)'", | |
REG_EXTENDED)) | |
perror("r_make"); | |
if (regcomp(&r_emake, | |
"^make\\[([0-9]+)\\]: Leaving directory '(.*)'", | |
REG_EXTENDED)) | |
perror("r_emake"); | |
init = true; | |
} | |
regmatch_t m[4]; | |
if (regexec(&r_make, line, AS(m), m, 0) == 0) { // add entry | |
int idx = atoi(line + m[1].rm_so); | |
if (idx < 0 || idx >= AS(current_pwd)) { | |
printf("fpmake: make directory index overflow: %d!!\n", idx); | |
return; | |
} | |
current_pwd[idx].idx = idx; | |
snprintf(current_pwd[idx].dir, sizeof(current_pwd[idx].dir), | |
"%.*s", m[2].rm_eo - m[2].rm_so, line + m[2].rm_so); | |
if (idx > max_pwd) | |
max_pwd = idx; | |
if (debug) | |
printf(" >> Entering IDX = %d/%d, '%s'\n", | |
idx, max_pwd, current_pwd[idx].dir); | |
} else if (regexec(&r_emake, line, AS(m), m, 0) == 0) { // clear it | |
int idx = atoi(line + m[1].rm_so); | |
current_pwd[idx].idx = -1; | |
if (debug) | |
printf(" << Leaving %d/%d '%s'\n", idx, max_pwd, | |
current_pwd[idx].dir); | |
// update max_pwd if appropriate | |
if (idx == max_pwd) { | |
while (current_pwd[max_pwd].idx == -1) | |
max_pwd--; | |
} | |
} | |
} | |
/* | |
* If an error line arrives, try to find some sort of | |
* <relative pathname>:<line number> pattern in there. | |
* If found, look in all the open sub-make jobs directory for an | |
* existing file, and doctor the line with THAT path before output. | |
* Of course, there's lots of potential issues in there, like similarly | |
* named files in multiple directories etc but hey. Better that than | |
* nothing at all. | |
*/ | |
static void | |
process_stderr( | |
const char * line) | |
{ | |
static bool init = false; | |
static regex_t r_info, r_error; | |
// fprintf(stderr, "ERR: %s\n", line); | |
if (!init) { | |
if (regcomp(&r_info, "from ([^/][^:]+):[0-9]+", REG_EXTENDED)) | |
perror("r_info"); | |
if (regcomp(&r_error, "^([^/][^:]+):[0-9]+", REG_EXTENDED)) | |
perror("r_emake"); | |
init = true; | |
} | |
regmatch_t m[4]; | |
if (regexec(&r_info, line, AS(m), m, 0) && | |
regexec(&r_error, line, AS(m), m, 0)) { | |
fprintf(stderr, "%s\n", line); | |
return; | |
} | |
int rell = m[1].rm_eo - m[1].rm_so; | |
const char * rel = strndup(line + m[1].rm_so, rell); | |
if (debug) | |
printf(" ERROR RELATIVE PATH: '%s'\n", rel); | |
char path[4096]; | |
int done = 0; | |
for (int pi = max_pwd; pi >= 0; pi--) { | |
if (current_pwd[pi].idx == -1) | |
continue; | |
snprintf(path, sizeof(path), "%s/%s", | |
current_pwd[pi].dir, rel); | |
int pl = strlen(path); | |
if (debug) | |
printf(" Trying[%d]: '%s'\n", pi, path); | |
struct stat stb; | |
if (stat(path, &stb) == 0) { | |
if (debug) | |
printf(" Exists!!\n"); | |
int nll = strlen(line) - rell + pl + 1; | |
char * newline = malloc(nll); | |
snprintf(newline, nll, "%.*s%s%.*s", | |
m[1].rm_so, line, path, | |
strlen(line) - m[1].rm_eo, line + m[1].rm_eo); | |
fprintf(stderr, "%s\n", newline); | |
free(newline); | |
done++; | |
break; | |
} | |
} | |
/* don't drop the error if we didn't doctor it */ | |
if (!done) { | |
if (debug) | |
printf("fmake: Unable to find a valid '%s', sorry\n", rel); | |
fprintf(stderr, "%s\n", line); | |
} | |
free((void*)rel); | |
} | |
/* read some data into running buffer, split into lines, call callback. | |
* this deal with fragmented lines, altho in reality, I don't think to | |
* get them. Oh well. */ | |
static int | |
st_read( | |
struct stream_t * s, | |
int fd, | |
void (*line)(const char *)) | |
{ | |
ssize_t rd = read(fd, s->buf + s->len, AS(s->buf) - s->len - 1); | |
if (rd <= 0) | |
return -1; | |
s->len += rd; | |
s->buf[s->len] = 0; | |
char *b = s->buf; | |
do { | |
char * r = index(b, '\n'); | |
if (!r) | |
break; | |
*r = 0; | |
line(b); | |
b = r + 1; | |
} while ((b - s->buf) < s->len); | |
size_t remains = s->len - (b - s->buf); | |
if (remains) { | |
if (debug) | |
printf("***** %d remains\n", (int)remains); | |
memmove(s->buf, b, remains); | |
} | |
s->len = remains; | |
return 0; | |
} | |
int | |
main( | |
int argc, | |
char *argv[]) | |
{ | |
static struct { | |
int pipe[2]; | |
struct stream_t st; | |
void (*line)(const char *); | |
} fd[2] = { | |
[0].line = process_stdout, | |
[1].line = process_stderr, | |
}; | |
for (int i = 0; i < AS(fd); i++) | |
if (pipe(fd[i].pipe)) { | |
perror(argv[0]); | |
exit(1); | |
} | |
// prep concurent make directory table | |
for (int i = 0; i < AS(current_pwd); i++) | |
current_pwd[i].idx = -1; | |
// pre fill current directory | |
getcwd(current_pwd[0].dir, sizeof(current_pwd[0].dir)); | |
current_pwd[0].idx = 0; | |
pid_t child = fork(); | |
if (child == 0) { | |
close(1); close(2); | |
if (dup2(fd[0].pipe[1], 1) == -1 || | |
dup2(fd[1].pipe[1], 2) == -1) { | |
printf("%s failed to dup2 descriptor\n", argv[0]); | |
exit(1); | |
} | |
argv[0] = "make"; | |
execvp("make", argv); | |
perror("execvp"); | |
exit(1); | |
} | |
int exited_status = 0; | |
pid_t exited = 0; | |
do { | |
struct pollfd fds[2] = { | |
[0] = { .fd = fd[0].pipe[0], .events = POLLIN, }, | |
[1] = { .fd = fd[1].pipe[0], .events = POLLIN, }, | |
}; | |
exited = waitpid(child, &exited_status, WNOHANG); | |
if (exited == child) | |
break; | |
int r = poll(fds, AS(fds), 200); | |
if (r == 0) | |
continue; | |
if (r == -1) { | |
perror("poll"); | |
break; | |
} | |
for (int i = 0; i < AS(fd); i++) { | |
if (fds[i].revents) { | |
if (fds[i].revents & POLLIN) { | |
if (st_read(&fd[i].st, fd[i].pipe[0], fd[i].line)) | |
break; | |
} else | |
break; | |
} | |
} | |
} while (1); | |
// kill kiddy if it wasn't dead already. | |
if (!exited) { | |
kill(child, SIGHUP); | |
/*exited = */waitpid(child, &exited_status, WNOHANG); | |
} | |
if (debug) | |
printf("%s exits with %d\n", argv[0], | |
WEXITSTATUS(exited_status)); | |
// exit like the kid make did | |
exit(WEXITSTATUS(exited_status)); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment