Skip to content

Instantly share code, notes, and snippets.

@buserror
Created June 24, 2020 17:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save buserror/227a7e64c92acece821ec8ee58776276 to your computer and use it in GitHub Desktop.
Save buserror/227a7e64c92acece821ec8ee58776276 to your computer and use it in GitHub Desktop.
#!/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 <buserror@gmail.com>
* 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