#include #include #include #include #include #include #include #include #include #include #include #include #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"; bool can_run_command(const char *cmd) { if(strchr(cmd, '/')) { return access(cmd, X_OK)==0; } const char *path = getenv("PATH"); if(!path) return false; char *buf = malloc(strlen(path)+strlen(cmd)+3); if(!buf) return false; for(; *path; ++path) { char *p = buf; for(; *path && *path!=':'; ++path,++p) *p = *path; if(p==buf) *p++='.'; if(p[-1]!='/') *p++='/'; strcpy(p, cmd); if(access(buf, X_OK)==0) { free(buf); return true; } if(!*path) break; } free(buf); return false; } #define BUFFER_SIZE 1024 bool copy_directory(const char *src_path, const char *dest_path) { DIR *src_dir = opendir(src_path); if (src_dir == NULL) { // Failed to open source directory return false; } // Create destination directory if it doesn't exist mkdir(dest_path, 0755); // Copy files and directories from source to destination struct dirent *entry; while ((entry = readdir(src_dir)) != NULL) { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { // Skip "." and ".." directories continue; } char src_file_path[BUFFER_SIZE]; snprintf(src_file_path, BUFFER_SIZE, "%s/%s", src_path, entry->d_name); char dest_file_path[BUFFER_SIZE]; snprintf(dest_file_path, BUFFER_SIZE, "%s/%s", dest_path, entry->d_name); struct stat src_file_stat; stat(src_file_path, &src_file_stat); if (S_ISDIR(src_file_stat.st_mode)) { // Recursively copy directories if (!copy_directory(src_file_path, dest_file_path)) { return false; } } else if (S_ISREG(src_file_stat.st_mode)) { // Copy regular files FILE *src_file = fopen(src_file_path, "rb"); if (src_file == NULL) { // Failed to open source file return false; } FILE *dest_file = fopen(dest_file_path, "wb"); if (dest_file == NULL) { // Failed to open destination file fclose(src_file); return false; } // Copy file contents char buffer[BUFFER_SIZE]; size_t bytes_read; while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, src_file)) > 0) { fwrite(buffer, 1, bytes_read, dest_file); } fclose(src_file); fclose(dest_file); } else { // Skip other file types continue; } } closedir(src_dir); return true; } void clear_directory(char const *path) { DIR *dir = opendir(path); if (dir == NULL) { // Unable to open the directory perror("opendir"); return; } struct dirent *entry; while ((entry = readdir(dir)) != NULL) { // Skip "." and ".." entries if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { continue; } // Get the full path of the entry char entry_path[1024]; snprintf(entry_path, sizeof(entry_path), "%s/%s", path, entry->d_name); // Check if the entry is a directory struct stat entry_stat; if (stat(entry_path, &entry_stat) != 0) { perror("stat"); continue; } if (S_ISDIR(entry_stat.st_mode)) { // Remove the directory recursively clear_directory(entry_path); if (rmdir(entry_path) != 0) { perror("rmdir"); } } else { // Remove the entry if (unlink(entry_path) != 0) { perror("unlink"); } } } closedir(dir); } #define MANIFEST_FNAME "tos_project.toml" char* find_project_root(void) { char* result = NULL; // Get the current working directory char cwd[1024]; if (getcwd(cwd, sizeof(cwd)) == NULL) { return NULL; } // Check if the current working directory contains the file "tos_project.toml" size_t file_path_len = strlen(cwd) + strlen("/tos_project.toml") + 1; char file_path[file_path_len]; snprintf(file_path, file_path_len, "%s/tos_project.toml", cwd); if (access(file_path, F_OK) == 0) { // The file exists, so we can return the path to the directory containing it result = malloc(strlen(cwd) + 1); strncpy(result, cwd, strlen(cwd) + 1); return result; } // The file does not exist in the current working directory, so we need to search backwards size_t len = strlen(cwd); for (int i = len - 1; i >= 0; i--) { if (cwd[i] == '/') { // We found a directory separator, so we can check if the parent directory contains the file cwd[i] = '\0'; snprintf(file_path, file_path_len, "%s/tos_project.toml", cwd); if (access(file_path, F_OK) == 0) { // The file exists, so we can return the path to the parent directory containing it result = malloc(strlen(cwd) + 1); strncpy(result, cwd, strlen(cwd) + 1); break; } } } return result; } #define SAMPLE_MANIFEST "[General]\n" \ "Name=\"%s\"\n" \ "Author=\"%s\"\n" \ "Version=\"0.1\"\n" \ "\n" \ "[Dependencies]\n" typedef struct { char *name, *uri; } dependency; #define MAX_DEP 20 typedef struct project_manifest { char *name, *author, *version; dependency dependencies[MAX_DEP]; size_t dependencies_amount; } project_manifest; void free_manifest(project_manifest *manifest) { int i; free(manifest->name); free(manifest->author); free(manifest->version); for (i = 0; i < manifest->dependencies_amount; i++) { free(manifest->dependencies[i].name); free(manifest->dependencies[i].uri); } } project_manifest* load_manifest(char const *path) { FILE* fp; int i; char errbuf[200]; fp = fopen(path, "r"); if (!fp) { fputs("Error: Cannot load manifest file: Cannot open file.\n", stderr); return NULL; } project_manifest *new = calloc(1, sizeof(project_manifest)); toml_table_t *conf = toml_parse_file(fp, errbuf, sizeof(errbuf)); fclose(fp); if (!conf) { 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, 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, 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, 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, RED "Error: Cannot load manifest file: Cannot find Version field.\n" RESET); return NULL; } new->name = name.u.s; new->author = author.u.s; new->version = version.u.s; new->dependencies_amount = 0; // FIXME: Add dependencies. toml_table_t *dependencies = toml_table_in(conf, "Dependencies"); if (!dependencies) { fprintf(stderr, RED "Error: Cannot load manifest file: Cannot find [Dependencies] table.\n" RESET); return NULL; } for (i = 0; ; i++) { int j; char const *key = toml_key_in(dependencies, i); if (!key) break; toml_datum_t dep_uri = toml_string_in(dependencies, key); new->dependencies_amount++; char *name = calloc(1, sizeof(char) * (strlen(key) + 1)); strcpy(name, key); char *url = calloc(1, sizeof(char) * (strlen(dep_uri.u.s) + 1)); strcpy(url, dep_uri.u.s); // HACK: Just replace the system command... for (j = 0; j < strlen(name); j++) if (name[j] == '\'') name[j] = ' '; for (j = 0; j < strlen(url); j++) if (url[j] == '\'') url[j] = ' '; dependency dep = { name, url }; new->dependencies[i] = dep; } toml_free(conf); return new; } char buffer_text_format[2048]; char *text_format(char const *format, ...) { va_list args; va_start(args, format); vsprintf(buffer_text_format, format, args); va_end(args); return buffer_text_format; } char *get_username(void) { #if defined(_WIN32) // FIXME: This leaks memory. TCHAR infoBuf[105]; DWORD bufCharCount = 105; if( !GetUserName( infoBuf, &bufCharCount ) ) printError( TEXT("GetUserName") ); char *c_szText[105]; wcstombs(c_szText, infoBuf, wcslen(infoBuf) + 1); return c_szText; #else uid_t uid = geteuid(); struct passwd *pw = getpwuid(uid); if (pw == NULL) return NULL; return pw->pw_name; #endif } bool create_manifest_file(char const *project_path) { int i; char *name; char cwd[4096]; if (strlen(project_path) == 1 && project_path[0] == '.') { if (getcwd(cwd, sizeof(cwd)) == NULL) { fputs("Error: Cannot create manifest: Cannot get current working directory.\n", stderr); return false; } else { name = basename(cwd); } } else { name = basename((char *)project_path); } char *uname = get_username(); if (!uname) { fputs("Error: Cannot create manifest: Cannot get username.\n", stderr); return false; } FILE *fd = fopen(text_format("%s/" MANIFEST_FNAME, project_path), "w+"); if (fd == NULL) { fputs("Error: Cannot create manifest: Cannot open manifest file.\n", stderr); return false; } fprintf(fd, SAMPLE_MANIFEST, name, uname); fclose(fd); return true; } 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] [--watch|-w|-W] - Build project in current working directory.\n", stderr); fputs(" * clean - Clean output code in current working directory.\n", stderr); fputs("\nTo read more about the manifest file, check out tbuild_manifest(1).\n", stderr); } bool file_exists(char const *path) { struct stat st = {0}; return (stat(path, &st) != -1); } // FIXME: Implement for Windows. bool makedir(char const *path) { struct stat st = {0}; if (stat(path, &st) == -1) { mkdir(path, 0700); } else { fprintf(stderr, YELLOW "Warning: File already exists. Continuing anyway." RESET); return false; } return true; } bool makedir_parenting(char const *path) { char *p; struct stat st = {0}; for(p=strchr(path+1, '/'); p; p=strchr(p+1, '/')){ *p = 0; if(stat(path, &st) == -1 && makedir(path) == true) return true; *p = '/'; } if(stat(path, &st) == -1) makedir(path); return true; } void replace_in_file(char const *file, char const *text_to_find, char const *text_to_replace) { FILE *input = fopen(file, "r"); FILE *output = fopen("temp.txt", "w"); char buffer[512]; while (fgets(buffer, sizeof(buffer), input) != NULL) { char *pos = strstr(buffer, text_to_find); if (pos == NULL) { fputs(buffer, output); continue; } char *temp = calloc(strlen(buffer) - strlen(text_to_find) + strlen(text_to_replace) + 1, 1); memcpy(temp, buffer, pos - buffer); memcpy(temp + (pos - buffer), text_to_replace, strlen(text_to_replace)); memcpy(temp + (pos - buffer) + strlen(text_to_replace), pos + strlen(text_to_find), 1 + strlen(buffer) - ((pos - buffer) + strlen(text_to_find))); fputs(temp, output); free(temp); } fclose(output); fclose(input); rename("temp.txt", file); } void convert_to_zealos(char const *path) { DIR *dir; struct dirent *entry; struct stat s; if (!(dir = opendir(path))) return; if (!(entry = readdir(dir))) return; do { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; char fpath[1024]; int len = snprintf(fpath, sizeof(fpath)-1, "%s/%s", path, entry->d_name); fpath[len] = 0; if (lstat(fpath, &s) == 0 && S_ISDIR(s.st_mode)) { // Is directory? convert_to_zealos(fpath); continue; } if (fpath[len-2] != 'H' || fpath[len-3] != '.') continue; printf(" -> Converting %s\n", entry->d_name); // Credit: ZealOS ConversionScript. replace_in_file(fpath, "MemCpy", "MemCopy"); replace_in_file(fpath, "MemCpy", "MemCopy"); replace_in_file(fpath, "MemCmp", "MemCompare"); replace_in_file(fpath, "StrCpy", "StrCopy"); replace_in_file(fpath, "StrCmp", "StrCompare"); replace_in_file(fpath, "StrICmp", "StrICompare"); replace_in_file(fpath, "StrNCmp", "StrNCompare"); replace_in_file(fpath, "StrNICmp", "StrNICompare"); replace_in_file(fpath, "BEqu", "BEqual"); replace_in_file(fpath, "LBEqu", "LBEqual"); replace_in_file(fpath, "ms", "mouse"); replace_in_file(fpath, "Snd", "Sound"); replace_in_file(fpath, "SndTaskEndCB", "SoundTaskEndCB"); replace_in_file(fpath, "mp_cnt", "mp_count"); replace_in_file(fpath, "QueIns", "QueueInsert"); replace_in_file(fpath, "QueInit", "QueueInit"); replace_in_file(fpath, "QueRem", "QueueRemove"); replace_in_file(fpath, "QueDel", "QueueDel"); replace_in_file(fpath, "MsSet", "MouseSet"); replace_in_file(fpath, "UnusedStk", "UnusedStack"); replace_in_file(fpath, "word_lst", "word_list"); replace_in_file(fpath, "FileExtRem", "FileExtRemove"); replace_in_file(fpath, "cnts", "counts"); replace_in_file(fpath, "PostMsg", "MessagePost"); replace_in_file(fpath, "PostMsgWait", "MessagePostWait"); replace_in_file(fpath, "QSort", "QuickSort"); replace_in_file(fpath, "QSortI64", "QuickSortI64"); replace_in_file(fpath, "ScanMsg", "MessageScan"); replace_in_file(fpath, "cnts", "counts"); replace_in_file(fpath, "Dsk", "Disk"); replace_in_file(fpath, "collision_cnt", "collision_count"); replace_in_file(fpath, "Drv", "Drive"); replace_in_file(fpath, "DrvRep", "DriveRep"); replace_in_file(fpath, "Drv2Let", "Drive2Letter"); replace_in_file(fpath, "LstSub", "ListSub"); replace_in_file(fpath, "LstMatch", "ListMatch"); replace_in_file(fpath, "DefineLstLoad", "DefineListLoad"); replace_in_file(fpath, "ExtDft", "ExtDefault"); replace_in_file(fpath, "ExtChg", "ExtChange"); replace_in_file(fpath, "RegDft", "RegDefault"); replace_in_file(fpath, "\"HC\"", "\"CC\""); replace_in_file(fpath, "CDrv", "CDrive"); replace_in_file(fpath, "CDbgInfo", "CDebugInfo"); replace_in_file(fpath, "dbg_info", "debug_info"); replace_in_file(fpath, "StrFirstRem", "StrFirstRemove"); replace_in_file(fpath, "StrLastRem", "StrLastRemove"); replace_in_file(fpath, "TempleOS/Apps", "/Apps"); replace_in_file(fpath, "adam_task", "sys_task"); replace_in_file(fpath, "JobQue", "JobQueue"); replace_in_file(fpath, "MSG_", "MESSAGE_"); replace_in_file(fpath, ".HC", ".ZC"); replace_in_file(fpath, "Msg", "Message"); replace_in_file(fpath, "MusicSettingsRst", "MusicSettingsReset"); replace_in_file(fpath, "hndlr", "handler"); replace_in_file(fpath, "FifoU8Rem", "FifoU8Remove"); replace_in_file(fpath, "GodBitsIns", "GodBitsInsert"); replace_in_file(fpath, "fp_draw_ms", "fp_draw_mouse"); replace_in_file(fpath, "DrawStdMs", "DrawStdMouse"); replace_in_file(fpath, "WIG_TASK_DFT", "WIG_TASK_DEFAULT"); replace_in_file(fpath, "DirMk", "DirMake"); replace_in_file(fpath, "GetI64", "I64Get"); replace_in_file(fpath, "GetF64", "F64Get"); replace_in_file(fpath, "GetStr", "StrGet"); replace_in_file(fpath, "GetChar", "CharGet"); // Added from Anfintony's Insecticide November 24 2022 replace_in_file(fpath, "GetMsg", "MessageGet"); replace_in_file(fpath, "DRV_SIGNATURE_VAL", "DRIVE_SIGNATURE_VAL"); replace_in_file(fpath, "dv_signature", "drive_signature"); replace_in_file(fpath, "DrvTextAttrGet", "DriveTextAttrGet"); replace_in_file(fpath, "DrvIsWritable", "DriveIsWritable"); replace_in_file(fpath, "gr_palette_std", "gr32_palette_std"); replace_in_file(fpath, "GetKey", "KeyGet"); replace_in_file(fpath, "STD_DISTRO_DVD_CFG", "STD_DISTRO_DVD_CONFIG"); replace_in_file(fpath, "CBGR48", "CBGR24"); replace_in_file(fpath, "CFreeLst", "CFreeList"); replace_in_file(fpath, "DrvLock", "DriveLock"); replace_in_file(fpath, "DrvUnlock", "DriveUnlock"); replace_in_file(fpath, "DrvChk", "DriveCheck"); replace_in_file(fpath, "AMAlloc", "SysMAlloc"); replace_in_file(fpath, "Let2Drv", "Letter2Drive"); replace_in_file(fpath, "Let2Let", "Letter2Letter"); replace_in_file(fpath, "Let2BlkDev", "Letter2BlkDev"); replace_in_file(fpath, "first_drv_let", "first_drive_let"); // Added by Doodguy and Anfintony November 25 2022 replace_in_file(fpath, "ScanKey", "KeyScan"); replace_in_file(fpath, "ScanChar", "CharScan"); replace_in_file(fpath, "fp_final_scrn_update", "fp_final_screen_update"); entry->d_name[strlen(entry->d_name)-2] = 'Z'; rename(fpath, text_format("%s/%s", path, entry->d_name)); } while ((entry = readdir(dir))); closedir(dir); } bool run_scripts(char const *path) { DIR *dir; struct dirent *entry; struct stat s; if (!(dir = opendir(path))) return false; if (!(entry = readdir(dir))) return false; do { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; char fpath[1024]; int len = snprintf(fpath, sizeof(fpath)-1, "%s/%s", path, entry->d_name); fpath[len] = 0; if (lstat(fpath, &s) == 0 && S_ISDIR(s.st_mode)) { // Is directory? run_scripts(fpath); continue; } if (fpath[len-3] != 'p' || fpath[len-2] != 'y' || fpath[len-3] != '.') continue; printf(" -> Running %s\n", entry->d_name); char *argv[] = { fpath, NULL }; execve(python_interpreter_path, argv, NULL); } while ((entry = readdir(dir))); closedir(dir); 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"); print_help(argv); return 0; } char path_save[PATH_MAX]; char abs_exe_path[PATH_MAX]; char *p; if(!(p = strrchr(argv[0], '/'))) getcwd(abs_exe_path, sizeof(abs_exe_path)); else { *p = '\0'; getcwd(path_save, sizeof(path_save)); chdir(argv[0]); getcwd(abs_exe_path, sizeof(abs_exe_path)); chdir(path_save); } char *project_path = "."; char cmd = tolower(argv[1][0]); if (cmd == 'i') { if (argc > 2) { project_path = calloc(1, (strlen(argv[2])+1)*sizeof(char)); strcpy(project_path, argv[2]); } 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(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(RED "Error: Cannot create project: Project already exists!\n" RESET, stderr); return -1; } create_manifest_file(project_path); FILE *fp = fopen(text_format("%s/.gitignore", project_path), "w+"); if (fp) { fputs("build\nout\n", fp); fclose(fp); } else { fputs("Warning: Cannot open .gitignore!\n", stderr); } text_format("%s/src", project_path); char *src_dir = malloc((strlen(buffer_text_format)+1)*sizeof(char)); strcpy(src_dir, buffer_text_format); if (!file_exists(src_dir)) { bool status = makedir(src_dir); if (!status) { fputs("Error: Cannot initialize project: Cannot create src directory.\n", stderr); return 1; } fp = fopen(text_format("%s/Main.HC", src_dir), "w+"); fputs("U0 Main() {\n\ \"Hello, world!\\n\";\n\ }\n", fp); fclose(fp); fp = fopen(text_format("%s/Load.HC", src_dir), "w+"); fputs("Cd(__DIR__);;\n\ #include \"Main\"\n", fp); fclose(fp); fp = fopen(text_format("%s/Run.HC", src_dir), "w+"); fputs("Cd(__DIR__);;\n\ #include \"Load\"\n\ \"Running...\\n\";\n\ Main;\n", fp); fclose(fp); fp = fopen(text_format("%s/RunCD.HC", src_dir), "w+"); fputs("#include \"Load\"\n\ \"Running from CD...\\n\";\n\ Main;\n", fp); fclose(fp); fp = fopen(text_format("%s/Install.HC", src_dir), "w+"); fputs("// TODO: Add support for multiple partitions, not just C:\n\ if (!FileFind(\"C:/Apps/Main\",,FUF_JUST_DIRS)) {\n\ \"Installing...\\n\";\n\ DirMk(\"C:/Apps/Main\");\n\ } else \"Updating...\\n\";\n\ CopyTree(\"T:/\", \"C:/Apps/Main\");\n\ Del(\"C:/Apps/Main/RunCD.*\");\n\ \"Done!\\n\";\n", fp); fclose(fp); } if (can_run_command("git")) system(text_format("git init %s", project_path)); free(src_dir); // Free only if alloc'ed, "." doesn't count since it is embedded in the program itself. // Trying to free it if it's "." would cause a crash cause of this. if (argc > 2) free(project_path); } else if (cmd == 'b' || cmd == '.') { int i; build_options options = { 0 }; for (int i = 2; i < argc; i++) { if (argv[i][0] != '-') continue; if (argv[i][1] == '-') { if (strcmp(argv[i], "--zeal")) options.zeal_build = true; if (strcmp(argv[i], "--watch")) options.watch = true; continue; } for (int j = 1; j < strlen(argv[i]); j++) { switch (argv[i][j]) { case 'z': case 'Z': options.zeal_build = true; break; case 'w': case 'W': options.watch = true; break; } } } // Find project root. char cwd[4096]; if (getcwd(cwd, sizeof(cwd)) == NULL) { fputs("Error: Cannot build project: Cannot get current working directory.\n", stderr); return 1; } project_path = find_project_root(); if (project_path == NULL) { fputs("Error: Cannot build project: Not in a project directory.\n", stderr); return 1; } int ret = build_project(project_path, options); if (ret != 0) return ret; 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); } free(project_path); } }