Add file watching and colour coding.

This patch adds a new option to building: -w. What this option does is
it runs the build system on any change detected in either src/ or lib/.
This is a really convenient option since you don't need to type "tbuild
b" every time.

Signed-off-by: xSlendiX <slendi@socopon.com>
This commit is contained in:
xSlendiX 2022-12-24 18:49:14 +02:00
parent f6e8b0f73c
commit 6b995e1582
5 changed files with 227 additions and 123 deletions

5
.gitignore vendored
View File

@ -1,3 +1,2 @@
tbuild
main.o
build
out

3
.gitmodules vendored
View File

@ -1,3 +1,6 @@
[submodule "tomlc99"]
path = tomlc99
url = https://github.com/cktan/tomlc99
[submodule "x-watcher"]
path = x-watcher
url = https://github.com/nikp123/x-watcher

View File

@ -1,5 +1,5 @@
CC=tcc
CFLAGS=-O0 -ggdb -Wall
CC=cc
CFLAGS=-O0 -ggdb -Wall -lpthread
.PHONY: all clean install

337
main.c
View File

@ -12,6 +12,33 @@
#include <stdarg.h>
#include "tomlc99/toml.h"
#include "x-watcher/x-watcher.h"
#if defined(_WIN32)
// FIXME: Find a way to do ANSI on Windows.
#define RED ""
#define GREEN ""
#define YELLOW ""
#define BLUE ""
#define MAGENTA ""
#define CYAN ""
#define RESET ""
#else
#define RED "\e[31m"
#define GREEN "\e[32m"
#define YELLOW "\e[33m"
#define BLUE "\e[34m"
#define MAGENTA "\e[35m"
#define CYAN "\e[36m"
#define RESET "\e[0m"
#endif
typedef struct {
bool zeal_build;
bool watch;
} build_options;
// FIXME: This is hardcoded, fix it.
char *python_interpreter_path = "/usr/local/bin/python3";
@ -241,31 +268,31 @@ project_manifest* load_manifest(char const *path) {
toml_table_t *conf = toml_parse_file(fp, errbuf, sizeof(errbuf));
fclose(fp);
if (!conf) {
fprintf(stderr, "Error: Cannot load manifest file: Cannot parse file: %s\n", errbuf);
fprintf(stderr, RED "Error: Cannot load manifest file: Cannot parse file: %s\n" RESET, errbuf);
return NULL;
}
toml_table_t *general = toml_table_in(conf, "General");
if (!general) {
fprintf(stderr, "Error: Cannot load manifest file: Cannot find [General] table.\n");
fprintf(stderr, RED "Error: Cannot load manifest file: Cannot find [General] table.\n" RESET);
return NULL;
}
toml_datum_t name = toml_string_in(general, "Name");
if (!name.ok) {
fprintf(stderr, "Error: Cannot load manifest file: Cannot find Name field.\n");
fprintf(stderr, RED "Error: Cannot load manifest file: Cannot find Name field.\n" RESET);
return NULL;
}
toml_datum_t author = toml_string_in(general, "Author");
if (!author.ok) {
fprintf(stderr, "Error: Cannot load manifest file: Cannot find Author field.\n");
fprintf(stderr, RED "Error: Cannot load manifest file: Cannot find Author field.\n" RESET);
return NULL;
}
toml_datum_t version = toml_string_in(general, "Version");
if (!version.ok) {
fprintf(stderr, "Error: Cannot load manifest file: Cannot find Version field.\n");
fprintf(stderr, RED "Error: Cannot load manifest file: Cannot find Version field.\n" RESET);
return NULL;
}
@ -278,7 +305,7 @@ project_manifest* load_manifest(char const *path) {
// FIXME: Add dependencies.
toml_table_t *dependencies = toml_table_in(conf, "Dependencies");
if (!dependencies) {
fprintf(stderr, "Error: Cannot load manifest file: Cannot find [Dependencies] table.\n");
fprintf(stderr, RED "Error: Cannot load manifest file: Cannot find [Dependencies] table.\n" RESET);
return NULL;
}
@ -383,9 +410,9 @@ void print_help(char **argv) {
printf("Usage: %s [command]\n", argv[0]);
fputs("\nCommands:\n", stderr);
fputs(" * init|i [path=.] - Setup a new project.\n", stderr);
fputs(" * build|. [--zeal|-z|-Z] - Build project in current working directory.\n", stderr);
fputs(" * build|. [--zeal|-z|-Z] [--watch|-w|-W] - Build project in current working directory.\n", stderr);
fputs(" * clean - Clean output code in current working directory.\n", stderr);
fputs("\nTo see manifest file usage, check out man tbuild(1)\n", stderr);
fputs("\nTo read more about the manifest file, check out tbuild_manifest(1).\n", stderr);
}
bool file_exists(char const *path) {
@ -400,7 +427,7 @@ bool makedir(char const *path) {
if (stat(path, &st) == -1) {
mkdir(path, 0700);
} else {
fprintf(stderr, "Warning: File already exists. Continuing anyway.");
fprintf(stderr, YELLOW "Warning: File already exists. Continuing anyway." RESET);
return false;
}
return true;
@ -480,7 +507,7 @@ void convert_to_zealos(char const *path) {
if (fpath[len-2] != 'H' || fpath[len-3] != '.')
continue;
printf("Converting %s\n", entry->d_name);
printf(" -> Converting %s\n", entry->d_name);
// Credit: ZealOS ConversionScript.
replace_in_file(fpath, "MemCpy", "MemCopy");
@ -607,7 +634,7 @@ bool run_scripts(char const *path) {
if (fpath[len-3] != 'p' || fpath[len-2] != 'y' || fpath[len-3] != '.')
continue;
printf("Running %s\n", entry->d_name);
printf(" -> Running %s\n", entry->d_name);
char *argv[] = {
fpath,
NULL
@ -621,6 +648,133 @@ bool run_scripts(char const *path) {
return true;
}
int build_project(char *project_path, build_options options) {
int i;
puts("Starting build...");
project_manifest* manifest = load_manifest(text_format("%s/" MANIFEST_FNAME, project_path));
if (!manifest) {
fputs(RED "Error: Cannot build project: Failed to open manifest.\n" RESET, stderr);
return 1;
}
text_format("%s/lib", project_path);
char *lib_dir = malloc((strlen(buffer_text_format)+1)*sizeof(char));
strcpy(lib_dir, buffer_text_format);
if (!file_exists(lib_dir)) {
bool status = makedir(lib_dir);
if (!status) {
fputs(RED "Error: Cannot build project: Cannot create lib directory.\n" RESET, stderr);
free_manifest(manifest);
return 1;
}
}
for (i = 0; i < manifest->dependencies_amount; i++) {
dependency dep = manifest->dependencies[i];
printf("%s -> %s (%i)\n", dep.name, dep.uri, i);
puts(text_format("%s/%s", lib_dir, dep.name));
if (!file_exists(text_format("%s/%s", lib_dir, dep.name)))
system(text_format("git clone --depth 1 --recursive --shallow-submodules '%s' '%s/%s'", dep.uri, lib_dir, dep.name));
else
system(text_format("cd '%s/%s' && git pull", lib_dir, dep.name));
}
// Create build directory
text_format("%s/build", project_path);
char *build_dir = malloc((strlen(buffer_text_format)+1)*sizeof(char));
strcpy(build_dir, buffer_text_format);
if (!file_exists(build_dir)) {
bool status = makedir(build_dir);
if (!status) {
fputs(RED "Error: Cannot build project: Cannot create build directory.\n" RESET, stderr);
free_manifest(manifest);
return 1;
}
}
text_format("%s/src", project_path);
char *src_dir = malloc((strlen(buffer_text_format)+1)*sizeof(char));
strcpy(src_dir, buffer_text_format);
// Clear and populate.
puts(" -> Populating build directory...");
clear_directory(build_dir);
copy_directory(src_dir, build_dir);
if (file_exists(lib_dir))
copy_directory(lib_dir, build_dir);
// Run scripts (if any)
puts(" -> Running scripts...");
// FIXME: This is incredibly hacky.
text_format("%s/scripts", project_path);
char *scripts_dir = malloc((strlen(buffer_text_format)+1)*sizeof(char));
strcpy(scripts_dir, buffer_text_format);
if (file_exists(scripts_dir)) {
if (!run_scripts(scripts_dir)) {
fputs(RED "Failed running script!\b" RESET, stderr);
return 1;
}
}
if (options.zeal_build) {
puts(" -> Converting to ZealOS...");
convert_to_zealos(build_dir);
}
// TODO: Obfuscate code if enabled.
text_format("%s/output", project_path);
char *out_dir = malloc((strlen(buffer_text_format)+1)*sizeof(char));
strcpy(out_dir, buffer_text_format);
if (!file_exists(out_dir)) {
bool status = makedir(out_dir);
if (!status) {
fputs(RED "Error: Cannot build project: Cannot create output directory.\n" RESET, stderr);
free_manifest(manifest);
return 1;
}
}
if (options.zeal_build)
text_format("%s/output/%s-%s.zeal.ISO.C", project_path, manifest->name, manifest->version);
else
text_format("%s/output/%s-%s.ISO.C", project_path, manifest->name, manifest->version);
puts(" -> Generating ISO.C");
char *iso_c = malloc((strlen(buffer_text_format)+1)*sizeof(char));
strcpy(iso_c, buffer_text_format);
#if defined(_WIN32)
system(text_format("RedSeaGen.exe '%s' '%s'", build_dir, iso_c));
#else
system(text_format("RedSeaGen '%s' '%s'", build_dir, iso_c));
#endif
free(build_dir);
free(out_dir);
free(scripts_dir);
free(lib_dir);
free_manifest(manifest);
puts(GREEN "Build finished!" RESET);
return 0;
}
void watcher_cb(XWATCHER_FILE_EVENT event, char const *path, int context, void *data) {
if (event != XWATCHER_FILE_MODIFIED)
return;
puts(YELLOW "File change detected!" RESET);
struct {
char *project_path;
build_options options;
} *dat = data;
build_project(dat->project_path, dat->options);
}
int main(int argc, char **argv) {
if (argc < 2) {
puts("No arguments provided.\n");
@ -650,20 +804,20 @@ int main(int argc, char **argv) {
strcpy(project_path, argv[2]);
}
fprintf(stderr, "Initializing project in `%s`.\n", project_path);
fprintf(stderr, GREEN "Initializing project in `%s`.\n" RESET, project_path);
bool ret = true;
if (!(strlen(project_path) == 1 && project_path[0] == '.'))
ret = makedir_parenting(project_path);
if (!ret) {
fputs("Error: Cannot create project: Cannot create directories.\n", stderr);
fputs(RED "Error: Cannot create project: Cannot create directories.\n" RESET, stderr);
return -1;
}
// Check if manifest file exists.
if (file_exists(text_format("%s/" MANIFEST_FNAME, project_path))) {
fputs("Error: Cannot create project: Project already exists!\n", stderr);
fputs(RED "Error: Cannot create project: Project already exists!\n" RESET, stderr);
return -1;
}
@ -737,9 +891,7 @@ Del(\"C:/Apps/Main/RunCD.*\");\n\
} else if (cmd == 'b' || cmd == '.') {
int i;
struct {
bool zeal_build;
} options = { 0 };
build_options options = { 0 };
for (int i = 2; i < argc; i++) {
if (argv[i][0] != '-')
@ -748,6 +900,8 @@ Del(\"C:/Apps/Main/RunCD.*\");\n\
if (argv[i][1] == '-') {
if (strcmp(argv[i], "--zeal"))
options.zeal_build = true;
if (strcmp(argv[i], "--watch"))
options.watch = true;
continue;
}
@ -758,6 +912,10 @@ Del(\"C:/Apps/Main/RunCD.*\");\n\
case 'Z':
options.zeal_build = true;
break;
case 'w':
case 'W':
options.watch = true;
break;
}
}
}
@ -775,111 +933,54 @@ Del(\"C:/Apps/Main/RunCD.*\");\n\
return 1;
}
project_manifest* manifest = load_manifest(text_format("%s/" MANIFEST_FNAME, project_path));
if (!manifest) {
fputs("Error: Cannot build project: Failed to open manifest.\n", stderr);
return 1;
}
puts(buffer_text_format);
int ret = build_project(project_path, options);
if (ret != 0)
return ret;
text_format("%s/lib", project_path);
char *lib_dir = malloc((strlen(buffer_text_format)+1)*sizeof(char));
strcpy(lib_dir, buffer_text_format);
if (!file_exists(lib_dir)) {
bool status = makedir(lib_dir);
if (!status) {
fputs("Error: Cannot build project: Cannot create lib directory.\n", stderr);
free_manifest(manifest);
return 1;
}
if (options.watch) {
text_format("%s/src", project_path);
char *src_dir = malloc((strlen(buffer_text_format)+1)*sizeof(char));
strcpy(src_dir, buffer_text_format);
text_format("%s/lib", project_path);
char *lib_dir = malloc((strlen(buffer_text_format)+1)*sizeof(char));
strcpy(lib_dir, buffer_text_format);
x_watcher *watcher = xWatcher_create();
struct {
char *project_path;
build_options options;
} dat = {
project_path,
options
};
xWatcher_reference src;
src.path = src_dir;
src.callback_func = watcher_cb;
src.context = 1;
src.additional_data = &dat;
xWatcher_appendDir(watcher, &src);
xWatcher_reference lib;
lib.path = lib_dir;
lib.callback_func = watcher_cb;
lib.context = 2;
lib.additional_data = &dat;
xWatcher_appendDir(watcher, &lib);
xWatcher_start(watcher);
puts(YELLOW "Listening for file changes..." RESET);
getchar();
xWatcher_destroy(watcher);
free(src_dir);
}
for (i = 0; i < manifest->dependencies_amount; i++) {
dependency dep = manifest->dependencies[i];
printf("%s -> %s (%i)\n", dep.name, dep.uri, i);
puts(text_format("%s/%s", lib_dir, dep.name));
if (!file_exists(text_format("%s/%s", lib_dir, dep.name)))
system(text_format("git clone --depth 1 --recursive --shallow-submodules '%s' '%s/%s'", dep.uri, lib_dir, dep.name));
else
// FIXME: Make this cross platform.
system(text_format("cd '%s/%s' && git pull", lib_dir, dep.name));
}
// Create build directory
text_format("%s/build", project_path);
char *build_dir = malloc((strlen(buffer_text_format)+1)*sizeof(char));
strcpy(build_dir, buffer_text_format);
if (!file_exists(build_dir)) {
bool status = makedir(build_dir);
if (!status) {
fputs("Error: Cannot build project: Cannot create build directory.\n", stderr);
free_manifest(manifest);
return 1;
}
}
text_format("%s/src", project_path);
char *src_dir = malloc((strlen(buffer_text_format)+1)*sizeof(char));
strcpy(src_dir, buffer_text_format);
// Clear and populate.
puts("Populating build directory...");
clear_directory(build_dir);
copy_directory(src_dir, build_dir);
if (file_exists(lib_dir))
copy_directory(lib_dir, build_dir);
// Run scripts (if any)
puts("Running scripts...");
// FIXME: This is incredibly hacky.
text_format("%s/scripts", project_path);
char *scripts_dir = malloc((strlen(buffer_text_format)+1)*sizeof(char));
strcpy(scripts_dir, buffer_text_format);
if (file_exists(scripts_dir)) {
if (!run_scripts(scripts_dir)) {
fputs("Failed running script!\b", stderr);
return -1;
}
}
if (options.zeal_build) {
puts("Converting to ZealOS...");
convert_to_zealos(build_dir);
}
// TODO: Obfuscate code if enabled.
text_format("%s/output", project_path);
char *out_dir = malloc((strlen(buffer_text_format)+1)*sizeof(char));
strcpy(out_dir, buffer_text_format);
if (!file_exists(out_dir)) {
bool status = makedir(out_dir);
if (!status) {
fputs("Error: Cannot build project: Cannot create output directory.\n", stderr);
free_manifest(manifest);
return 1;
}
}
if (options.zeal_build)
text_format("%s/output/%s-%s.zeal.ISO.C", project_path, manifest->name, manifest->version);
else
text_format("%s/output/%s-%s.ISO.C", project_path, manifest->name, manifest->version);
char *iso_c = malloc((strlen(buffer_text_format)+1)*sizeof(char));
strcpy(iso_c, buffer_text_format);
#if defined(_WIN32)
system(text_format("RedSeaGen.exe '%s' '%s'", build_dir, iso_c));
#else
system(text_format("RedSeaGen '%s' '%s'", build_dir, iso_c));
#endif
//abs_exe_path,
free(build_dir);
free(out_dir);
free(scripts_dir);
free(lib_dir);
free(project_path);
free_manifest(manifest);
}
}

1
x-watcher Submodule

@ -0,0 +1 @@
Subproject commit 01e9a4b6b051f240fc68eb0a0e3085c4a209dd31