1990 lines
53 KiB
C
1990 lines
53 KiB
C
/*
|
|
* xmk, an automated build tool
|
|
*
|
|
* 2019 - 2020 Xavier Del Campo Romero <xavi.dcr@tutanota.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public LIcense as publised by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
* MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#ifdef WIN32
|
|
#include <windows.h>
|
|
#elif defined(__unix__)
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#define APP_NAME "xmk"
|
|
#define AUTHORS "Xavier Del Campo Romero"
|
|
#define DEFAULT_FILE_NAME "default.xmk"
|
|
|
|
#if defined(typeof) && (__STDC_VERSION__ >= 201112L)
|
|
/* Provide a safer version which refuses to compile when
|
|
* pointers (instead of arrays) are passed to this macro. */
|
|
# define LENGTHOF(a) \
|
|
_Generic ( &a, \
|
|
typeof (*a) **: (void)0, \
|
|
typeof (*a) *const *: (void)0, \
|
|
default: ARRAY_SIZE_DEFALT(a))
|
|
#else
|
|
# define LENGTHOF(a) (sizeof (a) / sizeof (a[0]))
|
|
#endif
|
|
|
|
#define LOGV(...) logv(__func__, __LINE__, __VA_ARGS__)
|
|
#define LOGVV(...) logvv(__func__, __LINE__, __VA_ARGS__)
|
|
#define FATAL_ERROR(...) fatal_error(__func__, __LINE__, __VA_ARGS__)
|
|
|
|
#define foreach(type, iter, list) \
|
|
for (struct {type *i; char brk;} __a = \
|
|
{.i = list, .brk = 0}; \
|
|
(size_t)(__a.i - list) < LENGTHOF(list) && !__a.brk; \
|
|
__a.i++, __a.brk--) \
|
|
for (type *const iter = __a.i; \
|
|
!__a.brk; \
|
|
__a.brk++)
|
|
|
|
#define STATIC_ASSERT(e) {enum {x = 1 / !!(e)};}
|
|
|
|
static struct config
|
|
{
|
|
const char *path;
|
|
bool preprocess;
|
|
bool verbose;
|
|
bool extra_verbose;
|
|
bool quiet;
|
|
} config;
|
|
|
|
enum parse_state
|
|
{
|
|
SEARCHING,
|
|
CHECKING
|
|
};
|
|
|
|
enum rule
|
|
{
|
|
DEFINE_AS,
|
|
BUILD,
|
|
DEPENDS_ON,
|
|
CREATED_USING,
|
|
TARGET
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
const char *const* const keywords;
|
|
const enum recipe
|
|
{
|
|
KEYWORD,
|
|
SYMBOL,
|
|
LIST,
|
|
NESTED_RULE,
|
|
END
|
|
} *const *const recipe_list;
|
|
|
|
const enum rule *const nested_rules;
|
|
void (*const symbol_callback)(const char *);
|
|
enum parse_state (*const scope_block_opened)(void);
|
|
const char *const scope_block_opened_str;
|
|
char ***list;
|
|
size_t *list_size;
|
|
} syntax_rule;
|
|
|
|
static char *build_target;
|
|
static char *current_scope;
|
|
char *file_buffer;
|
|
static size_t line;
|
|
|
|
/* Definitions store data pairs, so default
|
|
* syntax_rule behaviour cannot be used here. */
|
|
static struct
|
|
{
|
|
char **names;
|
|
char **values;
|
|
size_t n;
|
|
size_t selected_i;
|
|
} defines;
|
|
|
|
static void fatal_error(const char *func, int line, const char *format, ...);
|
|
static int parse_arguments(const int argv, const char *const argc[]);
|
|
static int exec(const struct config *config);
|
|
static void help(void);
|
|
static void set_preprocess(void);
|
|
static void set_verbose(void);
|
|
static void set_extra_verbose(void);
|
|
static void set_input(const char *input);
|
|
static void set_quiet(void);
|
|
static bool verbose(void);
|
|
static bool extra_verbose(void);
|
|
static int parse_file(void);
|
|
static int check_syntax(void);
|
|
static const char *get_word(char *buffer, size_t *from, bool *newline_detected);
|
|
static const char *get_basename(const char *word);
|
|
static const char *get_extension(const char *word);
|
|
static const char *get_dependency(const char *word);
|
|
bool is_define(const char *name);
|
|
char *expand_define(char *const buffer, const char *word);
|
|
static bool check_rule(syntax_rule *rule, const char *word, enum parse_state *state, bool *newline_detected);
|
|
static void add_symbol(syntax_rule *rule, const char *word);
|
|
static void set_build_target(const char *target);
|
|
static void add_target(const char *target);
|
|
static void add_define(const char *define);
|
|
static void create_basic_tree(syntax_rule* dep_rule);
|
|
enum parse_state target_scope_block_opened(void);
|
|
enum parse_state depends_on_scope_block_opened(void);
|
|
static bool scope(syntax_rule *rule, const char *word, enum parse_state *state, bool *finished);
|
|
static bool handle_list(syntax_rule* rule,
|
|
const char *word,
|
|
enum parse_state* state,
|
|
bool newline_detected,
|
|
bool* finished);
|
|
enum parse_state created_using_scope_block_opened(void);
|
|
static int execute_commands(const char *target, bool *parent_update_pending);
|
|
static int ex_build_target(const char *build_target, size_t target_idx, bool *parent_update_pending);
|
|
static bool update_needed(const char *target, const char *dep);
|
|
static bool file_exists(const char *file);
|
|
static bool target_exists(const char *target, size_t *index);
|
|
static void cleanup(void);
|
|
|
|
static syntax_rule syntax_rules[] =
|
|
{
|
|
[BUILD] =
|
|
{
|
|
.keywords = (const char *const [])
|
|
{
|
|
"build",
|
|
NULL
|
|
},
|
|
|
|
.recipe_list = (const enum recipe *const [])
|
|
{
|
|
(const enum recipe[])
|
|
{
|
|
KEYWORD,
|
|
SYMBOL,
|
|
END
|
|
},
|
|
NULL
|
|
},
|
|
|
|
.symbol_callback = set_build_target
|
|
},
|
|
|
|
[TARGET] =
|
|
{
|
|
.keywords = (const char *const[])
|
|
{
|
|
"target",
|
|
NULL
|
|
},
|
|
|
|
.recipe_list = (const enum recipe*[])
|
|
{
|
|
(const enum recipe[])
|
|
{
|
|
KEYWORD,
|
|
SYMBOL,
|
|
NESTED_RULE,
|
|
END
|
|
},
|
|
NULL
|
|
},
|
|
|
|
.symbol_callback = add_target,
|
|
.scope_block_opened = target_scope_block_opened,
|
|
.scope_block_opened_str = "target_scope_block_opened"
|
|
},
|
|
|
|
[DEFINE_AS] =
|
|
{
|
|
.keywords = (const char *const[])
|
|
{
|
|
"define",
|
|
"as",
|
|
NULL
|
|
},
|
|
|
|
.recipe_list = (const enum recipe *const[])
|
|
{
|
|
(const enum recipe[])
|
|
{
|
|
KEYWORD,
|
|
SYMBOL,
|
|
KEYWORD,
|
|
SYMBOL,
|
|
END
|
|
},
|
|
|
|
(const enum recipe[])
|
|
{
|
|
KEYWORD,
|
|
LIST,
|
|
KEYWORD,
|
|
SYMBOL,
|
|
END
|
|
},
|
|
NULL
|
|
},
|
|
|
|
.symbol_callback = add_define,
|
|
},
|
|
|
|
[CREATED_USING] =
|
|
{
|
|
.keywords = (const char *const[])
|
|
{
|
|
"created",
|
|
"using",
|
|
NULL
|
|
},
|
|
|
|
.recipe_list = (const enum recipe *const[])
|
|
{
|
|
(const enum recipe[])
|
|
{
|
|
KEYWORD,
|
|
KEYWORD,
|
|
LIST,
|
|
END
|
|
},
|
|
NULL
|
|
},
|
|
|
|
.scope_block_opened = created_using_scope_block_opened,
|
|
.scope_block_opened_str = "created_using_scope_block_opened"
|
|
},
|
|
|
|
[DEPENDS_ON] =
|
|
{
|
|
.keywords = (const char *const[])
|
|
{
|
|
"depends",
|
|
"on",
|
|
NULL
|
|
},
|
|
|
|
.recipe_list = (const enum recipe *const[])
|
|
{
|
|
(const enum recipe[])
|
|
{
|
|
KEYWORD,
|
|
KEYWORD,
|
|
LIST,
|
|
END
|
|
},
|
|
NULL
|
|
},
|
|
|
|
.scope_block_opened = depends_on_scope_block_opened,
|
|
.scope_block_opened_str = "depends_on_scope_block_opened"
|
|
}
|
|
};
|
|
|
|
typedef const struct
|
|
{
|
|
bool needed;
|
|
const union
|
|
{
|
|
void (*no_param)(void);
|
|
void (*param_str)(const char *);
|
|
} callback;
|
|
const char *arg;
|
|
const char *description;
|
|
bool additional_param;
|
|
} supported_arg;
|
|
|
|
static supported_arg supported_args[] =
|
|
{
|
|
{
|
|
.needed = false,
|
|
.callback = {.no_param = help},
|
|
.arg = "--help",
|
|
.description = "Shows this message",
|
|
.additional_param = false
|
|
},
|
|
{
|
|
.needed = false,
|
|
.callback = {.no_param = set_preprocess},
|
|
.arg = "-E",
|
|
.description = "Only preprocessed output",
|
|
.additional_param = false
|
|
},
|
|
{
|
|
.needed = false,
|
|
.callback = {.no_param = set_verbose},
|
|
.arg = "-v",
|
|
.description = "Verbose output. Ignores quiet mode",
|
|
.additional_param = false
|
|
},
|
|
{
|
|
.needed = false,
|
|
.callback = {.no_param = set_extra_verbose},
|
|
.arg = "-vv",
|
|
.description = "Extra verbose output. Ignores quiet mode",
|
|
.additional_param = false
|
|
},
|
|
{
|
|
.needed = false,
|
|
.callback = {.param_str = set_input},
|
|
.arg = "-f",
|
|
.description = "[" DEFAULT_FILE_NAME "]. Sets input xmk file.",
|
|
.additional_param = true
|
|
},
|
|
{
|
|
.needed = false,
|
|
.callback = {.no_param = set_quiet},
|
|
.arg = "-q",
|
|
.description = "Quiet mode. Commands are not printed into stdout",
|
|
.additional_param = false
|
|
}
|
|
};
|
|
|
|
int main(const int argv, const char *const argc[])
|
|
{
|
|
STATIC_ASSERT(__STDC_VERSION__ >= 199901L);
|
|
|
|
if (!parse_arguments(argv, argc))
|
|
{
|
|
return exec(&config);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void logv(const char *const func, const int line, const char *const format, ...)
|
|
{
|
|
if (verbose() && func && format)
|
|
{
|
|
va_list ap;
|
|
printf("[v] %s:%d: ", func, line);
|
|
va_start(ap, format);
|
|
vprintf(format, ap);
|
|
printf("\n");
|
|
va_end(ap);
|
|
fflush(stdout);
|
|
}
|
|
}
|
|
|
|
static void logvv(const char *const func, const int line, const char *const format, ...)
|
|
{
|
|
if (extra_verbose() && func && format)
|
|
{
|
|
va_list ap;
|
|
printf("[vv] %s:%d: ", func, line);
|
|
va_start(ap, format);
|
|
vprintf(format, ap);
|
|
printf("\n");
|
|
va_end(ap);
|
|
fflush(stdout);
|
|
}
|
|
}
|
|
|
|
static void fatal_error(const char *const func, const int line, const char *const format, ...)
|
|
{
|
|
if (func && format)
|
|
{
|
|
va_list ap;
|
|
fprintf(stderr, "[error]");
|
|
|
|
if (verbose())
|
|
fprintf(stderr, " %s:%d: ", func, line);
|
|
else
|
|
fprintf(stderr, ": ");
|
|
|
|
va_start(ap, format);
|
|
vfprintf(stderr, format, ap);
|
|
fprintf(stderr, "\n");
|
|
va_end(ap);
|
|
fflush(stderr);
|
|
cleanup();
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static int exec(const struct config* const config)
|
|
{
|
|
if (config)
|
|
{
|
|
/* Retrieve user-defined file path. */
|
|
const char *const path = config->path ? config->path : DEFAULT_FILE_NAME;
|
|
|
|
if (path)
|
|
{
|
|
FILE *const f = fopen(path, "rb");
|
|
|
|
if (f)
|
|
{
|
|
fseek(f, 0, SEEK_END);
|
|
|
|
{
|
|
/* Calculate file size. */
|
|
const size_t sz = ftell(f);
|
|
file_buffer = malloc((sz + 1) * sizeof *file_buffer);
|
|
|
|
/* Return to initial position. */
|
|
rewind(f);
|
|
|
|
if (file_buffer)
|
|
{
|
|
/* Read file contents into allocated buffer and get number of read bytes. */
|
|
const size_t read_bytes = fread(file_buffer, sizeof *file_buffer, sz, f);
|
|
|
|
/* Close file. */
|
|
fclose(f);
|
|
|
|
if (read_bytes == sz)
|
|
{
|
|
/* File contents were read succesfully. */
|
|
LOGV("File %s was opened successfully", path);
|
|
|
|
file_buffer[sz] = '\0';
|
|
line = 1;
|
|
|
|
return parse_file();
|
|
}
|
|
else
|
|
{
|
|
FATAL_ERROR("Could not read %s succesfully. "
|
|
"Only %d/%d bytes could be read",
|
|
path, read_bytes, sz);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FATAL_ERROR("Cannot allocate buffer for file data");
|
|
}
|
|
|
|
/* Close file. */
|
|
fclose(f);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FATAL_ERROR("Input file %s could not be opened", path);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FATAL_ERROR("Invalid given file path");
|
|
}
|
|
}
|
|
|
|
/* This instruction is reached when an error has occurred. */
|
|
return 1;
|
|
}
|
|
|
|
static void help(void)
|
|
{
|
|
printf("%s, an automated build tool.\n\n", APP_NAME);
|
|
printf("Usage:\n");
|
|
printf("%s [OPTIONS]\n", APP_NAME);
|
|
|
|
/* Print all possible arguments and their descriptions. */
|
|
foreach (supported_arg, arg, supported_args)
|
|
{
|
|
printf("%s\t%s\n", arg->arg, arg->description);
|
|
}
|
|
|
|
printf("Written by %s, build date: %s %s\n", AUTHORS, __DATE__, __TIME__);
|
|
exit(0);
|
|
}
|
|
|
|
static int parse_arguments(const int argv, const char *const argc[])
|
|
{
|
|
enum
|
|
{
|
|
GET_ARG,
|
|
GET_PARAM
|
|
} state = GET_ARG;
|
|
|
|
bool found[LENGTHOF(supported_args)] = {false};
|
|
void (*str_callback)(const char *) = NULL;
|
|
|
|
for (int arg = 1; arg < argv; arg++)
|
|
{
|
|
/* Selected n-argument from the list. */
|
|
const char *const arg_str = argc[arg];
|
|
|
|
switch (state)
|
|
{
|
|
case GET_ARG:
|
|
{
|
|
foreach (supported_arg, sup, supported_args)
|
|
{
|
|
const char *const sarg = sup->arg;
|
|
|
|
if (!strncmp(arg_str, sarg, strlen(sarg))
|
|
&&
|
|
(strlen(arg_str) == strlen(sarg)))
|
|
{
|
|
/* Found valid parameter. */
|
|
found[sup - supported_args] = true;
|
|
|
|
if (sup->additional_param)
|
|
{
|
|
str_callback = sup->callback.param_str;
|
|
state = GET_PARAM;
|
|
}
|
|
else
|
|
{
|
|
if (sup->callback.no_param)
|
|
/* Execute callback for selected argument. */
|
|
sup->callback.no_param();
|
|
|
|
/* Exit loop. */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case GET_PARAM:
|
|
{
|
|
if (str_callback)
|
|
str_callback(arg_str);
|
|
|
|
state = GET_ARG;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
foreach (supported_arg, sup, supported_args)
|
|
{
|
|
if (sup->needed && !found[sup - supported_args])
|
|
FATAL_ERROR("Needed parameter %s was not found", sup->arg);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void set_preprocess(void)
|
|
{
|
|
config.preprocess = true;
|
|
}
|
|
|
|
static void set_input(const char *const input)
|
|
{
|
|
config.path = input;
|
|
}
|
|
|
|
static void set_verbose(void)
|
|
{
|
|
config.verbose = true;
|
|
}
|
|
|
|
static void set_extra_verbose(void)
|
|
{
|
|
config.extra_verbose = true;
|
|
/* Also, set verbose mode. */
|
|
config.verbose = true;
|
|
}
|
|
|
|
static void set_quiet(void)
|
|
{
|
|
config.quiet = true;
|
|
}
|
|
|
|
static bool preprocess_only(void)
|
|
{
|
|
return config.preprocess;
|
|
}
|
|
|
|
static int parse_file(void)
|
|
{
|
|
const int result = check_syntax();
|
|
|
|
if (preprocess_only())
|
|
{
|
|
printf("%s", file_buffer);
|
|
return 0;
|
|
}
|
|
|
|
if (file_buffer)
|
|
{
|
|
free(file_buffer);
|
|
file_buffer = NULL;
|
|
}
|
|
|
|
if (!preprocess_only())
|
|
{
|
|
if (!result)
|
|
{
|
|
if (build_target)
|
|
return execute_commands(build_target, NULL);
|
|
else
|
|
FATAL_ERROR("No build target has not been defined. "
|
|
"Please add \"build TARGET_NAME\"");
|
|
}
|
|
}
|
|
|
|
/* Return failure code. */
|
|
return 1;
|
|
}
|
|
|
|
static bool verbose(void)
|
|
{
|
|
return config.verbose;
|
|
}
|
|
|
|
static bool extra_verbose(void)
|
|
{
|
|
return config.extra_verbose;
|
|
}
|
|
|
|
static int check_syntax(void)
|
|
{
|
|
size_t from = 0;
|
|
const char *word;
|
|
enum parse_state state = SEARCHING;
|
|
enum rule rule_checking;
|
|
bool newline_detected;
|
|
|
|
while ((word = get_word(file_buffer, &from, &newline_detected)))
|
|
{
|
|
if (!strcmp(word, "keyword_list.o"))
|
|
{
|
|
volatile int a = 0;
|
|
a++;
|
|
}
|
|
|
|
switch (state)
|
|
{
|
|
case SEARCHING:
|
|
|
|
foreach (syntax_rule, rule, syntax_rules)
|
|
{
|
|
if (check_rule(rule, word, &state, &newline_detected))
|
|
{
|
|
rule_checking = rule - syntax_rules;
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case CHECKING:
|
|
{
|
|
syntax_rule *const rule = &syntax_rules[rule_checking];
|
|
|
|
check_rule(rule, word, &state, &newline_detected);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
/* Undefined state. */
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const char *get_word(char *buffer, size_t *const from, bool* const newline_detected)
|
|
{
|
|
if (buffer && from && newline_detected)
|
|
{
|
|
bool comment = false;
|
|
*newline_detected = false;
|
|
char ch = buffer[*from];
|
|
|
|
while (1)
|
|
{
|
|
switch (ch)
|
|
{
|
|
case '#':
|
|
comment = true;
|
|
break;
|
|
|
|
case '\0':
|
|
return '\0';
|
|
|
|
default:
|
|
if (!comment)
|
|
goto whitespaces_skipped;
|
|
break;
|
|
|
|
case '\n':
|
|
line++;
|
|
comment = false;
|
|
*newline_detected = true;
|
|
/* Fall through. */
|
|
case '\r':
|
|
/* Fall through. */
|
|
case '\t':
|
|
/* Fall through. */
|
|
case ' ':
|
|
break;
|
|
}
|
|
|
|
ch = buffer[++(*from)];
|
|
}
|
|
|
|
whitespaces_skipped:
|
|
|
|
{
|
|
const size_t orig_from = *from;
|
|
|
|
if (ch)
|
|
{
|
|
/* A non-empty character has been found. */
|
|
static char word[255];
|
|
bool quotes = ch == '\"';
|
|
|
|
if (quotes)
|
|
/* Skip first quotes and get next character. */
|
|
ch = buffer[++(*from)];
|
|
|
|
size_t i = 0;
|
|
while ( ( ( (quotes)
|
|
&&
|
|
(ch != '\"') )
|
|
||
|
|
( (!quotes)
|
|
&&
|
|
(ch != ' ')
|
|
&&
|
|
(ch != '\t')
|
|
&&
|
|
(ch != '\n')
|
|
&&
|
|
(ch != '\r')) )
|
|
&&
|
|
(i < (LENGTHOF(word) - 1)))
|
|
{
|
|
word[i++] = buffer[(*from)++];
|
|
ch = buffer[*from];
|
|
}
|
|
|
|
if (i >= LENGTHOF(word) - 1)
|
|
FATAL_ERROR("maximum word length has been exceeded");
|
|
|
|
if (ch == '\n')
|
|
line++;
|
|
|
|
if (quotes)
|
|
/* Ignore closing quotes. */
|
|
(*from)++;
|
|
|
|
word[i] = '\0';
|
|
|
|
if (!quotes && strlen(word) > 1)
|
|
{
|
|
if (word[0] == '$')
|
|
{
|
|
if (word[1] == '$')
|
|
{
|
|
/* Escaped $ sign found. Return word without the first character. */
|
|
return word + 1;
|
|
}
|
|
else if (word[1] == '(')
|
|
{
|
|
if (!strcmp(word, "$(target)"))
|
|
{
|
|
if (current_scope)
|
|
{
|
|
return current_scope;
|
|
}
|
|
else
|
|
{
|
|
FATAL_ERROR("%s must be used inside target scope", "$(target)");
|
|
}
|
|
}
|
|
else if (!strcmp(word, "$(target_name)"))
|
|
{
|
|
if (current_scope)
|
|
{
|
|
return get_basename(current_scope);
|
|
}
|
|
else
|
|
{
|
|
FATAL_ERROR("%s must be used inside target scope", "$(target_name)");
|
|
}
|
|
}
|
|
else if (!strcmp(word, "$(target_ext)"))
|
|
{
|
|
if (current_scope)
|
|
{
|
|
return get_extension(current_scope);
|
|
}
|
|
else
|
|
{
|
|
FATAL_ERROR("%s must be used inside target scope", "$(target_ext)");
|
|
}
|
|
}
|
|
else if (strstr(word, "$(dep"))
|
|
{
|
|
return get_dependency(word);
|
|
}
|
|
}
|
|
else if (is_define(&word[1]))
|
|
{
|
|
*from = orig_from;
|
|
buffer = expand_define(&buffer[*from], word);
|
|
return get_word(buffer, from, newline_detected);
|
|
}
|
|
else
|
|
{
|
|
FATAL_ERROR("Undefined symbol %s", word);
|
|
}
|
|
}
|
|
}
|
|
else if (!quotes && *word == '$')
|
|
{
|
|
FATAL_ERROR("Expected symbol after escaped %s symbol", "$");
|
|
}
|
|
|
|
/* Return address to constructed word. */
|
|
return word;
|
|
}
|
|
else
|
|
{
|
|
/* Buffer end has been reached. */
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Invalid given pointers. */
|
|
FATAL_ERROR("Invalid given pointers");
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
char *expand_define(char *const buffer, const char *const word)
|
|
{
|
|
const size_t before_length = buffer - file_buffer;
|
|
const size_t length = strlen(word);
|
|
char *const after = buffer + length;
|
|
|
|
if (*after)
|
|
{
|
|
/* There is at least one more character after the define. */
|
|
const size_t after_length = strlen(after);
|
|
|
|
/* Create a temporary copy where data after define value will be stored. */
|
|
char *const after_temp = malloc((after_length + 1) * sizeof *after_temp);
|
|
|
|
if (after_temp)
|
|
{
|
|
const char *const value = defines.values[defines.selected_i];
|
|
const size_t value_length = strlen(value);
|
|
const size_t new_length = before_length + value_length + after_length;
|
|
|
|
/* Dump into temporary buffer. */
|
|
strcpy(after_temp, after);
|
|
|
|
/* Reallocate the newly expanded buffer. */
|
|
file_buffer = realloc(file_buffer, new_length * sizeof *file_buffer);
|
|
|
|
if (file_buffer)
|
|
{
|
|
strcpy(&file_buffer[before_length], value);
|
|
strcpy(&file_buffer[before_length + value_length], after_temp);
|
|
free(after_temp);
|
|
LOGVV("Resulting file buffer:\n\n%s", file_buffer);
|
|
return file_buffer;
|
|
}
|
|
else
|
|
{
|
|
FATAL_ERROR("Could not expand define due to insufficient memory");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FATAL_ERROR("Could not create temporary data for define expansion");
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static const char *get_basename(const char *const word)
|
|
{
|
|
if (word)
|
|
{
|
|
static char basename[255];
|
|
const char *w = word;
|
|
char *p = basename;
|
|
|
|
while (*w && *w != '.')
|
|
{
|
|
*p++ = *w++;
|
|
}
|
|
|
|
*p = '\0';
|
|
|
|
return basename;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static const char *get_extension(const char *const word)
|
|
{
|
|
if (word)
|
|
{
|
|
/* File extensions are usually way shorter than names. */
|
|
static char ext[10];
|
|
const char *w = word;
|
|
|
|
while (*w && *w != '.')
|
|
{
|
|
w++;
|
|
}
|
|
|
|
if (w && w[1])
|
|
{
|
|
w++;
|
|
}
|
|
|
|
strcpy(ext, w);
|
|
|
|
return ext;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static const char *get_dependency(const char *const word)
|
|
{
|
|
/* Format: "$dep[INDEX]". */
|
|
enum
|
|
{
|
|
OPENING_BRACKET,
|
|
INDEX,
|
|
CLOSING_BRACKET
|
|
} state = OPENING_BRACKET;
|
|
char dep_i_str[8] = {0};
|
|
size_t dep_i_str_idx = 0;
|
|
|
|
for (const char *dep_idx = word + strlen("$(dep");
|
|
(size_t)(dep_idx - word) < strlen(word);
|
|
dep_idx++)
|
|
{
|
|
const char letter = *dep_idx;
|
|
switch (state)
|
|
{
|
|
case OPENING_BRACKET:
|
|
if (letter == '[')
|
|
{
|
|
state = INDEX;
|
|
}
|
|
break;
|
|
|
|
case INDEX:
|
|
if (letter >= '0' && letter <= '9')
|
|
{
|
|
if (dep_i_str_idx < strlen(dep_i_str))
|
|
{
|
|
dep_i_str[dep_i_str_idx++] = letter;
|
|
}
|
|
|
|
switch (*(dep_idx + 1))
|
|
{
|
|
case '\0':
|
|
printf("Missing \"]\" character on dependency index\r\n");
|
|
break;
|
|
|
|
case ']':
|
|
state = CLOSING_BRACKET;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FATAL_ERROR("Invalid index %d", letter);
|
|
}
|
|
break;
|
|
|
|
case CLOSING_BRACKET:
|
|
break;
|
|
}
|
|
}
|
|
|
|
dep_i_str[++dep_i_str_idx] = '\0';
|
|
|
|
{
|
|
size_t i;
|
|
/* Accept any numerical base. */
|
|
const size_t dep_index = strtol(dep_i_str, NULL, 0);
|
|
|
|
if (target_exists(current_scope, &i))
|
|
{
|
|
if (syntax_rules[DEPENDS_ON].list && syntax_rules[DEPENDS_ON].list_size)
|
|
{
|
|
const size_t target_deps = syntax_rules[DEPENDS_ON].list_size[i];
|
|
|
|
if (!target_deps)
|
|
{
|
|
FATAL_ERROR("No dependencies are available for target %s", current_scope);
|
|
}
|
|
|
|
if (dep_index < target_deps)
|
|
{
|
|
return syntax_rules[DEPENDS_ON].list[i][dep_index];
|
|
}
|
|
else
|
|
{
|
|
FATAL_ERROR("Index %d exceeds number of defined dependencies", dep_index);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FATAL_ERROR("Dependencies list has not been allocated");
|
|
}
|
|
}
|
|
}
|
|
|
|
return word;
|
|
}
|
|
|
|
bool is_define(const char *const name)
|
|
{
|
|
for (size_t i = 0; i < defines.n; i++)
|
|
{
|
|
if (!strcmp(defines.names[i], name))
|
|
{
|
|
LOGVV("Detected define \"%s\"->\"%s\"", defines.names[i], defines.values[i]);
|
|
defines.selected_i = i;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool check_rule(syntax_rule *const rule, const char *const word, enum parse_state* const state, bool* const newline_detected)
|
|
{
|
|
if (rule)
|
|
{
|
|
enum
|
|
{
|
|
MAX_RECURSION = 2
|
|
};
|
|
static size_t step_i[MAX_RECURSION];
|
|
static size_t keyword_i[MAX_RECURSION];
|
|
static size_t recipe_i[MAX_RECURSION];
|
|
static size_t recursion_level;
|
|
const enum recipe *const recipe = rule->recipe_list[recipe_i[recursion_level]];
|
|
|
|
if (recipe)
|
|
{
|
|
const enum recipe step = recipe[step_i[recursion_level]];
|
|
|
|
switch (step)
|
|
{
|
|
case KEYWORD:
|
|
{
|
|
const char *const keyword = rule->keywords[keyword_i[recursion_level]];
|
|
|
|
if (keyword)
|
|
{
|
|
const size_t len = strlen(keyword);
|
|
if (!strncmp(word, keyword, len) && len == strlen(word))
|
|
{
|
|
/* Found valid keyword. */
|
|
step_i[recursion_level]++;
|
|
keyword_i[recursion_level]++;
|
|
|
|
{
|
|
const enum recipe next_step = recipe[step_i[recursion_level]];
|
|
|
|
if (next_step == END)
|
|
{
|
|
/* All words for selected rule have been found. */
|
|
step_i[recursion_level] = 0;
|
|
keyword_i[recursion_level] = 0;
|
|
recipe_i[recursion_level] = 0;
|
|
|
|
if (recursion_level)
|
|
{
|
|
recursion_level--;
|
|
}
|
|
|
|
*state = SEARCHING;
|
|
}
|
|
else
|
|
{
|
|
*state = CHECKING;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else if ((strlen(word) == 1) && (word[0] == '}'))
|
|
{
|
|
if (recursion_level)
|
|
{
|
|
recursion_level--;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
recipe_i[recursion_level]++;
|
|
/* Try again with another recipe (if available). */
|
|
return check_rule(rule, word, state, newline_detected);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NESTED_RULE:
|
|
if (recursion_level < MAX_RECURSION)
|
|
{
|
|
recursion_level++;
|
|
}
|
|
|
|
{
|
|
bool finished;
|
|
scope(rule, word, state, &finished);
|
|
}
|
|
|
|
*state = SEARCHING;
|
|
return true;
|
|
|
|
case SYMBOL:
|
|
add_symbol(rule, word);
|
|
|
|
step_i[recursion_level]++;
|
|
{
|
|
const enum recipe next_step = recipe[step_i[recursion_level]];
|
|
|
|
if (next_step == END)
|
|
{
|
|
step_i[recursion_level] = 0;
|
|
keyword_i[recursion_level] = 0;
|
|
recipe_i[recursion_level] = 0;
|
|
|
|
if (recursion_level)
|
|
{
|
|
recursion_level--;
|
|
}
|
|
|
|
*state = SEARCHING;
|
|
}
|
|
else
|
|
{
|
|
*state = CHECKING;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
|
|
case LIST:
|
|
{
|
|
bool finished;
|
|
|
|
if (handle_list(rule, word, state, *newline_detected, &finished))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case END:
|
|
step_i[recursion_level] = 0;
|
|
keyword_i[recursion_level] = 0;
|
|
recipe_i[recursion_level] = 0;
|
|
|
|
if (recursion_level)
|
|
{
|
|
recursion_level--;
|
|
}
|
|
|
|
*state = SEARCHING;
|
|
return true;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
step_i[recursion_level] = 0;
|
|
keyword_i[recursion_level] = 0;
|
|
recipe_i[recursion_level] = 0;
|
|
|
|
*state = SEARCHING;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void add_symbol(syntax_rule *const rule, const char *const word)
|
|
{
|
|
void (*const symbol_callback)(const char *) = rule->symbol_callback;
|
|
|
|
if (symbol_callback)
|
|
{
|
|
symbol_callback(word);
|
|
}
|
|
}
|
|
|
|
static void set_build_target(const char *const target)
|
|
{
|
|
if (!build_target)
|
|
{
|
|
const size_t length = (strlen(target) + 1);
|
|
build_target = calloc(length, sizeof *build_target);
|
|
|
|
if (build_target)
|
|
{
|
|
strcpy(build_target, target);
|
|
LOGV("Build target set to \"%s\"", build_target);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FATAL_ERROR("Only one target can be defined");
|
|
}
|
|
}
|
|
|
|
static void add_target(const char *const target)
|
|
{
|
|
bool repeated = false;
|
|
syntax_rule *const targets = &syntax_rules[TARGET];
|
|
|
|
if (targets->list && targets->list_size)
|
|
{
|
|
for (size_t i = 0; i < *targets->list_size; i++)
|
|
{
|
|
/* Retrieve target from the list. */
|
|
const char *const l_target = (*targets->list)[i];
|
|
|
|
if (l_target)
|
|
{
|
|
if (!strcmp(l_target, target))
|
|
{
|
|
repeated = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!repeated)
|
|
{
|
|
if (!targets->list && !targets->list_size)
|
|
{
|
|
/* No targets have been allocated yet. */
|
|
targets->list = malloc(sizeof *targets->list);
|
|
targets->list_size = malloc(sizeof *targets->list_size);
|
|
|
|
if (targets->list_size && targets->list)
|
|
{
|
|
*targets->list = malloc(sizeof **targets->list);
|
|
/* Initialize number of defined targets. */
|
|
*targets->list_size = 0;
|
|
}
|
|
}
|
|
else if (*targets->list)
|
|
{
|
|
/* Resize buffer so the new target can be allocated. */
|
|
*targets->list = realloc(*targets->list, (*targets->list_size + 1) * sizeof (**targets->list));
|
|
}
|
|
|
|
if (targets->list && *targets->list && targets->list_size)
|
|
{
|
|
size_t *const list_size = targets->list_size;
|
|
char **const new_target = &(*targets->list)[*list_size];
|
|
const size_t length = strlen(target) + 1;
|
|
/* Now, allocate a copy of "target" into targets list. */
|
|
*new_target = calloc(length, sizeof **new_target);
|
|
|
|
if (*new_target)
|
|
{
|
|
strcpy(*new_target, target);
|
|
(*list_size)++;
|
|
|
|
LOGV("Targets list: %zu", *list_size);
|
|
|
|
for (size_t i = 0; i < *list_size; i++)
|
|
{
|
|
const char *const target_str = (*targets->list)[i];
|
|
LOGV("\t%zu/%zu: %s", i + 1, *list_size, target_str);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FATAL_ERROR("An error happened when constructing target list");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FATAL_ERROR("Target %s has already been defined", target);
|
|
}
|
|
}
|
|
|
|
static void add_define(const char *const define)
|
|
{
|
|
static enum
|
|
{
|
|
GET_NAME,
|
|
GET_VALUE
|
|
} state;
|
|
|
|
switch (state)
|
|
{
|
|
case GET_NAME:
|
|
|
|
defines.names = realloc(defines.names, (defines.n + 1) * sizeof *defines.names);
|
|
|
|
if (defines.names)
|
|
{
|
|
const size_t length = strlen(define);
|
|
char **const name = &defines.names[defines.n];
|
|
|
|
*name = malloc((length + 1) * sizeof **name);
|
|
|
|
if (*name)
|
|
{
|
|
strcpy(*name, define);
|
|
LOGVV("Detected new define name \"%s\"", *name);
|
|
state = GET_VALUE;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case GET_VALUE:
|
|
|
|
defines.values = realloc(defines.values, (defines.n + 1) * sizeof *defines.values);
|
|
|
|
if (defines.values)
|
|
{
|
|
const size_t length = strlen(define);
|
|
char **const value = &defines.values[defines.n];
|
|
*value = calloc(length + 1, sizeof **value);
|
|
|
|
if (*value)
|
|
{
|
|
strcpy(*value, define);
|
|
LOGVV("Detected new value for \"%s\": \"%s\"", defines.names[defines.n], *value);
|
|
state = GET_NAME;
|
|
defines.n++;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
enum parse_state target_scope_block_opened(void)
|
|
{
|
|
if (!syntax_rules[TARGET].list_size)
|
|
{
|
|
/* Allocate space for list size for current target. */
|
|
syntax_rules[TARGET].list_size = calloc(1, sizeof *syntax_rules[TARGET].list_size);
|
|
}
|
|
|
|
if (syntax_rules[TARGET].list_size)
|
|
{
|
|
create_basic_tree(&syntax_rules[DEPENDS_ON]);
|
|
create_basic_tree(&syntax_rules[CREATED_USING]);
|
|
|
|
return CHECKING;
|
|
}
|
|
else
|
|
{
|
|
FATAL_ERROR("Could not allocate space for target list");
|
|
}
|
|
|
|
return SEARCHING;
|
|
}
|
|
|
|
enum parse_state created_using_scope_block_opened(void)
|
|
{
|
|
return CHECKING;
|
|
}
|
|
|
|
enum parse_state depends_on_scope_block_opened(void)
|
|
{
|
|
return CHECKING;
|
|
}
|
|
|
|
static bool scope(syntax_rule *const rule, const char *const word, enum parse_state* const state, bool* const finished)
|
|
{
|
|
*finished = false;
|
|
|
|
if (strlen(word) == 1)
|
|
{
|
|
switch (*word)
|
|
{
|
|
case '{':
|
|
{
|
|
/* User has opened a scope block. */
|
|
LOGVV("Scope block opened");
|
|
enum parse_state (*const callback)(void) = rule->scope_block_opened;
|
|
|
|
LOGVV("Scope block callback: %p (%s)", callback, rule->scope_block_opened_str);
|
|
|
|
if (callback)
|
|
{
|
|
*state = callback();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
FATAL_ERROR("Keyword %s does not accept %c", rule->keywords[0], *word);
|
|
*state = SEARCHING;
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case '}':
|
|
/* User has closed a scope block. */
|
|
*state = SEARCHING;
|
|
return true;
|
|
|
|
default:
|
|
/* 1 byte long word. Keep running this function. */
|
|
break;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool handle_list(syntax_rule *const rule,
|
|
const char *const word,
|
|
enum parse_state* const state,
|
|
const bool newline_detected,
|
|
bool* const finished)
|
|
{
|
|
const size_t current_target = *syntax_rules[TARGET].list_size - 1;
|
|
|
|
*finished = false;
|
|
|
|
if (scope(rule, word, state, finished))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/* This point is reached when no scope blocks are found. */
|
|
if (!rule->list)
|
|
{
|
|
rule->list = malloc(sizeof *rule->list);
|
|
|
|
if (rule->list && rule->list_size)
|
|
{
|
|
*rule->list = malloc(sizeof **rule->list);
|
|
|
|
if (*rule->list)
|
|
{
|
|
const size_t length = strlen(word) + 1;
|
|
**rule->list = malloc(length * sizeof ***rule->list);
|
|
|
|
if (**rule->list)
|
|
{
|
|
strcpy(**rule->list, word);
|
|
rule->list_size[current_target]++;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (!rule->list[current_target])
|
|
{
|
|
rule->list[current_target] = malloc(sizeof **rule->list);
|
|
|
|
if (rule->list[current_target])
|
|
{
|
|
const size_t length = sizeof (**(rule->list[current_target])) * (strlen(word) + 1);
|
|
|
|
*(rule->list[current_target]) = malloc(length * sizeof **(rule->list[current_target]));
|
|
|
|
if (*(rule->list[current_target]))
|
|
{
|
|
strcpy(*(rule->list[current_target]), word);
|
|
rule->list_size[current_target]++;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else if (newline_detected)
|
|
{
|
|
const size_t new_length = ++(rule->list_size[current_target]);
|
|
rule->list[current_target] = realloc( rule->list[current_target],
|
|
sizeof (*rule->list[current_target]) * new_length);
|
|
|
|
if (rule->list[current_target])
|
|
{
|
|
const size_t length = strlen(word) + 1;
|
|
const size_t sz = sizeof *rule->list[current_target][new_length - 1];
|
|
const size_t str_length = sz * length;
|
|
|
|
rule->list[current_target][new_length - 1] = calloc(str_length, sz);
|
|
|
|
if (rule->list[current_target][new_length - 1])
|
|
{
|
|
strcpy(rule->list[current_target][new_length - 1], word);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else if (rule->list_size)
|
|
{
|
|
char ** str = &rule->list[current_target][rule->list_size[current_target] - 1];
|
|
|
|
if (!*str)
|
|
{
|
|
const size_t length = strlen(word) + 1;
|
|
*str = malloc(length * sizeof **str);
|
|
|
|
if (*str)
|
|
{
|
|
strcpy(*str, word);
|
|
rule->list_size[current_target]++;
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const size_t new_length = strlen(*str) + strlen(" ") + strlen(word) + 1;
|
|
*str = realloc(*str, sizeof (**str) * new_length);
|
|
|
|
if (*str)
|
|
{
|
|
strcat(*str, " ");
|
|
strcat(*str, word);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
FATAL_ERROR("Some error has been detected");
|
|
|
|
return false;
|
|
}
|
|
|
|
static void create_basic_tree(syntax_rule *const dep_rule)
|
|
{
|
|
const size_t n_targets = *syntax_rules[TARGET].list_size;
|
|
const char *const target_name = (*syntax_rules[TARGET].list)[n_targets - 1];
|
|
|
|
if (!current_scope)
|
|
{
|
|
current_scope = calloc(strlen(target_name) + 1, sizeof *current_scope);
|
|
}
|
|
else
|
|
{
|
|
current_scope = realloc(current_scope, sizeof (char) * (strlen(target_name) + 1));
|
|
}
|
|
|
|
if (current_scope)
|
|
{
|
|
strcpy(current_scope, target_name);
|
|
}
|
|
|
|
if (!dep_rule->list && !dep_rule->list_size)
|
|
{
|
|
dep_rule->list = calloc(1, sizeof *dep_rule->list);
|
|
dep_rule->list_size = calloc(1, sizeof *dep_rule->list_size);
|
|
|
|
if (!dep_rule->list || !dep_rule->list_size)
|
|
{
|
|
FATAL_ERROR("Could not allocate space for dependency list");
|
|
}
|
|
}
|
|
else if (dep_rule->list && dep_rule->list_size)
|
|
{
|
|
dep_rule->list = realloc(dep_rule->list, n_targets * sizeof (*dep_rule->list));
|
|
dep_rule->list_size = realloc(dep_rule->list_size, n_targets * sizeof *dep_rule->list_size);
|
|
|
|
if (dep_rule->list && dep_rule->list_size)
|
|
{
|
|
dep_rule->list[n_targets - 1] = NULL;
|
|
dep_rule->list_size[n_targets - 1] = 0;
|
|
}
|
|
else
|
|
FATAL_ERROR("Could not reallocate to %zu", n_targets);
|
|
}
|
|
}
|
|
|
|
static int execute_commands(const char *const target, bool *const parent_update_pending)
|
|
{
|
|
size_t i;
|
|
|
|
if (target_exists(target, &i))
|
|
return ex_build_target(target, i, parent_update_pending);
|
|
else if (!file_exists(target))
|
|
FATAL_ERROR("Target \"%s\" could not be found on target list", target);
|
|
|
|
cleanup();
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef WIN32
|
|
static int build(const char *const command)
|
|
{
|
|
int error_code;
|
|
STARTUPINFOA startup_info = {.cb = sizeof startup_info};
|
|
PROCESS_INFORMATION process_info = {0};
|
|
DWORD exit_code = 0;
|
|
|
|
if (CreateProcessA
|
|
(
|
|
NULL, /*lpApplicationName*/
|
|
command, /* lpCommandLine */
|
|
NULL, /*lpProcessAttributes */
|
|
NULL, /*lpThreadAttributes */
|
|
false, /*bInheritHandles */
|
|
0, /* dwCreationFlags*/
|
|
NULL, /* lpEnvironment */
|
|
NULL, /* lpCurrentDirectory */
|
|
&startup_info, /*lpStartupInfo */
|
|
&process_info /* lpProcessInformation */
|
|
))
|
|
{
|
|
WaitForSingleObject(process_info.hProcess, INFINITE);
|
|
GetExitCodeProcess(process_info.hProcess, &exit_code);
|
|
}
|
|
else
|
|
{
|
|
exit_code = GetLastError();
|
|
}
|
|
|
|
CloseHandle(startup_info.hStdInput);
|
|
CloseHandle(startup_info.hStdOutput);
|
|
CloseHandle(startup_info.hStdError);
|
|
CloseHandle(process_info.hProcess);
|
|
CloseHandle(process_info.hThread);
|
|
|
|
return exit_code;
|
|
}
|
|
#endif
|
|
|
|
#ifdef _POSIX_VERSION
|
|
static int build(const char *const command)
|
|
{
|
|
return system(command);
|
|
}
|
|
#endif
|
|
|
|
static int ex_build_target(const char *const build_target, const size_t target_idx, bool *const parent_update_pending)
|
|
{
|
|
bool update_pending = false;
|
|
|
|
if (syntax_rules[CREATED_USING].list_size)
|
|
{
|
|
const size_t n_commands = syntax_rules[CREATED_USING].list_size[target_idx];
|
|
|
|
LOGV("%zu commands have been defined for target \"%s\"", n_commands, build_target);
|
|
|
|
for (size_t i = 0; i < n_commands; i++)
|
|
{
|
|
const char *const command = syntax_rules[CREATED_USING].list[target_idx][i];
|
|
LOGV("\tCommand %zu=\"%s\"(%p)",
|
|
i, command, command);
|
|
}
|
|
|
|
if (!file_exists(build_target))
|
|
update_pending = true;
|
|
|
|
if (syntax_rules[DEPENDS_ON].list && syntax_rules[DEPENDS_ON].list_size)
|
|
{
|
|
const size_t target_deps = syntax_rules[DEPENDS_ON].list_size[target_idx];
|
|
LOGV("Target %s has %zu dependencies", build_target, target_deps);
|
|
|
|
if (!target_deps && !n_commands)
|
|
FATAL_ERROR("No build steps or dependencies have"
|
|
"been indicated for target %s\n", build_target);
|
|
|
|
for (size_t dep = 0; dep < target_deps; dep++)
|
|
{
|
|
const char *const dependency = syntax_rules[DEPENDS_ON].list[target_idx][dep];
|
|
|
|
LOGV("Checking dependency %zu/%zu \"%s\"", dep +1, target_deps, dependency);
|
|
|
|
if (dependency)
|
|
{
|
|
execute_commands(dependency, &update_pending);
|
|
|
|
if (!update_needed(build_target, dependency))
|
|
;
|
|
else if (!update_pending)
|
|
update_pending = true;
|
|
} /* if (dependency) */
|
|
} /* for (size_t dep = 0; dep < target_deps; dep++) */
|
|
} /* if (syntax_rules[DEPENDS_ON].list && syntax_rules[DEPENDS_ON].list_size) */
|
|
} /* if (syntax_rules[CREATED_USING].list_size) */
|
|
|
|
if (parent_update_pending)
|
|
*parent_update_pending = update_pending;
|
|
|
|
/* At this point, all dependencies have been resolved. */
|
|
if (update_pending)
|
|
{
|
|
const size_t target_commands = syntax_rules[CREATED_USING].list_size[target_idx];
|
|
|
|
LOGV("Target \"%s\" must be built", build_target);
|
|
|
|
for (size_t i = 0; i < target_commands; i++)
|
|
{
|
|
char *const command = syntax_rules[CREATED_USING].list[target_idx][i];
|
|
|
|
if (command)
|
|
{
|
|
if (!config.quiet)
|
|
/* Print resulting command. */
|
|
printf("%s\r\n", command);
|
|
|
|
{
|
|
const int exit_code = build(command);
|
|
free(command);
|
|
|
|
if (exit_code)
|
|
FATAL_ERROR("Error [%d]", exit_code);
|
|
}
|
|
}
|
|
else
|
|
FATAL_ERROR("Command %d for target %d is empty", i, target_idx);
|
|
}
|
|
|
|
/* At this point, all commands for a given target have been executed. */
|
|
if (!file_exists(build_target))
|
|
{
|
|
FATAL_ERROR("Commands executed for generating \"%s\" were successful, "
|
|
"but file has not been generated\n", build_target);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LOGV("Target \"%s\" is up to date", build_target);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
#ifdef WIN32
|
|
static bool update_needed(const char *const target, const char *const dep)
|
|
{
|
|
bool ret = true;
|
|
|
|
if (dep && target && syntax_rules[TARGET].list_size)
|
|
{
|
|
const size_t n_targets = *syntax_rules[TARGET].list_size;
|
|
|
|
HANDLE target_file = CreateFileA(target,
|
|
GENERIC_READ,
|
|
0,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL);
|
|
HANDLE dep_file = CreateFileA( dep,
|
|
GENERIC_READ,
|
|
0,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL);
|
|
|
|
if ((dep_file == INVALID_HANDLE_VALUE)
|
|
||
|
|
(target_file == INVALID_HANDLE_VALUE))
|
|
{
|
|
/* Dependency does not exist, so it must be built. */
|
|
}
|
|
else
|
|
{
|
|
/* At this point, dependency already exists, but
|
|
* we must check if it is newer than our target. */
|
|
|
|
FILETIME target_write_time;
|
|
|
|
if (GetFileTime(target_file, NULL, NULL, &target_write_time))
|
|
{
|
|
const ULARGE_INTEGER target_time =
|
|
{
|
|
.HighPart = target_write_time.dwHighDateTime,
|
|
.LowPart = target_write_time.dwLowDateTime
|
|
};
|
|
|
|
FILETIME dep_write_time;
|
|
|
|
if (GetFileTime(dep_file, NULL, NULL, &dep_write_time))
|
|
{
|
|
const ULARGE_INTEGER dep_time =
|
|
{
|
|
.HighPart = dep_write_time.dwHighDateTime,
|
|
.LowPart = dep_write_time.dwLowDateTime
|
|
};
|
|
|
|
ret = dep_time.QuadPart > target_time.QuadPart;
|
|
}
|
|
else
|
|
{
|
|
/* Could not extract dependency file modification timedate. */
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Could not extract target file modification timedate. */
|
|
}
|
|
}
|
|
|
|
CloseHandle(target_file);
|
|
CloseHandle(dep_file);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
#ifdef _POSIX_VERSION
|
|
static bool update_needed(const char *const target, const char *const dep)
|
|
{
|
|
/* TODO */
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
static bool file_exists(const char *const file)
|
|
{
|
|
FILE *const f = fopen(file, "rb");
|
|
bool ret;
|
|
|
|
if (!!(ret = f))
|
|
{
|
|
fclose(f);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool target_exists(const char *const target, size_t *const index)
|
|
{
|
|
if (target && syntax_rules[TARGET].list_size)
|
|
{
|
|
size_t i;
|
|
const size_t n_targets = *syntax_rules[TARGET].list_size;
|
|
|
|
for (i = 0; i < n_targets; i++)
|
|
{
|
|
if (!strcmp((*syntax_rules[TARGET].list)[i], target))
|
|
{
|
|
if (index)
|
|
{
|
|
*index = i;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
printf("No targets have been defined.\r\n");
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void cleanup_list(syntax_rule *const rule)
|
|
{
|
|
if (rule->list_size && rule->list)
|
|
{
|
|
const size_t n = *rule->list_size;
|
|
|
|
for (size_t i = 0; i < n; i++)
|
|
{
|
|
if (rule->list[i])
|
|
{
|
|
if (*rule->list[i])
|
|
{
|
|
free(*rule->list[i]);
|
|
}
|
|
|
|
free(rule->list[i]);
|
|
}
|
|
}
|
|
|
|
free(rule->list);
|
|
free(rule->list_size);
|
|
}
|
|
}
|
|
|
|
static void cleanup(void)
|
|
{
|
|
cleanup_list(&syntax_rules[CREATED_USING]);
|
|
cleanup_list(&syntax_rules[DEPENDS_ON]);
|
|
|
|
{
|
|
syntax_rule *const targets = &syntax_rules[TARGET];
|
|
|
|
if (targets->list_size && targets->list)
|
|
{
|
|
for (size_t i = 0; i < *targets->list_size; i++)
|
|
{
|
|
if ((*targets->list)[i])
|
|
{
|
|
free((*targets->list)[i]);
|
|
}
|
|
}
|
|
|
|
free(*targets->list);
|
|
free(targets->list_size);
|
|
}
|
|
}
|
|
|
|
if (defines.names)
|
|
{
|
|
for (size_t i = 0; i < defines.n; i++)
|
|
{
|
|
if (defines.names[i])
|
|
{
|
|
free(defines.names[i]);
|
|
}
|
|
}
|
|
|
|
free(defines.names);
|
|
}
|
|
|
|
if (defines.values)
|
|
{
|
|
for (size_t i = 0; i < defines.n; i++)
|
|
{
|
|
if (defines.values[i])
|
|
{
|
|
free(defines.values[i]);
|
|
}
|
|
}
|
|
|
|
free(defines.values);
|
|
}
|
|
|
|
if (file_buffer)
|
|
{
|
|
free(file_buffer);
|
|
}
|
|
}
|