fmake

make any project just by typing `fmake`
git clone git@getsh.org:fmake.git
Log | Files | Refs | README | LICENSE

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 }