Parsee/build.c

863 lines
18 KiB
C

/* 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 <sys/stat.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <stdbool.h>
#include <dirent.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#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 *cflags, *ldflags;
char *cc;
} basic;
char *repo;
} buildinfo_t;
static void
destroy_buildinfo(buildinfo_t *info)
{
if (!info)
{
return;
}
#define FreeIfExistent(v) do \
{ \
if (v) \
{ \
free(v); \
v = NULL; \
} \
} while (0)
FreeIfExistent(info->basic.codename);
FreeIfExistent(info->basic.version);
FreeIfExistent(info->basic.ldflags);
FreeIfExistent(info->basic.binary);
FreeIfExistent(info->basic.cflags);
FreeIfExistent(info->basic.name);
FreeIfExistent(info->basic.src);
FreeIfExistent(info->basic.inc);
FreeIfExistent(info->basic.obj);
FreeIfExistent(info->basic.cc);
FreeIfExistent(info->repo);
}
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;
}
/* We're not meant to ever be there, but TCC is stupid. */
return -1;
}
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 str_array_t *
split(char *dir)
{
str_array_t *ret;
char *start;
if (!dir || strlen(dir) >= PATH_MAX - 1)
{
return NULL;
}
ret = str_array_create();
for (start = dir; 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';
str_array_add(ret, subtmp);
}
return ret;
}
static time_t
mod_date(char *file)
{
struct stat s;
if (stat(file, &s))
{
return (time_t) 0;
}
return s.st_mtime;
}
static bool
build_file(char *cSource, buildinfo_t info, bool isTool)
{
str_array_t *args, *cflags;
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);
if (!isTool && (mod_date(cSource) < mod_date(oFileName)))
{
free(objPath);
free(oFileName);
free(oFile);
return true;
}
args = str_array_create();
if (!isTool)
{
printf("\tCC %s...\n", cSource);
}
str_array_add(args, Compiler(info));
if (isTool)
{
str_array_add(args, "-lCytoplasm");
str_array_add(args, "-I.");
}
else
{
str_array_add(args, "-c");
}
str_array_add(args, cSource);
cflags = split(info.basic.cflags);
for (i = 0; i < str_array_len(cflags); i++)
{
str_array_add(args, str_array_get(cflags, i));
}
str_array_free(cflags);
str_array_add(args, "-o");
str_array_add(args, oFileName);
str_array_add(args, "-I");
str_array_add(args, info.basic.inc);
str_array_add(args, "-I");
str_array_add(args, info.basic.src);
{
char *pre = string_cat("\"", info.basic.version);
char *pos = string_cat(pre, "\"");
vers = string_cat("-DVERSION=", pos);
str_array_add(args, vers);
free(pos);
free(pre);
}
{
char *pre = string_cat("\"", info.basic.name);
char *pos = string_cat(pre, "\"");
name = string_cat("-DNAME=", pos);
str_array_add(args, name);
free(pos);
free(pre);
}
{
char *pre = string_cat("\"", info.basic.codename);
char *pos = string_cat(pre, "\"");
code = string_cat("-DCODE=", pos);
str_array_add(args, code);
free(pos);
free(pre);
}
{
char *pre = string_cat("\"", info.repo);
char *pos = string_cat(pre, "\"");
repo = string_cat("-DREPOSITORY=", pos);
str_array_add(args, repo);
free(pos);
free(pre);
}
str_array_add(args, NULL);
ret = exec_code(Compiler(info), args->values);
str_array_free(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, *ldflags;
size_t i;
bool ret = true;
if (!arr)
{
return false;
}
flags = str_array_create();
str_array_add(flags, Compiler(info));
ldflags = split(info.basic.cflags);
for (i = 0; i < str_array_len(ldflags); i++)
{
str_array_add(flags, str_array_get(ldflags, i));
}
str_array_free(ldflags);
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("CFLAGS", basic.cflags, "C compiler arguments");
If("LDFLAGS", basic.ldflags, "Linker arguments");
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);
if (argc >= 2 && !strcmp(argv[1], "clean"))
{
char *args[8];
size_t i;
unlink(info.basic.binary);
args[i++] = "rm";
args[i++] = "-r";
args[i++] = info.basic.obj;
args[i++] = NULL;
exec_code(args[0], args);
goto end;
}
/* 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);
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);
/* TODO: Step 6: Build every Ayadoc */
end:
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/...) */
if ((argc - 1) < 1)
{
/* No arguments, let's just build. */
return main_build(argc, argv);
}
if (!strcmp(argv[1], "build") ||
!strcmp(argv[1], "clean"))
{
return main_build(argc, argv);
}
printf("%s: unknown verb: %s\n", argv[0], argv[1]);
return EXIT_FAILURE;
}