/* build.c - Simple, POSIX, non-Cytoplasm utility to build out * the entirety of Parsee from scratch, without any Makefiles. * * The main reason why this tool exists is merely because the * current Make-based building is not POSIX compliant, and I * am simply not porting it to be. The Makefile shall stay * supported however, but if possible, use build.c. * To run it, just build it with: * cc build.c -o /tmp/build && /tmp/build * * TODO: Parallel jobs, CFLAGS and LDFLAGS. * * Note that this bit is formatted differently. * -------------------------------- * LICENSE: CC0 * Written-By: LDA [lda@freetards.xyz] [@fourier:ari.lt] */ #include #include #include #include #include #include #include #include #define DEFAULT_BUILD_PATH "build.conf" #define DEFAULT_COMPILER "cc" #define Compiler(info) (info.basic.cc ? info.basic.cc : DEFAULT_COMPILER) static char * string_rep_ext(char *in, char *ext1, char *ext2) { char *end; size_t inLen; if (!in || !ext1 || !ext2) { return NULL; } inLen = strlen(in); end = inLen + in; while (end >= in) { if (!strcmp(end, ext1)) { size_t cpyLen = end - in; size_t extLen = strlen(ext2); char *ret = malloc(cpyLen + extLen + 1); memcpy(ret, in, cpyLen); memcpy(ret + cpyLen, ext2, extLen); ret[cpyLen + extLen] = '\0'; return ret; } end--; } return NULL; } static char * string_dup(char *in) { char *out; size_t len; if (!in) { return NULL; } len = strlen(in); out = malloc(len + 1); memcpy(out, in, len); out[len] = '\0'; return out; } static char * string_cat(char *in1, char *in2) { char *out; size_t len1, len2; if (!in1) { return string_dup(in2); } if (!in2) { return string_dup(in1); } len1 = strlen(in1); len2 = strlen(in2); out = malloc(len1 + len2 + 1); memcpy(out, in1, len1); memcpy(out + len1, in2, len2); out[len1 + len2] = '\0'; return out; } static char * trim_nl(char *in) { char *tc; if (!in) { return NULL; } while ((tc = strrchr(in, '\n'))) { *tc = '\0'; } return in; } typedef struct str_array { char **values; size_t quantity; } str_array_t; static str_array_t * str_array_create(void) { str_array_t *ret = malloc(sizeof(*ret)); ret->values = NULL; ret->quantity = 0; return ret; } static void str_array_free(str_array_t *arr) { size_t i; if (!arr) { return; } for (i = 0; i < arr->quantity; i++) { if (arr->values[i]) free(arr->values[i]); } if (arr->values) free(arr->values); free(arr); } static void str_array_set(str_array_t *arr, size_t i, char *str) { if (!arr) { return; } if (i >= arr->quantity) { size_t size = (i+1) * sizeof(*arr->values); size_t j; arr->values = realloc(arr->values, size); for (j = arr->quantity; j <= i; j++) { arr->values[j] = NULL; } arr->quantity = i + 1 ; } if (arr->values[i]) free(arr->values[i]); arr->values[i] = string_dup(str); } static void str_array_add(str_array_t *arr, char *str) { if (!arr) { return; } str_array_set(arr, arr->quantity, str); } static char * str_array_get(str_array_t *arr, size_t i) { if (!arr) { return NULL; } return i < arr->quantity ? arr->values[i] : NULL; } static size_t str_array_len(str_array_t *arr) { return arr ? arr->quantity : 0; } typedef struct buildinfo { struct basic { char *codename; char *version; char *name; char *binary; char *src; char *inc; char *obj; char *cc; } basic; char *repo; } buildinfo_t; static void destroy_buildinfo(buildinfo_t *info) { if (!info) { return; } if (info->basic.codename) { free(info->basic.codename); info->basic.codename = NULL; } if (info->basic.version) { free(info->basic.version); info->basic.version = NULL; } if (info->basic.name) { free(info->basic.name); info->basic.name = NULL; } if (info->basic.binary) { free(info->basic.binary); info->basic.binary = NULL; } if (info->basic.src) { free(info->basic.src); info->basic.src = NULL; } if (info->basic.inc) { free(info->basic.inc); info->basic.inc = NULL; } if (info->basic.obj) { free(info->basic.obj); info->basic.obj = NULL; } if (info->repo) { free(info->repo); info->repo = NULL; } } static char * cmd_stdout(char *cmd) { FILE *f; char *line = NULL; size_t size; if (!cmd) { return NULL; } if (!(f = popen(cmd, "r"))) { return NULL; } getline(&line, &size, f); pclose(f); return line; } static int exec_code(char *program, char *argv[]) { pid_t forkRet; if (!program || !argv) { return -1; } forkRet = fork(); if (forkRet == 0) { /* Child */ execvp(program, argv); exit(0); } else { /* Parent */ int status; if (waitpid(forkRet, &status, 0) == -1) { return -1; } return status; } } static char * strchrn(char *s, char c) { s = strchr(s, c); return s ? s+1 : NULL; } static char * strchrl(char *s, char c) { char *s1 = NULL; while ((s = strchr(s, c))) { s1 = s; s++; } return s1; } static void mkdir_rec(char *dir) { char tmp[PATH_MAX]; char *start; if (!dir || strlen(dir) >= PATH_MAX - 1) { return; } memset(tmp, '\0', sizeof(tmp)); for (start = dir; start && *start; start = strchrn(start, '/')) { char subtmp[PATH_MAX]; char *next = strchr(start, '/'); if (!next) { next = start + strlen(start); } memcpy(subtmp, start, next - start); subtmp[next - start] = '\0'; { memcpy(tmp, dir, start - dir); tmp[strlen(tmp) - 1] = '\0'; mkdir(tmp, 0770); } } } static bool build_file(char *cSource, buildinfo_t info, bool isTool) { char *args[16] = { 0 }; char *oFileName, *objPath, *oFile; int ret, i = 0; int srclen; char *code, *name, *vers, *repo; if (!cSource) { return false; } if (!isTool) { srclen = strncmp(cSource, "src", 3) ? strlen(info.basic.obj) + 1 : strlen(info.basic.src) + 1 ; objPath = string_cat(info.basic.obj, "/"); oFile = string_rep_ext(cSource + srclen, ".c", ".o"); oFileName = string_cat(objPath, oFile); } else { srclen = 6; objPath = string_dup("tools/out/"); oFile = string_rep_ext(cSource + srclen, ".c", ""); oFileName = string_cat(objPath, oFile); } mkdir_rec(oFileName); args[i++] = Compiler(info); if (isTool) { args[i++] = "-lCytoplasm"; args[i++] = "-I."; } else { args[i++] = "-c"; } args[i++] = cSource; args[i++] = "-o"; args[i++] = oFileName; args[i++] = "-I"; args[i++] = info.basic.inc; args[i++] = "-I"; args[i++] = info.basic.src; { char *pre = string_cat("\"", info.basic.version); char *pos = string_cat(pre, "\""); vers = string_cat("-DVERSION=", pos); args[i++] = vers; free(pos); free(pre); } { char *pre = string_cat("\"", info.basic.name); char *pos = string_cat(pre, "\""); name = string_cat("-DNAME=", pos); args[i++] = name; free(pos); free(pre); } { char *pre = string_cat("\"", info.basic.codename); char *pos = string_cat(pre, "\""); code = string_cat("-DCODE=", pos); args[i++] = code; free(pos); free(pre); } { char *pre = string_cat("\"", info.repo); char *pos = string_cat(pre, "\""); repo = string_cat("-DREPOSITORY=", pos); args[i++] = repo; free(pos); free(pre); } args[i++] = NULL; ret = exec_code(Compiler(info), args); free(objPath); free(oFileName); free(oFile); free(vers); free(name); free(code); free(repo); return ret == EXIT_SUCCESS; } static bool finalise_file(str_array_t *arr, buildinfo_t info) { str_array_t *flags; size_t i; bool ret = true; if (!arr) { return false; } flags = str_array_create(); str_array_add(flags, Compiler(info)); str_array_add(flags, "-lCytoplasm"); for (i = 0; i < str_array_len(arr); i++) { char *file = str_array_get(arr, i); if (file) { size_t srclen = strncmp(file, "src", 3) ? strlen(info.basic.obj) + 1 : strlen(info.basic.src) + 1 ; char *objPath = string_cat(info.basic.obj, "/"); char *oFile = string_rep_ext(file + srclen, ".c", ".o"); char *oFileName = string_cat(objPath, oFile); str_array_add(flags, oFileName); free(oFileName); free(oFile); free(objPath); } } str_array_add(flags, "-o"); str_array_add(flags, info.basic.binary); str_array_add(flags, NULL); ret = exec_code(Compiler(info), flags->values); str_array_free(flags); return ret; } static str_array_t * collect_sources(char *dir, bool head, char *ext) { DIR *handle; str_array_t *ret; struct dirent *ent; if (!dir) { return NULL; } ret = str_array_create(); handle = opendir(dir); if (!handle) { printf("error: cannot open directory '%s'\n", dir); return ret; } while ((ent = readdir(handle))) { char *name = ent->d_name; if (*name == '.') continue; if (strlen(name) > strlen(ext) && !strcmp(name + strlen(name) - strlen(ext), ext)) { char *d1 = string_cat(dir, "/"); char *na = string_cat(d1, name); str_array_add(ret, na); free(d1); free(na); continue; } if (!strchr(name, '.') && strcmp(name, "out") && strcmp(name, "Makefile")) { str_array_t *sub; char *d1 = string_cat(dir, "/"); char *d2 = string_cat(d1, name); size_t i; sub = collect_sources(d2, false, ext); for (i = 0; i < str_array_len(sub); i++) { char *file = str_array_get(sub, i); str_array_add(ret, file); } str_array_free(sub); free(d2); free(d1); } } closedir(handle); return ret; } static char * process_png(char *png, buildinfo_t info) { size_t i = 0; char *symbol; char *cFile, *oFile; char *pcFile, *poFile; char *pcFile1, *poFile1; char *filename, *symbol1; char *arguments[8] = { 0 }; if (!png) { return NULL; } if (!(filename = strchrl(png, '/'))) { return NULL; } filename++; pcFile1= string_cat(info.basic.obj, "/"); pcFile = string_cat(pcFile1, filename); cFile = string_rep_ext(pcFile, ".png", ".c"); free(pcFile); free(pcFile1); poFile1= string_cat(info.basic.obj, "/"); poFile = string_cat(poFile1, filename); oFile = string_rep_ext(poFile, ".png", ".o"); free(poFile); free(poFile1); symbol1 = string_rep_ext(filename, ".png", ""); symbol = string_cat("media_", symbol1); free(symbol1); mkdir_rec(oFile); mkdir_rec(cFile); /* Build the image into Base64 */ arguments[i++] = "tools/out/b64"; arguments[i++] = png; arguments[i++] = symbol; arguments[i++] = cFile; arguments[i++] = NULL; if (exec_code(arguments[0], arguments)) { free(symbol); free(cFile); free(oFile); return NULL; } /* Compile it out */ i = 0; arguments[i++] = Compiler(info); arguments[i++] = "-c"; arguments[i++] = cFile; arguments[i++] = "-o"; arguments[i++] = oFile; arguments[i++] = NULL; if (exec_code(arguments[0], arguments)) { free(symbol); free(cFile); free(oFile); return NULL; } free(symbol); free(oFile); return cFile; } /* Builds the entirety of Parsee. */ static int main_build(int argc, char *argv[]) { buildinfo_t info = { 0 }; FILE *buildinfo = NULL; char *line = NULL; size_t size, i; ssize_t nread; str_array_t *sources, *images; /* Step 1: Get all basic information from build.conf */ if (!(buildinfo = fopen(DEFAULT_BUILD_PATH, "r"))) { printf("error: cannot open '%s'\n", DEFAULT_BUILD_PATH); goto fail; } while ((nread = getline(&line, &size, buildinfo)) != -1) { char *eq = strchr(line, '='); char *end = strchr(line, '\n'); char *key, *val; if (!eq) { free(line); printf( "error: line in '%s' does not contain '='\n", DEFAULT_BUILD_PATH ); goto fail; } /* Set delimiters */ *eq = '\0'; if (end) *end = '\0'; key = line; val = eq + 1; /* Now, we have KV mappings. */ #define If(k, v, d) do \ { \ if (!strcmp(key, k) && !info.v) \ { \ info.v = string_dup(val); \ printf("%s: %s\n", d, val); \ } \ } while (0) If("CODE", basic.codename, "Codename"); If("NAME", basic.name, "Name"); If("VERSION", basic.version, "Version"); If("BINARY", basic.binary, "Binary name"); If("INCLUDES", basic.inc, "Include path"); If("SOURCE", basic.src, "Source path"); If("OBJECT", basic.obj, "Object path"); If("CC", basic.cc, "Compiler"); } if (line) { free(line); line = NULL; } fclose(buildinfo); buildinfo = NULL; /* Step 2: Get all information from commands. */ if (!(info.repo = cmd_stdout("git remote get-url origin"))) { printf("error: cannot find origins url\n"); goto fail; } info.repo = trim_nl(info.repo); /* Step 3: Build all utilities. */ sources = collect_sources("tools", true, ".c"); for (i = 0; i < str_array_len(sources); i++) { char *file = str_array_get(sources, i); printf("\tTOOL %s...\n", file); if (!build_file(file, info, true)) { str_array_free(sources); goto fail; } } str_array_free(sources); /* Step 4: Build all media files. */ sources = collect_sources(info.basic.src, true, ".c"); images = collect_sources("etc/media", true, ".png"); for (i = 0; i < str_array_len(images); i++) { char *file = str_array_get(images, i); char *out; out = process_png(file, info); if (!out) { str_array_free(images); str_array_free(sources); goto fail; } printf("\tPNG %s\n", file); str_array_add(sources, out); free(out); } str_array_free(images); /* Step 5: Build all of Parsee itself */ for (i = 0; i < str_array_len(sources); i++) { char *file = str_array_get(sources, i); printf("\tCC %s...\n", file); if (!build_file(file, info, false)) { str_array_free(sources); goto fail; } } printf("\tLINK\n"); if (!finalise_file(sources, info)) { str_array_free(sources); goto fail; } str_array_free(sources); /* Step 6: Build every Ayadoc */ /* Step 7: Good! */ destroy_buildinfo(&info); return EXIT_SUCCESS; fail: if (buildinfo) { fclose(buildinfo); buildinfo = NULL; } destroy_buildinfo(&info); return EXIT_FAILURE; } int main(int argc, char* argv[]) { /* TODO: Multiple flags(build/install/ayadoc/...) */ return main_build(argc, argv); }