Skip to content

Instantly share code, notes, and snippets.

@judah-caruso
Created February 21, 2023 00:52
Show Gist options
  • Save judah-caruso/622e7815c729fe1581a32699cf24545f to your computer and use it in GitHub Desktop.
Save judah-caruso/622e7815c729fe1581a32699cf24545f to your computer and use it in GitHub Desktop.
/*
Simple program that treats a C/C++ source
file *as* the build script. This is the
step between: compiling a source file
directly, and creating a build script.
License: MIT
@build.cc clang
@build.out build
@build.std c89
@build.arg -Wall -pedantic
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <assert.h>
#define NAME "build"
#define VERSION "1a"
static const char* USAGE = ".. usage %s <build file>\n";
static const char* HELP_HEADER = \
".. version %s\n\n"
" flags\n"
" -help show a detailed help message\n"
" -version show this message\n\n"
;
static const char* HELP_BODY = \
" Comments containing @build.[option] describe the build process.\n\n"
" options\n"
" cc which compiler to use (default: cc)\n"
" out the resulting binary or library name (default: a.out)\n"
" std which version of C/C++ to use (default: c99)\n"
" src source files needed (note: the build file is always passed first)\n"
" lib libraries to link against (note: '-l' is not needed)\n"
" arg arguments that are passed directly\n"
;
#define S(str) str.count, str.data
typedef struct {
char* data;
int count;
} string;
typedef struct {
enum {
C_DETECT,
C_CLANG,
C_GCC,
C_MSVC
} compiler;
char* file; /* The file containing build information.
Always passed as the first source file. */
char* command_buf;
int command_buf_len;
int written;
string cc;
string out;
string std;
string src;
string lib;
string arg;
} Build_Info;
void
cmd_append(Build_Info* info, const char* format, ...)
{
va_list(args);
va_start(args, format);
info->written += vsnprintf(
info->command_buf + info->written,
info->command_buf_len - info->written,
format,
args
);
va_end(args);
}
void
generate_clang_command(Build_Info* info)
{
cmd_append(info, "%.*s ", S(info->cc));
if (info->src.data) {
cmd_append(info, "%.*s ", S(info->src));
}
cmd_append(info, "%s ", info->file);
if (info->out.data) {
cmd_append(info, "-o %.*s ", S(info->out));
}
if (info->std.data) {
cmd_append(info, "-std=%.*s ", S(info->std));
}
{
char* chunk = info->lib.data;
int chunk_len = 0;
int i = 0;
for (i = 0; i < info->lib.count; i += 1) {
chunk_len += 1;
if (info->lib.data[i] == ' ' || i >= info->lib.count - 1) {
cmd_append(info, "-l%.*s ", chunk_len, chunk);
chunk += chunk_len;
chunk_len = 0;
continue;
}
}
}
cmd_append(info, "%.*s", S(info->arg));
}
void
generate_gcc_command(Build_Info* info)
{ assert(0 && "TODO: GCC Command!"); }
void
generate_msvc_command(Build_Info* info)
{ assert(0 && "TODO: MSVC Command!"); }
int
main(int argc, char* argv[])
{
int ret = 0;
FILE* handle = NULL;
size_t total_read = 0;
char* file_mem = NULL;
char* file_name = NULL;
char* file_ext = NULL;
size_t file_size = 0;
int valid_ext = 0;
char* iter = NULL;
char* range = NULL;
Build_Info build = {0};
build.cc.data = "cc";
build.cc.count = 2;
build.std.data = "c99";
build.std.count = 3;
build.out.data = "a.out";
build.out.count = 5;
if (argc <= 1) {
printf(USAGE, NAME);
exit(0);
}
file_name = argv[1];
if (strcmp(file_name, "-version") == 0) {
printf(USAGE, NAME);
printf(HELP_HEADER, VERSION, NAME);
exit(0);
}
else if (strcmp(file_name, "-help") == 0) {
printf(USAGE, NAME);
printf(HELP_HEADER, VERSION, NAME);
puts(HELP_BODY);
exit(0);
}
file_ext = strchr(file_name, '.');
if (!file_ext) {
printf(".. invalid file '%s'\n", file_name);
exit(1);
}
valid_ext =
strcmp(file_ext, ".c") == 0 ||
strcmp(file_ext, ".cpp") == 0 ||
strcmp(file_ext, ".c++") == 0 ||
strcmp(file_ext, ".cxx") == 0 ||
strcmp(file_ext, ".h") == 0 ||
strcmp(file_ext, ".hpp") == 0 ||
strcmp(file_ext, ".h++") == 0 ||
strcmp(file_ext, ".hxx") == 0;
if (!valid_ext) {
printf(".. invalid extension '%s'\n", file_name);
exit(2);
}
handle = fopen(file_name, "rb");
if (!handle) {
printf(".. unable to open '%s'\n", file_name);
exit(3);
}
fseek(handle, 0, SEEK_END);
file_size = ftell(handle);
fseek(handle, 0, SEEK_SET);
file_mem = (char*)malloc(file_size * sizeof(char));
total_read = fread(file_mem, sizeof(char), file_size * sizeof(char), handle);
if (total_read != file_size) {
printf(".. unable to read '%s'\n", file_name);
exit(4);
}
fclose(handle);
iter = file_mem;
build.file = file_name;
while (*iter++) {
if (*iter == '@') {
range = iter;
continue;
}
if (range && *iter == ' ') {
int len = (int)(iter - range);
char* rest = NULL;
int rest_len = 0;
/* skip whitespace */
while (*iter++) if (*iter != ' ') break;
rest = iter;
while (*iter++) if (*iter == '\n') break;
rest_len = (int)(iter - rest);
if (strncmp("@build.cc", range, len) == 0) {
build.cc.data = rest;
build.cc.count = rest_len;
}
else if (strncmp("@build.out", range, len) == 0) {
build.out.data = rest;
build.out.count = rest_len;
}
else if (strncmp("@build.src", range, len) == 0) {
build.src.data = rest;
build.src.count = rest_len;
}
else if (strncmp("@build.std", range, len) == 0) {
build.std.data = rest;
build.std.count = rest_len;
}
else if (strncmp("@build.inc", range, len) == 0) {
build.src.data = rest;
build.src.count = rest_len;
}
else if (strncmp("@build.lib", range, len) == 0) {
build.lib.data = rest;
build.lib.count = rest_len;
}
else if (strncmp("@build.arg", range, len) == 0) {
build.arg.data = rest;
build.arg.count = rest_len;
}
range = NULL;
continue;
}
}
if (build.cc.data) {
if (strncmp("clang", build.cc.data, build.cc.count) == 0 ||
strncmp("clang++", build.cc.data, build.cc.count) == 0) {
build.compiler = C_CLANG;
}
else if (strncmp("gcc", build.cc.data, build.cc.count) == 0 ||
strncmp("g++", build.cc.data, build.cc.count) == 0) {
build.compiler = C_GCC;
}
else if (strncmp("cl", build.cc.data, build.cc.count) == 0) {
build.compiler = C_MSVC;
}
}
{
#define SIZE 1024 * 1024
char buffer[SIZE] = {0};
build.command_buf = buffer;
build.command_buf_len = SIZE;
switch (build.compiler) {
case C_DETECT: {
assert(0 && "TODO: Detect what 'cc' is!");
} break;
case C_CLANG: {
generate_clang_command(&build);
} break;
case C_GCC: {
generate_gcc_command(&build);
} break;
case C_MSVC: {
generate_msvc_command(&build);
} break;
}
printf(".. %s command: %s\n", NAME, buffer);
ret = system(buffer);
}
free(file_mem);
printf(".. %s exit: %d\n", NAME, ret);
return ret;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment