blob: 1f4022aed67de4454c2daa2928601e3a878d9d12 [file] [log] [blame]
/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "Charmonizer/Core/Make.h"
#include "Charmonizer/Core/CFlags.h"
#include "Charmonizer/Core/Compiler.h"
#include "Charmonizer/Core/OperatingSystem.h"
#include "Charmonizer/Core/Util.h"
#define CHAZ_MAKEBINARY_EXE 1
#define CHAZ_MAKEBINARY_STATIC_LIB 2
#define CHAZ_MAKEBINARY_SHARED_LIB 3
struct chaz_MakeVar {
char *name;
char *value;
size_t num_elements;
};
struct chaz_MakeRule {
char *targets;
char *prereqs;
char *commands;
};
struct chaz_MakeBinary {
int type;
char *target_dir;
char *basename;
char *version;
char *major_version;
char **sources; /* List of all sources. */
size_t num_sources;
char **single_sources; /* Only sources from add_src_file. */
size_t num_single_sources;
char **dirs;
size_t num_dirs;
chaz_MakeVar *obj_var; /* Owned by MakeFile. */
char *dollar_var;
chaz_MakeRule *rule; /* Not added to MakeFile, owned by MakeBinary. */
chaz_CFlags *compile_flags;
chaz_CFlags *link_flags;
};
struct chaz_MakeFile {
chaz_MakeVar **vars;
size_t num_vars;
chaz_MakeRule **rules;
size_t num_rules;
chaz_MakeRule *clean;
chaz_MakeRule *distclean;
chaz_MakeBinary **binaries;
size_t num_binaries;
};
/* Static vars. */
static struct {
char *make_command;
int shell_type;
} chaz_Make = {
NULL,
0
};
/* Detect make command.
*
* The argument list must be a NULL-terminated series of different spellings
* of `make`, which will be auditioned in the order they are supplied. Here
* are several possibilities:
*
* make
* gmake
* nmake
* dmake
*/
static int
chaz_Make_detect(const char *make1, ...);
static int
chaz_Make_audition(const char *make);
static void
chaz_MakeFile_finish_exe(chaz_MakeFile *self, chaz_MakeBinary *binary);
static void
chaz_MakeFile_finish_shared_lib(chaz_MakeFile *self, chaz_MakeBinary *binary);
static void
chaz_MakeFile_finish_static_lib(chaz_MakeFile *self, chaz_MakeBinary *binary);
static chaz_MakeBinary*
chaz_MakeFile_add_binary(chaz_MakeFile *self, int type, const char *dir,
const char *basename, const char *target);
static void
chaz_MakeFile_write_binary_rules(chaz_MakeFile *self, chaz_MakeBinary *binary,
FILE *out);
static void
chaz_MakeFile_write_object_rules(char **sources, const char *command,
FILE *out);
static void
chaz_MakeFile_write_pattern_rules(char **dirs, const char *command,
FILE *out);
static chaz_MakeRule*
S_new_rule(const char *target, const char *prereq);
static void
S_destroy_rule(chaz_MakeRule *rule);
static void
S_write_rule(chaz_MakeRule *rule, FILE *out);
static void
chaz_MakeBinary_destroy(chaz_MakeBinary *self);
static void
chaz_MakeBinary_list_files_callback(const char *dir, char *file,
void *context);
static void
chaz_MakeBinary_do_add_src_file(chaz_MakeBinary *self, const char *path);
/** Return the path to the object file for a source file.
*
* @param path The path to the source file.
*/
static char*
chaz_MakeBinary_obj_path(const char *src_path);
void
chaz_Make_init(const char *make_command) {
chaz_Make.shell_type = chaz_OS_shell_type();
if (make_command) {
if (!chaz_Make_detect(make_command, NULL)) {
chaz_Util_warn("Make utility '%s' doesn't appear to work",
make_command);
}
}
else {
int succeeded = 0;
/* mingw32-make seems to try to run commands under both cmd.exe
* and sh.exe. Not sure about dmake.
*/
if (chaz_Make.shell_type == CHAZ_OS_POSIX) {
succeeded = chaz_Make_detect("make", "gmake", "dmake",
"mingw32-make", NULL);
}
else if (chaz_Make.shell_type == CHAZ_OS_CMD_EXE) {
succeeded = chaz_Make_detect("nmake", "dmake", "mingw32-make",
NULL);
}
if (!succeeded) {
chaz_Util_warn("No working make utility found");
}
else if (chaz_Util_verbosity) {
printf("Detected make utility '%s'\n", chaz_Make.make_command);
}
}
}
void
chaz_Make_clean_up(void) {
free(chaz_Make.make_command);
}
const char*
chaz_Make_get_make(void) {
return chaz_Make.make_command;
}
int
chaz_Make_shell_type(void) {
return chaz_Make.shell_type;
}
static int
chaz_Make_detect(const char *make1, ...) {
va_list args;
const char *candidate;
int found = 0;
const char makefile_content[] = "foo:\n\t@echo 643490c943525d19\n";
chaz_Util_write_file("_charm_Makefile", makefile_content);
/* Audition candidates. */
found = chaz_Make_audition(make1);
va_start(args, make1);
while (!found && (NULL != (candidate = va_arg(args, const char*)))) {
found = chaz_Make_audition(candidate);
}
va_end(args);
chaz_Util_remove_and_verify("_charm_Makefile");
return found;
}
static int
chaz_Make_audition(const char *make) {
int succeeded = 0;
char *command = chaz_Util_join(" ", make, "-f", "_charm_Makefile", NULL);
chaz_Util_remove_and_verify("_charm_foo");
chaz_OS_run_redirected(command, "_charm_foo");
if (chaz_Util_can_open_file("_charm_foo")) {
size_t len;
char *content = chaz_Util_slurp_file("_charm_foo", &len);
if (content != NULL && strstr(content, "643490c943525d19") != NULL) {
succeeded = 1;
}
free(content);
}
chaz_Util_remove_and_verify("_charm_foo");
if (succeeded) {
chaz_Make.make_command = chaz_Util_strdup(make);
}
free(command);
return succeeded;
}
chaz_MakeFile*
chaz_MakeFile_new() {
chaz_MakeFile *self = (chaz_MakeFile*)calloc(1, sizeof(chaz_MakeFile));
const char *exe_ext = chaz_CC_exe_ext();
const char *obj_ext = chaz_CC_obj_ext();
char *generated;
self->vars = (chaz_MakeVar**)calloc(1, sizeof(chaz_MakeVar*));
self->rules = (chaz_MakeRule**)calloc(1, sizeof(chaz_MakeRule*));
self->binaries = (chaz_MakeBinary**)calloc(1, sizeof(chaz_MakeBinary*));
self->clean = S_new_rule("clean", NULL);
self->distclean = S_new_rule("distclean", "clean");
generated = chaz_Util_join("", "charmonizer", exe_ext, " charmonizer",
obj_ext, " charmony.h Makefile", NULL);
chaz_MakeRule_add_rm_command(self->distclean, generated);
free(generated);
return self;
}
void
chaz_MakeFile_destroy(chaz_MakeFile *self) {
size_t i;
for (i = 0; self->vars[i]; i++) {
chaz_MakeVar *var = self->vars[i];
free(var->name);
free(var->value);
free(var);
}
free(self->vars);
for (i = 0; self->rules[i]; i++) {
S_destroy_rule(self->rules[i]);
}
free(self->rules);
for (i = 0; self->binaries[i]; i++) {
chaz_MakeBinary_destroy(self->binaries[i]);
}
free(self->binaries);
S_destroy_rule(self->clean);
S_destroy_rule(self->distclean);
free(self);
}
chaz_MakeVar*
chaz_MakeFile_add_var(chaz_MakeFile *makefile, const char *name,
const char *value) {
chaz_MakeVar *var = (chaz_MakeVar*)malloc(sizeof(chaz_MakeVar));
chaz_MakeVar **vars = makefile->vars;
size_t num_vars = makefile->num_vars + 1;
var->name = chaz_Util_strdup(name);
var->value = chaz_Util_strdup("");
var->num_elements = 0;
if (value) { chaz_MakeVar_append(var, value); }
vars = (chaz_MakeVar**)realloc(vars,
(num_vars + 1) * sizeof(chaz_MakeVar*));
vars[num_vars-1] = var;
vars[num_vars] = NULL;
makefile->vars = vars;
makefile->num_vars = num_vars;
return var;
}
chaz_MakeRule*
chaz_MakeFile_add_rule(chaz_MakeFile *makefile, const char *target,
const char *prereq) {
chaz_MakeRule *rule = S_new_rule(target, prereq);
chaz_MakeRule **rules = makefile->rules;
size_t num_rules = makefile->num_rules + 1;
rules = (chaz_MakeRule**)realloc(rules,
(num_rules + 1) * sizeof(chaz_MakeRule*));
rules[num_rules-1] = rule;
rules[num_rules] = NULL;
makefile->rules = rules;
makefile->num_rules = num_rules;
return rule;
}
chaz_MakeRule*
chaz_MakeFile_clean_rule(chaz_MakeFile *makefile) {
return makefile->clean;
}
chaz_MakeRule*
chaz_MakeFile_distclean_rule(chaz_MakeFile *makefile) {
return makefile->distclean;
}
chaz_MakeBinary*
chaz_MakeFile_add_exe(chaz_MakeFile *self, const char *dir,
const char *basename) {
const char *exe_ext = chaz_CC_exe_ext();
char *target;
chaz_MakeBinary *binary;
if (dir == NULL || strcmp(dir, ".") == 0) {
target = chaz_Util_join("", basename, exe_ext, NULL);
}
else {
const char *dir_sep = chaz_OS_dir_sep();
target = chaz_Util_join("", dir, dir_sep, basename, exe_ext, NULL);
}
binary = chaz_MakeFile_add_binary(self, CHAZ_MAKEBINARY_EXE, dir, basename,
target);
free(target);
return binary;
}
void
chaz_MakeFile_finish_exe(chaz_MakeFile *self, chaz_MakeBinary *binary) {
const char *link = chaz_CC_link_command();
const char *link_flags_string;
char *command;
(void)self;
/* This is destructive but shouldn't be a problem since a Makefile
* is only written once.
*/
chaz_CFlags_set_link_output(binary->link_flags, "$@");
link_flags_string = chaz_CFlags_get_string(binary->link_flags);
/* Objects in dollar var must come before flags since flags may
* contain libraries.
*/
command = chaz_Util_join(" ", link, binary->dollar_var, link_flags_string,
NULL);
chaz_MakeRule_add_command(binary->rule, command);
free(command);
}
chaz_MakeBinary*
chaz_MakeFile_add_shared_lib(chaz_MakeFile *self, const char *dir,
const char *basename, const char *version,
const char *major_version) {
int binary_format = chaz_CC_binary_format();
char *target;
chaz_MakeBinary *binary;
if (binary_format == CHAZ_CC_BINFMT_PE) {
target = chaz_CC_shared_lib_filename(dir, basename, major_version);
}
else {
target = chaz_CC_shared_lib_filename(dir, basename, version);
}
binary = chaz_MakeFile_add_binary(self, CHAZ_MAKEBINARY_SHARED_LIB, dir,
basename, target);
binary->version = chaz_Util_strdup(version);
binary->major_version = chaz_Util_strdup(major_version);
chaz_CFlags_compile_shared_library(binary->compile_flags);
chaz_CFlags_link_shared_library(binary->link_flags, basename, version,
major_version);
free(target);
return binary;
}
void
chaz_MakeFile_finish_shared_lib(chaz_MakeFile *self, chaz_MakeBinary *binary) {
const char *link = chaz_CC_link_command();
const char *link_flags_string;
int binfmt = chaz_CC_binary_format();
char *no_v_name
= chaz_CC_shared_lib_filename(binary->target_dir, binary->basename,
NULL);
char *major_v_name
= chaz_CC_shared_lib_filename(binary->target_dir, binary->basename,
binary->major_version);
char *command;
if (binfmt == CHAZ_CC_BINFMT_MACHO) {
const char *dir_sep = chaz_OS_dir_sep();
char *install_name;
/* Set temporary install name with full path on Darwin. */
install_name = chaz_Util_join("", "-install_name $(CURDIR)", dir_sep,
major_v_name, NULL);
chaz_CFlags_append(binary->link_flags, install_name);
free(install_name);
}
chaz_CFlags_set_link_output(binary->link_flags, "$@");
link_flags_string = chaz_CFlags_get_string(binary->link_flags);
command = chaz_Util_join(" ", link, binary->dollar_var, link_flags_string,
NULL);
chaz_MakeRule_add_command(binary->rule, command);
free(command);
/* Add symlinks. */
if (binfmt == CHAZ_CC_BINFMT_ELF || binfmt == CHAZ_CC_BINFMT_MACHO) {
command = chaz_Util_join(" ", "ln -sf", binary->rule->targets,
major_v_name, NULL);
chaz_MakeRule_add_command(binary->rule, command);
free(command);
if (binfmt == CHAZ_CC_BINFMT_MACHO) {
command = chaz_Util_join(" ", "ln -sf", binary->rule->targets,
no_v_name, NULL);
}
else {
command = chaz_Util_join(" ", "ln -sf", major_v_name, no_v_name,
NULL);
}
chaz_MakeRule_add_command(binary->rule, command);
free(command);
chaz_MakeRule_add_rm_command(self->clean, major_v_name);
chaz_MakeRule_add_rm_command(self->clean, no_v_name);
}
if (binfmt == CHAZ_CC_BINFMT_PE) {
/* Remove import library. */
char *filename
= chaz_CC_import_lib_filename(binary->target_dir, binary->basename,
binary->major_version);
chaz_MakeRule_add_rm_command(self->clean, filename);
free(filename);
}
if (chaz_CC_msvc_version_num()) {
/* Remove export file. */
char *filename
= chaz_CC_export_filename(binary->target_dir, binary->basename,
binary->major_version);
chaz_MakeRule_add_rm_command(self->clean, filename);
free(filename);
}
free(major_v_name);
free(no_v_name);
}
chaz_MakeBinary*
chaz_MakeFile_add_static_lib(chaz_MakeFile *self, const char *dir,
const char *basename) {
char *target = chaz_CC_static_lib_filename(dir, basename);
chaz_MakeBinary *binary
= chaz_MakeFile_add_binary(self, CHAZ_MAKEBINARY_STATIC_LIB, dir,
basename, target);
free(target);
return binary;
}
static void
chaz_MakeFile_finish_static_lib(chaz_MakeFile *self, chaz_MakeBinary *binary) {
char *command;
(void)self;
command = chaz_CC_format_archiver_command("$@", binary->dollar_var);
chaz_MakeRule_add_command(binary->rule, command);
free(command);
command = chaz_CC_format_ranlib_command("$@");
if (command) {
chaz_MakeRule_add_command(binary->rule, command);
free(command);
}
}
static chaz_MakeBinary*
chaz_MakeFile_add_binary(chaz_MakeFile *self, int type, const char *dir,
const char *basename, const char *target) {
chaz_MakeBinary *binary
= (chaz_MakeBinary*)calloc(1, sizeof(chaz_MakeBinary));
const char *suffix;
char *uc_basename = chaz_Util_strdup(basename);
char *binary_var_name;
char *obj_var_name;
char *dollar_var;
size_t i;
size_t num_binaries;
size_t alloc_size;
chaz_MakeBinary **binaries;
switch (type) {
case CHAZ_MAKEBINARY_EXE: suffix = "EXE"; break;
case CHAZ_MAKEBINARY_STATIC_LIB: suffix = "STATIC_LIB"; break;
case CHAZ_MAKEBINARY_SHARED_LIB: suffix = "SHARED_LIB"; break;
default:
chaz_Util_die("Unknown binary type %d", type);
return NULL;
}
for (i = 0; uc_basename[i] != '\0'; i++) {
uc_basename[i] = toupper((unsigned char)uc_basename[i]);
}
binary_var_name = chaz_Util_join("_", uc_basename, suffix, NULL);
obj_var_name = chaz_Util_join("_", uc_basename, suffix, "OBJS", NULL);
dollar_var = chaz_Util_join("", "$(", obj_var_name, ")", NULL);
chaz_MakeFile_add_var(self, binary_var_name, target);
binary->type = type;
binary->target_dir = dir ? chaz_Util_strdup(dir) : NULL;
binary->basename = chaz_Util_strdup(basename);
binary->obj_var = chaz_MakeFile_add_var(self, obj_var_name, NULL);
binary->dollar_var = dollar_var;
binary->rule = S_new_rule(target, dollar_var);
binary->sources = (char**)calloc(1, sizeof(char*));
binary->single_sources = (char**)calloc(1, sizeof(char*));
binary->dirs = (char**)calloc(1, sizeof(char*));
binary->compile_flags = chaz_CC_new_cflags();
binary->link_flags = chaz_CC_new_cflags();
num_binaries = self->num_binaries;
alloc_size = (num_binaries + 2) * sizeof(chaz_MakeBinary*);
binaries = (chaz_MakeBinary**)realloc(self->binaries, alloc_size);
binaries[num_binaries] = binary;
binaries[num_binaries+1] = NULL;
self->binaries = binaries;
self->num_binaries = num_binaries + 1;
free(uc_basename);
free(obj_var_name);
free(binary_var_name);
return binary;
}
chaz_MakeBinary*
chaz_MakeFile_add_lemon_exe(chaz_MakeFile *makefile, const char *dir) {
chaz_MakeBinary *exe = chaz_MakeFile_add_exe(makefile, dir, "lemon");
chaz_MakeBinary_add_src_file(exe, dir, "lemon.c");
return exe;
}
chaz_MakeRule*
chaz_MakeFile_add_lemon_grammar(chaz_MakeFile *makefile,
const char *base_name) {
char *c_file = chaz_Util_join(".", base_name, "c", NULL);
char *h_file = chaz_Util_join(".", base_name, "h", NULL);
char *y_file = chaz_Util_join(".", base_name, "y", NULL);
char *command = chaz_Util_join(" ", "$(LEMON_EXE) -q", y_file, NULL);
chaz_MakeRule *rule = chaz_MakeFile_add_rule(makefile, c_file, y_file);
chaz_MakeRule *clean_rule = chaz_MakeFile_clean_rule(makefile);
chaz_MakeRule_add_prereq(rule, "$(LEMON_EXE)");
chaz_MakeRule_add_command(rule, command);
chaz_MakeRule_add_rm_command(clean_rule, h_file);
chaz_MakeRule_add_rm_command(clean_rule, c_file);
free(c_file);
free(h_file);
free(y_file);
free(command);
return rule;
}
void
chaz_MakeFile_write(chaz_MakeFile *makefile) {
FILE *out;
size_t i;
out = fopen("Makefile", "w");
if (!out) {
chaz_Util_die("Can't open Makefile\n");
}
for (i = 0; makefile->vars[i]; i++) {
chaz_MakeVar *var = makefile->vars[i];
fprintf(out, "%s = %s\n", var->name, var->value);
}
fprintf(out, "\n");
for (i = 0; makefile->rules[i]; i++) {
S_write_rule(makefile->rules[i], out);
}
for (i = 0; makefile->binaries[i]; i++) {
chaz_MakeFile_write_binary_rules(makefile, makefile->binaries[i], out);
}
S_write_rule(makefile->clean, out);
S_write_rule(makefile->distclean, out);
/* Suffix rule for .c files. */
if (chaz_CC_msvc_version_num()) {
fprintf(out, ".c.obj :\n");
fprintf(out, "\t$(CC) /nologo $(CFLAGS) /c $< /Fo$@\n\n");
}
else {
fprintf(out, ".c.o :\n");
fprintf(out, "\t$(CC) $(CFLAGS) -c $< -o $@\n\n");
}
fclose(out);
}
static void
chaz_MakeFile_write_binary_rules(chaz_MakeFile *self, chaz_MakeBinary *binary,
FILE *out) {
const char *cflags;
if (chaz_CC_msvc_version_num()) {
chaz_CFlags_append(binary->link_flags, "/nologo");
}
switch (binary->type) {
case CHAZ_MAKEBINARY_EXE:
chaz_MakeFile_finish_exe(self, binary);
break;
case CHAZ_MAKEBINARY_STATIC_LIB:
chaz_MakeFile_finish_static_lib(self, binary);
break;
case CHAZ_MAKEBINARY_SHARED_LIB:
chaz_MakeFile_finish_shared_lib(self, binary);
break;
default:
chaz_Util_die("Invalid binary type: %d", binary->type);
return;
}
chaz_MakeRule_add_rm_command(self->clean, binary->rule->targets);
chaz_MakeRule_add_rm_command(self->clean, binary->dollar_var);
S_write_rule(binary->rule, out);
cflags = chaz_CFlags_get_string(binary->compile_flags);
/* Write rules to compile with custom flags. */
if (cflags[0] != '\0') {
if (chaz_Make.shell_type == CHAZ_OS_CMD_EXE) {
/* Write a rule for each object file. This is needed for nmake
* which doesn't support pattern rules but also for mingw32-make
* which has problems with pattern rules and backslash directory
* separators.
*/
chaz_MakeFile_write_object_rules(binary->sources, cflags, out);
}
else {
/* Write a pattern rule for each directory. */
chaz_MakeFile_write_pattern_rules(binary->dirs, cflags, out);
/* Write a rule for each object added with add_src_file. */
chaz_MakeFile_write_object_rules(binary->single_sources, cflags,
out);
}
}
}
static void
chaz_MakeFile_write_object_rules(char **sources, const char *cflags,
FILE *out) {
chaz_CFlags *output_cflags = chaz_CC_new_cflags();
const char *output_cflags_string;
size_t i;
chaz_CFlags_set_output_obj(output_cflags, "$@");
output_cflags_string = chaz_CFlags_get_string(output_cflags);
for (i = 0; sources[i]; i++) {
const char *source = sources[i];
char *obj_path = chaz_MakeBinary_obj_path(source);
chaz_MakeRule *rule;
char *command;
if (obj_path == NULL) { continue; }
rule = S_new_rule(obj_path, source);
command = chaz_Util_join(" ", "$(CC) $(CFLAGS)", cflags, source,
output_cflags_string, NULL);
chaz_MakeRule_add_command(rule, command);
S_write_rule(rule, out);
free(command);
S_destroy_rule(rule);
free(obj_path);
}
chaz_CFlags_destroy(output_cflags);
}
static void
chaz_MakeFile_write_pattern_rules(char **dirs, const char *cflags, FILE *out) {
const char *obj_ext = chaz_CC_obj_ext();
const char *dir_sep = chaz_OS_dir_sep();
chaz_CFlags *output_cflags = chaz_CC_new_cflags();
const char *output_cflags_string;
char *command;
size_t i;
chaz_CFlags_set_output_obj(output_cflags, "$@");
output_cflags_string = chaz_CFlags_get_string(output_cflags);
command = chaz_Util_join(" ", "$(CC) $(CFLAGS)", cflags, "$<",
output_cflags_string, NULL);
for (i = 0; dirs[i]; i++) {
const char *dir = dirs[i];
char *target = chaz_Util_join("", dir, dir_sep, "%", obj_ext,
NULL);
char *prereq = chaz_Util_join("", dir, dir_sep, "%.c", NULL);
chaz_MakeRule *rule = S_new_rule(target, prereq);
chaz_MakeRule_add_command(rule, command);
S_write_rule(rule, out);
S_destroy_rule(rule);
free(prereq);
free(target);
}
free(command);
chaz_CFlags_destroy(output_cflags);
}
void
chaz_MakeVar_append(chaz_MakeVar *var, const char *element) {
char *value;
if (element[0] == '\0') { return; }
if (var->num_elements == 0) {
value = chaz_Util_strdup(element);
}
else {
value = (char*)malloc(strlen(var->value) + strlen(element) + 20);
if (var->num_elements == 1) {
sprintf(value, "\\\n %s \\\n %s", var->value, element);
}
else {
sprintf(value, "%s \\\n %s", var->value, element);
}
}
free(var->value);
var->value = value;
var->num_elements++;
}
static chaz_MakeRule*
S_new_rule(const char *target, const char *prereq) {
chaz_MakeRule *rule = (chaz_MakeRule*)malloc(sizeof(chaz_MakeRule));
rule->targets = NULL;
rule->prereqs = NULL;
rule->commands = NULL;
if (target) { chaz_MakeRule_add_target(rule, target); }
if (prereq) { chaz_MakeRule_add_prereq(rule, prereq); }
return rule;
}
static void
S_destroy_rule(chaz_MakeRule *rule) {
if (rule->targets) { free(rule->targets); }
if (rule->prereqs) { free(rule->prereqs); }
if (rule->commands) { free(rule->commands); }
free(rule);
}
static void
S_write_rule(chaz_MakeRule *rule, FILE *out) {
fprintf(out, "%s :", rule->targets);
if (rule->prereqs) {
fprintf(out, " %s", rule->prereqs);
}
fprintf(out, "\n");
if (rule->commands) {
fprintf(out, "%s", rule->commands);
}
fprintf(out, "\n");
}
void
chaz_MakeRule_add_target(chaz_MakeRule *rule, const char *target) {
char *targets;
if (!rule->targets) {
targets = chaz_Util_strdup(target);
}
else {
targets = chaz_Util_join(" ", rule->targets, target, NULL);
free(rule->targets);
}
rule->targets = targets;
}
void
chaz_MakeRule_add_prereq(chaz_MakeRule *rule, const char *prereq) {
char *prereqs;
if (!rule->prereqs) {
prereqs = chaz_Util_strdup(prereq);
}
else {
prereqs = chaz_Util_join(" ", rule->prereqs, prereq, NULL);
free(rule->prereqs);
}
rule->prereqs = prereqs;
}
void
chaz_MakeRule_add_command(chaz_MakeRule *rule, const char *command) {
char *commands;
if (!rule->commands) {
commands = (char*)malloc(strlen(command) + 20);
sprintf(commands, "\t%s\n", command);
}
else {
commands = (char*)malloc(strlen(rule->commands) + strlen(command) + 20);
sprintf(commands, "%s\t%s\n", rule->commands, command);
free(rule->commands);
}
rule->commands = commands;
}
void
chaz_MakeRule_add_command_with_libpath(chaz_MakeRule *rule,
const char *command, ...) {
va_list args;
char *path = NULL;
char *lib_command = NULL;
int binfmt = chaz_CC_binary_format();
if (binfmt == CHAZ_CC_BINFMT_ELF) {
va_start(args, command);
path = chaz_Util_vjoin(":", args);
va_end(args);
lib_command = chaz_Util_join("", "LD_LIBRARY_PATH=", path,
":$$LD_LIBRARY_PATH ", command, NULL);
free(path);
}
else if (binfmt == CHAZ_CC_BINFMT_PE) {
if (chaz_Make.shell_type == CHAZ_OS_CMD_EXE) {
va_start(args, command);
path = chaz_Util_vjoin(";", args);
va_end(args);
/* It's important to not add a space before `&&`. Otherwise, the
* space is added to the search path.
*/
lib_command = chaz_Util_join("", "path ", path, ";%path%&& ",
command, NULL);
}
else {
va_start(args, command);
path = chaz_Util_vjoin(":", args);
va_end(args);
lib_command = chaz_Util_join("", "PATH=", path, ":$$PATH ",
command, NULL);
}
free(path);
}
else {
/* Assume that library paths are compiled into the executable on
* Darwin.
*/
lib_command = chaz_Util_strdup(command);
}
chaz_MakeRule_add_command(rule, lib_command);
free(lib_command);
}
void
chaz_MakeRule_add_rm_command(chaz_MakeRule *rule, const char *files) {
char *command;
if (chaz_Make.shell_type == CHAZ_OS_POSIX) {
command = chaz_Util_join(" ", "rm -f", files, NULL);
}
else if (chaz_Make.shell_type == CHAZ_OS_CMD_EXE) {
command = chaz_Util_join("", "for %%i in (", files,
") do @if exist %%i del /f %%i", NULL);
}
else {
chaz_Util_die("Unsupported shell type: %d", chaz_Make.shell_type);
}
chaz_MakeRule_add_command(rule, command);
free(command);
}
void
chaz_MakeRule_add_recursive_rm_command(chaz_MakeRule *rule, const char *dirs) {
char *command;
if (chaz_Make.shell_type == CHAZ_OS_POSIX) {
command = chaz_Util_join(" ", "rm -rf", dirs, NULL);
}
else if (chaz_Make.shell_type == CHAZ_OS_CMD_EXE) {
command = chaz_Util_join("", "for %%i in (", dirs,
") do @if exist %%i rmdir /s /q %%i", NULL);
}
else {
chaz_Util_die("Unsupported shell type: %d", chaz_Make.shell_type);
}
chaz_MakeRule_add_command(rule, command);
free(command);
}
void
chaz_MakeRule_add_make_command(chaz_MakeRule *rule, const char *dir,
const char *target) {
char *command;
if (chaz_Make.shell_type == CHAZ_OS_POSIX) {
if (!target) {
command = chaz_Util_join("", "(cd ", dir, " && $(MAKE))", NULL);
}
else {
command = chaz_Util_join("", "(cd ", dir, " && $(MAKE) ", target,
")", NULL);
}
chaz_MakeRule_add_command(rule, command);
free(command);
}
else if (chaz_Make.shell_type == CHAZ_OS_CMD_EXE) {
if (!target) {
command = chaz_Util_join(" ", "pushd", dir, "&& $(MAKE) && popd",
NULL);
}
else {
command = chaz_Util_join(" ", "pushd", dir, "&& $(MAKE)", target,
"&& popd", NULL);
}
chaz_MakeRule_add_command(rule, command);
free(command);
}
else {
chaz_Util_die("Unsupported shell type: %d", chaz_Make.shell_type);
}
}
static void
chaz_MakeBinary_destroy(chaz_MakeBinary *self) {
size_t i;
free(self->target_dir);
free(self->basename);
free(self->version);
free(self->major_version);
free(self->dollar_var);
S_destroy_rule(self->rule);
for (i = 0; i < self->num_sources; i++) {
free(self->sources[i]);
}
free(self->sources);
for (i = 0; i < self->num_single_sources; i++) {
free(self->single_sources[i]);
}
free(self->single_sources);
for (i = 0; i < self->num_dirs; i++) {
free(self->dirs[i]);
}
free(self->dirs);
chaz_CFlags_destroy(self->compile_flags);
chaz_CFlags_destroy(self->link_flags);
free(self);
}
void
chaz_MakeBinary_add_src_file(chaz_MakeBinary *self, const char *dir,
const char *filename) {
size_t num_sources = self->num_single_sources;
size_t alloc_size = (num_sources + 2) * sizeof(char*);
char **sources = (char**)realloc(self->single_sources, alloc_size);
char *path;
if (dir == NULL || strcmp(dir, ".") == 0) {
path = chaz_Util_strdup(filename);
}
else {
const char *dir_sep = chaz_OS_dir_sep();
path = chaz_Util_join(dir_sep, dir, filename, NULL);
}
/* Add to single_sources. */
sources[num_sources] = path;
sources[num_sources+1] = NULL;
self->single_sources = sources;
self->num_single_sources = num_sources + 1;
chaz_MakeBinary_do_add_src_file(self, path);
}
void
chaz_MakeBinary_add_src_dir(chaz_MakeBinary *self, const char *path) {
size_t num_dirs = self->num_dirs;
char **dirs = (char**)realloc(self->dirs, (num_dirs + 2) * sizeof(char*));
dirs[num_dirs] = chaz_Util_strdup(path);
dirs[num_dirs+1] = NULL;
self->dirs = dirs;
self->num_dirs = num_dirs + 1;
chaz_Make_list_files(path, "c", chaz_MakeBinary_list_files_callback,
self);
}
static void
chaz_MakeBinary_list_files_callback(const char *dir, char *file,
void *context) {
const char *dir_sep = chaz_OS_dir_sep();
char *path = chaz_Util_join(dir_sep, dir, file, NULL);
chaz_MakeBinary_do_add_src_file((chaz_MakeBinary*)context, path);
free(path);
}
static void
chaz_MakeBinary_do_add_src_file(chaz_MakeBinary *self, const char *path) {
size_t num_sources = self->num_sources;
size_t alloc_size = (num_sources + 2) * sizeof(char*);
char **sources = (char**)realloc(self->sources, alloc_size);
char *obj_path;
sources[num_sources] = chaz_Util_strdup(path);
sources[num_sources+1] = NULL;
self->sources = sources;
self->num_sources = num_sources + 1;
obj_path = chaz_MakeBinary_obj_path(path);
if (obj_path == NULL) {
chaz_Util_warn("Invalid source filename: %s", path);
}
else {
chaz_MakeVar_append(self->obj_var, obj_path);
free(obj_path);
}
}
static char*
chaz_MakeBinary_obj_path(const char *src_path) {
const char *dir_sep = chaz_OS_dir_sep();
const char *obj_ext = chaz_CC_obj_ext();
size_t obj_ext_len = strlen(obj_ext);
size_t i = strlen(src_path);
char *retval;
while (i > 0) {
i -= 1;
if (src_path[i] == dir_sep[0]) { return NULL; }
if (src_path[i] == '.') { break; }
}
if (src_path[i] != '.') { return NULL; }
retval = (char*)malloc(i + obj_ext_len + 1);
memcpy(retval, src_path, i);
memcpy(retval + i, obj_ext, obj_ext_len + 1);
return retval;
}
void
chaz_MakeBinary_add_prereq(chaz_MakeBinary *self, const char *prereq) {
chaz_MakeRule_add_prereq(self->rule, prereq);
}
char*
chaz_MakeBinary_obj_string(chaz_MakeBinary *self) {
char *retval = chaz_Util_strdup("");
size_t i;
for (i = 0; i < self->num_sources; i++) {
const char *sep = retval[0] == '\0' ? "" : " ";
char *obj_path = chaz_MakeBinary_obj_path(self->sources[i]);
char *tmp;
if (obj_path == NULL) { continue; }
tmp = chaz_Util_join("", retval, sep, obj_path, NULL);
free(retval);
retval = tmp;
}
return retval;
}
const char*
chaz_MakeBinary_get_target(chaz_MakeBinary *self) {
return self->rule->targets;
}
chaz_CFlags*
chaz_MakeBinary_get_compile_flags(chaz_MakeBinary *self) {
return self->compile_flags;
}
chaz_CFlags*
chaz_MakeBinary_get_link_flags(chaz_MakeBinary *self) {
return self->link_flags;
}
void
chaz_Make_list_files(const char *dir, const char *ext,
chaz_Make_list_files_callback_t callback, void *context) {
int shell_type = chaz_OS_shell_type();
const char *pattern;
char *command;
char *list;
char *prefix;
char *file;
size_t command_size;
size_t list_len;
size_t prefix_len;
/* List files using shell. */
if (shell_type == CHAZ_OS_POSIX) {
pattern = "find %s -name '*.%s' -type f";
}
else if (shell_type == CHAZ_OS_CMD_EXE) {
pattern = "dir %s\\*.%s /s /b /a-d";
}
else {
chaz_Util_die("Unknown shell type %d", shell_type);
}
command_size = strlen(pattern) + strlen(dir) + strlen(ext) + 10;
command = (char*)malloc(command_size);
sprintf(command, pattern, dir, ext);
list = chaz_OS_run_and_capture(command, &list_len);
free(command);
if (!list) {
chaz_Util_die("Failed to list files in '%s'", dir);
}
list[list_len-1] = 0;
/* Find directory prefix to strip from files */
if (shell_type == CHAZ_OS_POSIX) {
prefix_len = strlen(dir);
prefix = (char*)malloc(prefix_len + 2);
memcpy(prefix, dir, prefix_len);
prefix[prefix_len++] = '/';
prefix[prefix_len] = '\0';
}
else {
char *output;
size_t output_len;
/* 'dir /s' returns absolute paths, so we have to find the absolute
* path of the directory. This is done by using the variable
* substitution feature of the 'for' command.
*/
pattern = "for %%I in (%s) do @echo %%~fI";
command_size = strlen(pattern) + strlen(dir) + 10;
command = (char*)malloc(command_size);
sprintf(command, pattern, dir);
output = chaz_OS_run_and_capture(command, &output_len);
free(command);
if (!output) { chaz_Util_die("Failed to find absolute path"); }
/* Strip whitespace from end of output. */
for (prefix_len = output_len; prefix_len > 0; --prefix_len) {
if (!isspace((unsigned char)output[prefix_len-1])) { break; }
}
prefix = (char*)malloc(prefix_len + 2);
memcpy(prefix, output, prefix_len);
prefix[prefix_len++] = '\\';
prefix[prefix_len] = '\0';
free(output);
}
/* Iterate file list and invoke callback. */
for (file = strtok(list, "\r\n"); file; file = strtok(NULL, "\r\n")) {
if (strlen(file) <= prefix_len
|| memcmp(file, prefix, prefix_len) != 0
) {
chaz_Util_die("Expected prefix '%s' for file name '%s'", prefix,
file);
}
callback(dir, file + prefix_len, context);
}
free(prefix);
free(list);
}