fmake.c (7252B)
1 /* See LICENSE file for copyright and license details. */ 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <string.h> 5 #ifdef _WIN32 6 #include <windows.h> 7 #include <process.h> 8 #else 9 #include <unistd.h> 10 #include <dirent.h> 11 #endif 12 13 #ifdef _WIN32 14 static HANDLE dir; 15 static WIN32_FIND_DATA entry; 16 #else 17 static DIR *dir; 18 static struct dirent *entry; 19 #endif 20 21 typedef enum { 22 FM_FIL = 1, 23 FM_DIR = 2, 24 FM_EXT = 4, 25 } filetype_t; 26 27 typedef enum { 28 FM_NOP = 0, /* no-op */ 29 FM_CBB = 1, /* chdir before building */ 30 FM_CAB = 2, /* chdir after building */ 31 FM_MKD = 4, /* mkdir */ 32 } operation_t; 33 34 typedef struct maker_config { 35 short level; 36 filetype_t filetype; 37 operation_t operation; 38 const char *filename; 39 const char *cmd; 40 const char *args[32]; 41 size_t args_len; 42 const struct maker_config *chain; 43 } maker_config_t; 44 45 #include "config.h" 46 47 static const size_t makers_len = sizeof(makers) / sizeof(maker_config_t); 48 static maker_config_t maker; 49 50 static short 51 should_execute_commands = 1, /* if 0 commands will be printed to stdout */ 52 level_requested = 1, /* -1 indicates no level requested */ 53 is_verbose = 0; 54 bs_i = 0; 55 56 char *errormsg; 57 size_t errormsg_len; 58 59 #define info(...) \ 60 if (is_verbose) { \ 61 fprintf(stderr, "fmake: " __VA_ARGS__); \ 62 } 63 64 #define errprint(...) \ 65 fprintf(stderr, "fmake: " __VA_ARGS__); 66 67 inline int 68 is_file_present(short filetype, const char* name, size_t name_len) { 69 switch(filetype) { 70 #ifdef _WIN32 71 case FM_FIL: 72 case FM_EXT: 73 dir = FindFirstFile(name, &entry); 74 if (dir == INVALID_HANDLE_VALUE) { 75 return -1; 76 } 77 goto FMAKE_FOUND_MATCH; 78 break; 79 #else 80 case FMAKE_FIL: 81 while ((entry = readdir(dir)) != NULL) { 82 if (entry->d_type == DT_REG || entry->d_type == DT_LNK) { 83 if (strcmp(entry->d_name, name) == 0) { 84 goto FMAKE_FOUND_MATCH; 85 } 86 } 87 } 88 break; 89 case FM_EXT: 90 while ((entry = readdir(dir)) != NULL) { 91 if (entry->d_type == DT_REG || entry->d_type == DT_LNK) { 92 char* dot = strrchr(entry->d_name, '.'); 93 if (dot && strcmp(dot + 1, name) == 0) { 94 goto FMAKE_FOUND_MATCH; 95 } 96 } 97 } 98 break; 99 #endif 100 case FM_DIR: 101 if (access(name, 0) == 0) { 102 goto FMAKE_FOUND_MATCH; 103 } 104 break; 105 } 106 return -1; 107 FMAKE_FOUND_MATCH: 108 #ifndef _WIN32 109 if (dir) closedir(dir); 110 #endif 111 return 0; 112 } 113 114 int 115 check_files(int maker_i) { 116 do { 117 maker = makers[maker_i]; 118 if (is_file_present(maker.filetype, 119 maker.filename, strlen(maker.filename)) == 0) 120 break; 121 122 info("%s\n", maker.filename); 123 } while(++maker_i < makers_len || makers[maker_i].level <= level_requested); 124 125 if (maker_i == makers_len) maker_i = 0; 126 return maker_i; 127 } 128 129 int 130 process_build(int argc, char* argv[]) { 131 int status = -1; 132 FILE* fd = should_execute_commands? stderr : stdout; 133 STARTUPINFO si = {0}; 134 PROCESS_INFORMATION pi = {0}; 135 int tmpint; 136 size_t cmdargs_len = 0; 137 138 if (should_execute_commands && (maker.operation & FM_MKD)) { 139 errprint("Running mkdir for 'out'\n"); 140 if (CreateDirectory("out", NULL) == 0) { 141 switch(GetLastError()) { 142 case ERROR_ALREADY_EXISTS: 143 break; 144 case ERROR_PATH_NOT_FOUND: 145 errprint("Cannot create 'out' directory\n"); 146 default: 147 return -1; 148 } 149 } 150 } 151 152 if (maker.operation & FM_CBB) { 153 errprint("Entering directory 'out'\n"); 154 if (chdir("out") != 0) { 155 perror("fmake"); 156 return -1; 157 } 158 } 159 160 if (should_execute_commands) fprintf(fd, "++ "); 161 for(size_t args_i = 0; args_i < maker.args_len && maker.args[args_i] != NULL; args_i++) { 162 tmpint = strlen(maker.args[args_i]); 163 /* +1 for space */ 164 cmdargs_len += tmpint + 1; 165 fprintf(fd, "%.*s ", tmpint, maker.args[args_i]); 166 } 167 if (should_execute_commands) fprintf(fd, "\n"); 168 fflush(fd); 169 170 if (!should_execute_commands) return 0; 171 172 #ifdef _WIN32 173 char *cmdargs = (char *)malloc(cmdargs_len); 174 tmpint = 0; 175 176 /* Windows can't accept argv[] style args. This converts it to a string */ 177 for(int i = 0; i < maker.args_len && maker.args[i] != NULL; i++) { 178 size_t cmdargs_pointer = (size_t) maker.args[i]; 179 strcpy(&cmdargs[tmpint], maker.args[i]); 180 tmpint += strlen(maker.args[i]); 181 cmdargs[tmpint++] = ' '; 182 } 183 cmdargs[cmdargs_len] = '\0'; 184 185 if (!CreateProcess( NULL, cmdargs, NULL, NULL, FALSE, 0, NULL, NULL, 186 &si, &pi)) { 187 if (GetLastError() == ERROR_FILE_NOT_FOUND) { 188 errprint("%s: not found\n", maker.cmd); 189 } 190 return -1; 191 } 192 193 WaitForSingleObject(pi.hProcess, INFINITE); 194 195 if (GetExitCodeProcess(pi.hProcess, &status) == 0) { 196 errprint("fatal: cannot get process status for %d", pi.dwProcessId); 197 return -1; 198 } 199 200 CloseHandle(pi.hProcess); 201 CloseHandle(pi.hThread); 202 203 #else 204 if (*maker.args[0] == '\0') { 205 status = execlp(maker.cmd, maker.cmd, '\0', (void*)0); 206 } 207 else { 208 status = execvp(maker.cmd, maker.args); 209 } 210 #endif 211 212 if (status != 0) { 213 info("'%s' exited with non-zero exit code\n", maker.cmd); 214 return status; 215 } 216 217 if (maker.operation & FM_CAB) { 218 errprint("Entering directory 'out'\n"); 219 if (chdir("out") != 0) { 220 perror("fmake"); 221 return -1; 222 } 223 } 224 225 if (maker.level > 1) { 226 maker = maker.chain? *maker.chain : 227 makers[check_files(maker.level - 1)]; 228 return process_build(argc, argv); 229 } 230 return 0; 231 } 232 233 void 234 fmake_usage(int status) { 235 printf("Usage: fmake [options] [target] ...\n"); 236 printf("Options:\n"); 237 char* opt_array[] = { 238 "-?", "Prints fmake usage", 239 "-C path", "Change to directory before calling the programs", 240 "-D", "Print various types of debugging information.", 241 "-N", "Don't actually run any build commands; just print them.", 242 "-V", "Print the version number of make and exit." 243 }; 244 for(int i = 0; i < sizeof(opt_array) / sizeof(char**); i += 2) { 245 printf(" %-27s %s\n", opt_array[i], opt_array[i + 1]); 246 } 247 exit(status); 248 } 249 250 int 251 main(int argc, char* argv[]) { 252 char working_dir[256]; 253 for(bs_i = 1; bs_i < argc; bs_i++) { 254 if (!(argv[bs_i][0] == '-' && argv[bs_i][1] != '\0')) { 255 exit(-1); 256 } 257 switch(argv[bs_i][1]) { 258 case '-': 259 goto FMAKE_AFTER_ARG_CHECK; 260 break; 261 case 'l': 262 // TODO show better listing 263 /* list supported build systems */ 264 for (size_t bs_i = 0; bs_i < makers_len; bs_i++) { 265 maker = makers[bs_i]; 266 printf("%-5d %-5d %-25s %-20s", maker.level, maker.filetype, maker.filename, maker.cmd); 267 for(size_t i = 1; i < maker.args_len; i++) printf("%s ", maker.args[i]); 268 printf("\n"); 269 } 270 break; 271 case 'C': 272 if (++bs_i == argc) { 273 fprintf(stderr, "fmake.exe: Option requires an argument\n"); 274 fmake_usage(-1); 275 } 276 if (chdir(argv[bs_i]) == -1){ 277 errprint("fmake: *** %s: %s. Stop.\n", argv[bs_i], strerror(errno)); 278 return -1; 279 } 280 break; 281 case '1': 282 case '2': 283 case '3': 284 level_requested = atoi(&argv[bs_i][1]); 285 break; 286 case 'N': 287 should_execute_commands = 0; 288 break; 289 case 'V': 290 printf("fmake " FMAKE_VERSION); 291 return 0; 292 break; 293 case 'D': 294 is_verbose=1; 295 break; 296 case '?': 297 fmake_usage(0); 298 break; 299 } 300 } 301 302 FMAKE_AFTER_ARG_CHECK: 303 argc = argc - bs_i; 304 argv = argv + bs_i; 305 bs_i = -1; 306 307 #ifndef _WIN32 308 dir = opendir("./"); 309 if (dir == NULL) { 310 perror("fmake"); 311 return -1; 312 } 313 #endif 314 315 /* foward through levels if requested */ 316 while(makers[++bs_i].level < level_requested); 317 318 maker = makers[check_files(bs_i)]; 319 320 return process_build(argc, argv); 321 }