Skip to content

Instantly share code, notes, and snippets.

@pervognsen
Last active May 16, 2018 01:55
Show Gist options
  • Save pervognsen/776accfbedfe4f519d56e3d1ea82703c to your computer and use it in GitHub Desktop.
Save pervognsen/776accfbedfe4f519d56e3d1ea82703c to your computer and use it in GitHub Desktop.
#!/bin/bash
#
# This is a sketch of an experimental design for a build system where project definitions are written in C.
# The build system is packaged as a single shell script which contains the C library code as a heredoc string.
# The shell script creates a concatenation from the library code and the file specified as the first command line argument.
# Then that is compiled and the result is run, which takes any required build-related actions; for this toy implementation,
# the only build action is running the C compiler directly, but it could also generate makefiles or VS solutions like premake.
#
# Note that using some tricks you can write a single file that is both a valid Unix shell script and Windows batch file,
# which combined with cross-platform code for the C library would allow a portable single-file build system solution.
#
# Example usage:
#
# Suppose you have a C project with source files bar.c and baz.c and you want to build an executable called foo.
#
# Create foo.bob:
#
# void setup() {
# c_executable("foo");
# sources("bar.c", "baz.c");
# }
#
# Command line transcript:
#
# $ ./bob foo
# [PROJECT] foo
# Compiling C executable 'foo'
# [COMMAND] gcc -o foo bar.c baz.c
#
# $ ./foo
# STUFF
bob_c=$(cat <<END
#line 34 "bob"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *strf(char *fmt, ...) {
va_list args;
va_start(args, fmt);
size_t size = vsnprintf(NULL, 0, fmt, args) + 1;
va_end(args);
char *str = malloc(size);
va_start(args, fmt);
vsnprintf(str, size, fmt, args);
va_end(args);
return str;
}
void vmsg(char *kind, char *fmt, va_list args) {
printf("%s%*s", kind, (int)(strlen(kind) <= 10 ? 10 - strlen(kind) : 1), "");
vprintf(fmt, args);
printf("\n");
}
void msg(char *kind, char *fmt, ...) {
va_list args;
va_start(args, fmt);
vmsg(kind, fmt, args);
va_end(args);
}
void error(char *fmt, ...) {
va_list args;
va_start(args, fmt);
vmsg("[ERROR]", fmt, args);
va_end(args);
exit(1);
}
void info(char *fmt, ...) {
va_list args;
va_start(args, fmt);
vmsg("", fmt, args);
va_end(args);
}
int run(char *str) {
msg("[COMMAND]", "%s", str);
return system(str);
}
typedef struct {
char *data;
size_t num_items;
size_t max_items;
} array_t;
void array_init(array_t *array, size_t item_size) {
array->num_items = 0;
array->max_items = 16;
array->data = malloc(array->max_items * item_size);
}
void array_add(array_t *array, void *item_ptr, size_t item_size) {
if (array->num_items == array->max_items) {
array->max_items *= 2;
array->data = realloc(array->data, array->max_items * item_size);
}
memcpy(array->data + array->num_items*item_size, item_ptr, item_size);
array->num_items++;
}
typedef struct {
union {
struct {
char **files;
size_t num_files;
};
array_t files_array;
};
} file_list_t;
void file_list_init(file_list_t *file_list) {
array_init(&file_list->files_array, sizeof(char *));
}
void file_list_add(file_list_t *file_list, char *filename) {
array_add(&file_list->files_array, &filename, sizeof(char *));
}
enum {
NONE,
C_EXECUTABLE
};
typedef struct {
int type;
char *name;
file_list_t sources;
} project_t;
void project_init(project_t *project) {
project->type = NONE;
project->name = "<unnamed>";
file_list_init(&project->sources);
}
void project_build(project_t *project) {
if (project->type == NONE) error("Trying to build project '%s' with no type selected.", project->name);
msg("[PROJECT]", "%s", project->name);
if (project->type == C_EXECUTABLE) {
info("Compiling C executable '%s'", project->name);
if (project->sources.num_files == 0) error("No source files specified.");
char *cmd = strf("gcc -o %s", project->name);
for (size_t i = 0; i < project->sources.num_files; i++)
cmd = strf("%s %s", cmd, project->sources.files[i]);
if (run(cmd) != 0) error("Compilation failed.");
}
}
project_t *this_project;
void c_executable(char *name) {
this_project->type = C_EXECUTABLE;
this_project->name = name;
}
void source(char *file) {
file_list_add(&this_project->sources, file);
}
void sources_func(char *file, ...) {
va_list args;
va_start(args, file);
while (file) {
source(file);
file = va_arg(args, char *);
}
va_end(args);
}
#define sources(...) sources_func(__VA_ARGS__, NULL)
void setup();
int main(int argc, char **argv) {
project_t default_project;
project_init(&default_project);
this_project = &default_project;
setup();
project_build(this_project);
return 0;
}
END
)
set -e
function cleanup {
rm -f $tmp_c $tmp_exe
}
trap cleanup EXIT
tmp_c=$(mktemp /tmp/bob-XXXXX.c)
tmp_exe=${tmp_c%.c}
echo "$bob_c" >&$tmp_c
echo "#line 0 \"$1.bob\"" >>$tmp_c
cat $1.bob >>$tmp_c
gcc -std=c99 -o $tmp_exe $tmp_c
eval $tmp_exe
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment