blob: f864a5dc51cc9fa743dcf1b33a85c40de4b4d30a [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 <errno.h>
#include <string.h>
#include <stdlib.h>
#include "Charmonizer/Core/Util.h"
#include "Charmonizer/Core/Compiler.h"
#include "Charmonizer/Core/ConfWriter.h"
#include "Charmonizer/Core/OperatingSystem.h"
/* Detect binary format.
*/
static void
chaz_CC_detect_binary_format(const char *filename);
/** Return the numeric value of a macro or 0 if it isn't defined.
*/
static int
chaz_CC_eval_macro(const char *macro);
/* Detect macros which may help to identify some compilers.
*/
static void
chaz_CC_detect_known_compilers(void);
/** Build a library filename from its components.
*/
static char*
chaz_CC_build_lib_filename(const char *dir, const char *prefix,
const char *basename, const char *version,
const char *ext);
/* Temporary files. */
#define CHAZ_CC_TRY_SOURCE_PATH "_charmonizer_try.c"
#define CHAZ_CC_TRY_BASENAME "_charmonizer_try"
#define CHAZ_CC_TARGET_PATH "_charmonizer_target"
/* Static vars. */
static struct {
char *cc_command;
char *cflags;
char *try_exe_name;
char exe_ext[10];
char shared_lib_ext[10];
char static_lib_ext[10];
char import_lib_ext[10];
char obj_ext[10];
char gcc_version_str[30];
int binary_format;
int cflags_style;
int intval___GNUC__;
int intval___GNUC_MINOR__;
int intval___GNUC_PATCHLEVEL__;
int intval__MSC_VER;
int intval___clang__;
int intval___SUNPRO_C;
int is_cygwin;
int is_mingw;
chaz_CFlags *extra_cflags;
chaz_CFlags *temp_cflags;
} chaz_CC = {
NULL, NULL, NULL,
"", "", "", "", "", "",
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
NULL, NULL
};
void
chaz_CC_init(const char *compiler_command, const char *compiler_flags) {
const char *code = "int main() { return 0; }\n";
int compile_succeeded = 0;
if (chaz_Util_verbosity) { printf("Creating compiler object...\n"); }
/* Assign, init. */
chaz_CC.cc_command = chaz_Util_strdup(compiler_command);
chaz_CC.cflags = chaz_Util_strdup(compiler_flags);
chaz_CC.extra_cflags = NULL;
chaz_CC.temp_cflags = NULL;
/* Set names for the targets which we "try" to compile. */
strcpy(chaz_CC.exe_ext, ".exe");
chaz_CC.try_exe_name
= chaz_Util_join("", CHAZ_CC_TRY_BASENAME, chaz_CC.exe_ext, NULL);
/* If we can't compile or execute anything, game over. */
if (chaz_Util_verbosity) {
printf("Trying to compile and execute a small test file...\n");
}
/* Try MSVC argument style. */
if (!compile_succeeded) {
chaz_CC.cflags_style = CHAZ_CFLAGS_STYLE_MSVC;
if (!chaz_Util_remove_and_verify(chaz_CC.try_exe_name)) {
chaz_Util_die("Failed to delete file '%s'", chaz_CC.try_exe_name);
}
compile_succeeded = chaz_CC_compile_exe(CHAZ_CC_TRY_SOURCE_PATH,
CHAZ_CC_TRY_BASENAME, code);
}
/* Try POSIX argument style. */
if (!compile_succeeded) {
chaz_CC.cflags_style = CHAZ_CFLAGS_STYLE_POSIX;
if (!chaz_Util_remove_and_verify(chaz_CC.try_exe_name)) {
chaz_Util_die("Failed to delete file '%s'", chaz_CC.try_exe_name);
}
compile_succeeded = chaz_CC_compile_exe(CHAZ_CC_TRY_SOURCE_PATH,
CHAZ_CC_TRY_BASENAME, code);
}
if (!compile_succeeded) {
chaz_Util_die("Failed to compile a small test file");
}
chaz_CC_detect_binary_format(chaz_CC.try_exe_name);
chaz_Util_remove_and_verify(chaz_CC.try_exe_name);
chaz_CC_detect_known_compilers();
if (chaz_CC.intval___GNUC__) {
chaz_CC.cflags_style = CHAZ_CFLAGS_STYLE_GNU;
}
else if (chaz_CC.intval__MSC_VER) {
chaz_CC.cflags_style = CHAZ_CFLAGS_STYLE_MSVC;
}
else if (chaz_CC.intval___SUNPRO_C) {
chaz_CC.cflags_style = CHAZ_CFLAGS_STYLE_SUN_C;
}
else {
chaz_CC.cflags_style = CHAZ_CFLAGS_STYLE_POSIX;
}
chaz_CC.extra_cflags = chaz_CFlags_new(chaz_CC.cflags_style);
chaz_CC.temp_cflags = chaz_CFlags_new(chaz_CC.cflags_style);
/* File extensions. */
if (chaz_CC.binary_format == CHAZ_CC_BINFMT_ELF) {
if (chaz_Util_verbosity) {
printf("Detected binary format: ELF\n");
}
strcpy(chaz_CC.exe_ext, "");
strcpy(chaz_CC.shared_lib_ext, ".so");
strcpy(chaz_CC.static_lib_ext, ".a");
strcpy(chaz_CC.obj_ext, ".o");
}
else if (chaz_CC.binary_format == CHAZ_CC_BINFMT_MACHO) {
if (chaz_Util_verbosity) {
printf("Detected binary format: Mach-O\n");
}
strcpy(chaz_CC.exe_ext, "");
strcpy(chaz_CC.shared_lib_ext, ".dylib");
strcpy(chaz_CC.static_lib_ext, ".a");
strcpy(chaz_CC.obj_ext, ".o");
}
else if (chaz_CC.binary_format == CHAZ_CC_BINFMT_PE) {
if (chaz_Util_verbosity) {
printf("Detected binary format: Portable Executable\n");
}
strcpy(chaz_CC.exe_ext, ".exe");
strcpy(chaz_CC.shared_lib_ext, ".dll");
if (chaz_CC.intval___GNUC__) {
strcpy(chaz_CC.static_lib_ext, ".a");
strcpy(chaz_CC.import_lib_ext, ".dll.a");
strcpy(chaz_CC.obj_ext, ".o");
}
else {
strcpy(chaz_CC.static_lib_ext, ".lib");
strcpy(chaz_CC.import_lib_ext, ".lib");
strcpy(chaz_CC.obj_ext, ".obj");
}
if (chaz_CC_has_macro("__CYGWIN__")) {
chaz_CC.is_cygwin = 1;
}
if (chaz_CC_has_macro("__MINGW32__")) {
chaz_CC.is_mingw = 1;
}
}
else {
chaz_Util_die("Failed to detect binary format");
}
free(chaz_CC.try_exe_name);
chaz_CC.try_exe_name
= chaz_Util_join("", CHAZ_CC_TRY_BASENAME, chaz_CC.exe_ext, NULL);
}
static void
chaz_CC_detect_binary_format(const char *filename) {
char *output;
size_t output_len;
int binary_format = 0;
output = chaz_Util_slurp_file(filename, &output_len);
/* ELF. */
if (binary_format == 0 && output_len >= 4
&& memcmp(output, "\x7F" "ELF", 4) == 0
) {
binary_format = CHAZ_CC_BINFMT_ELF;
}
/* Macho-O. */
if (binary_format == 0 && output_len >= 4
&& (memcmp(output, "\xCA\xFE\xBA\xBE", 4) == 0 /* Fat binary. */
|| memcmp(output, "\xFE\xED\xFA\xCE", 4) == 0 /* 32-bit BE. */
|| memcmp(output, "\xFE\xED\xFA\xCF", 4) == 0 /* 64-bit BE. */
|| memcmp(output, "\xCE\xFA\xED\xFE", 4) == 0 /* 32-bit LE. */
|| memcmp(output, "\xCF\xFA\xED\xFE", 4) == 0) /* 64-bit LE. */
) {
binary_format = CHAZ_CC_BINFMT_MACHO;
}
/* Portable Executable. */
if (binary_format == 0 && output_len >= 0x40
&& memcmp(output, "MZ", 2) == 0
) {
size_t pe_header_off =
(unsigned char)output[0x3C]
| ((unsigned char)output[0x3D] << 8)
| ((unsigned char)output[0x3E] << 16)
| ((unsigned char)output[0x3F] << 24);
if (output_len >= pe_header_off + 4
&& memcmp(output + pe_header_off, "PE\0\0", 4) == 0
) {
binary_format = CHAZ_CC_BINFMT_PE;
}
}
chaz_CC.binary_format = binary_format;
free(output);
}
static const char chaz_CC_eval_macro_code[] =
CHAZ_QUOTE( #include <stdio.h> )
CHAZ_QUOTE( int main() { )
CHAZ_QUOTE( #ifndef %s )
CHAZ_QUOTE( #error "nope" )
CHAZ_QUOTE( #endif )
CHAZ_QUOTE( printf("%%d", %s); )
CHAZ_QUOTE( return 0; )
CHAZ_QUOTE( } );
static int
chaz_CC_eval_macro(const char *macro) {
size_t size = sizeof(chaz_CC_eval_macro_code)
+ (strlen(macro) * 2)
+ 20;
char *code = (char*)malloc(size);
int retval = 0;
char *output;
size_t len;
sprintf(code, chaz_CC_eval_macro_code, macro, macro);
output = chaz_CC_capture_output(code, &len);
if (output) {
retval = atoi(output);
free(output);
}
free(code);
return retval;
}
int
chaz_CC_has_macro(const char *macro) {
size_t size = sizeof(chaz_CC_eval_macro_code)
+ (strlen(macro) * 2)
+ 20;
char *code = (char*)malloc(size);
int retval = 0;
sprintf(code, chaz_CC_eval_macro_code, macro, macro);
retval = chaz_CC_test_compile(code);
free(code);
return retval;
}
static void
chaz_CC_detect_known_compilers(void) {
chaz_CC.intval___GNUC__ = chaz_CC_eval_macro("__GNUC__");
if (chaz_CC.intval___GNUC__) {
chaz_CC.intval___GNUC_MINOR__
= chaz_CC_eval_macro("__GNUC_MINOR__");
chaz_CC.intval___GNUC_PATCHLEVEL__
= chaz_CC_eval_macro("__GNUC_PATCHLEVEL__");
sprintf(chaz_CC.gcc_version_str, "%d.%d.%d", chaz_CC.intval___GNUC__,
chaz_CC.intval___GNUC_MINOR__,
chaz_CC.intval___GNUC_PATCHLEVEL__);
}
chaz_CC.intval__MSC_VER = chaz_CC_eval_macro("_MSC_VER");
chaz_CC.intval___clang__ = chaz_CC_eval_macro("__clang__");
chaz_CC.intval___SUNPRO_C = chaz_CC_eval_macro("__SUNPRO_C");
}
void
chaz_CC_clean_up(void) {
free(chaz_CC.cc_command);
free(chaz_CC.cflags);
free(chaz_CC.try_exe_name);
chaz_CFlags_destroy(chaz_CC.extra_cflags);
chaz_CFlags_destroy(chaz_CC.temp_cflags);
}
int
chaz_CC_compile_exe(const char *source_path, const char *exe_name,
const char *code) {
chaz_CFlags *local_cflags = chaz_CFlags_new(chaz_CC.cflags_style);
const char *extra_cflags_string = "";
const char *temp_cflags_string = "";
const char *local_cflags_string;
char *exe_file = chaz_Util_join("", exe_name, chaz_CC.exe_ext, NULL);
char *command;
int result;
/* Write the source file. */
chaz_Util_write_file(source_path, code);
/* Prepare and run the compiler command. */
if (chaz_CC.extra_cflags) {
extra_cflags_string = chaz_CFlags_get_string(chaz_CC.extra_cflags);
}
if (chaz_CC.temp_cflags) {
temp_cflags_string = chaz_CFlags_get_string(chaz_CC.temp_cflags);
}
chaz_CFlags_set_output_exe(local_cflags, exe_file);
local_cflags_string = chaz_CFlags_get_string(local_cflags);
command = chaz_Util_join(" ", chaz_CC.cc_command, chaz_CC.cflags,
source_path, extra_cflags_string,
temp_cflags_string, local_cflags_string, NULL);
if (chaz_Util_verbosity < 2) {
chaz_OS_run_quietly(command);
}
else {
printf("%s\n", command);
system(command);
}
if (chaz_CC.intval__MSC_VER) {
/* Zap MSVC junk. */
size_t junk_buf_size = strlen(exe_file) + 4;
char *junk = (char*)malloc(junk_buf_size);
sprintf(junk, "%s.obj", exe_name);
chaz_Util_remove_and_verify(junk);
sprintf(junk, "%s.ilk", exe_name);
chaz_Util_remove_and_verify(junk);
sprintf(junk, "%s.pdb", exe_name);
chaz_Util_remove_and_verify(junk);
free(junk);
}
/* See if compilation was successful. Remove the source file. */
result = chaz_Util_can_open_file(exe_file);
if (!chaz_Util_remove_and_verify(source_path)) {
chaz_Util_die("Failed to remove '%s'", source_path);
}
chaz_CFlags_destroy(local_cflags);
free(command);
free(exe_file);
return result;
}
int
chaz_CC_compile_obj(const char *source_path, const char *obj_name,
const char *code) {
chaz_CFlags *local_cflags = chaz_CFlags_new(chaz_CC.cflags_style);
const char *extra_cflags_string = "";
const char *temp_cflags_string = "";
const char *local_cflags_string;
char *obj_file = chaz_Util_join("", obj_name, chaz_CC.obj_ext, NULL);
char *command;
int result;
/* Write the source file. */
chaz_Util_write_file(source_path, code);
/* Prepare and run the compiler command. */
if (chaz_CC.extra_cflags) {
extra_cflags_string = chaz_CFlags_get_string(chaz_CC.extra_cflags);
}
if (chaz_CC.temp_cflags) {
temp_cflags_string = chaz_CFlags_get_string(chaz_CC.temp_cflags);
}
chaz_CFlags_set_output_obj(local_cflags, obj_file);
local_cflags_string = chaz_CFlags_get_string(local_cflags);
command = chaz_Util_join(" ", chaz_CC.cc_command, chaz_CC.cflags,
source_path, extra_cflags_string,
temp_cflags_string, local_cflags_string, NULL);
if (chaz_Util_verbosity < 2) {
chaz_OS_run_quietly(command);
}
else {
printf("%s\n", command);
system(command);
}
/* See if compilation was successful. Remove the source file. */
result = chaz_Util_can_open_file(obj_file);
if (!chaz_Util_remove_and_verify(source_path)) {
chaz_Util_die("Failed to remove '%s'", source_path);
}
chaz_CFlags_destroy(local_cflags);
free(command);
free(obj_file);
return result;
}
int
chaz_CC_test_compile(const char *source) {
int compile_succeeded;
char *try_obj_name
= chaz_Util_join("", CHAZ_CC_TRY_BASENAME, chaz_CC.obj_ext, NULL);
if (!chaz_Util_remove_and_verify(try_obj_name)) {
chaz_Util_die("Failed to delete file '%s'", try_obj_name);
}
compile_succeeded = chaz_CC_compile_obj(CHAZ_CC_TRY_SOURCE_PATH,
CHAZ_CC_TRY_BASENAME, source);
chaz_Util_remove_and_verify(try_obj_name);
free(try_obj_name);
return compile_succeeded;
}
int
chaz_CC_test_link(const char *source) {
int link_succeeded;
if (!chaz_Util_remove_and_verify(chaz_CC.try_exe_name)) {
chaz_Util_die("Failed to delete file '%s'", chaz_CC.try_exe_name);
}
link_succeeded = chaz_CC_compile_exe(CHAZ_CC_TRY_SOURCE_PATH,
CHAZ_CC_TRY_BASENAME, source);
chaz_Util_remove_and_verify(chaz_CC.try_exe_name);
return link_succeeded;
}
char*
chaz_CC_capture_output(const char *source, size_t *output_len) {
char *captured_output = NULL;
int compile_succeeded;
/* Clear out previous versions and test to make sure removal worked. */
if (!chaz_Util_remove_and_verify(chaz_CC.try_exe_name)) {
chaz_Util_die("Failed to delete file '%s'", chaz_CC.try_exe_name);
}
if (!chaz_Util_remove_and_verify(CHAZ_CC_TARGET_PATH)) {
chaz_Util_die("Failed to delete file '%s'", CHAZ_CC_TARGET_PATH);
}
/* Attempt compilation; if successful, run app and slurp output. */
compile_succeeded = chaz_CC_compile_exe(CHAZ_CC_TRY_SOURCE_PATH,
CHAZ_CC_TRY_BASENAME, source);
if (compile_succeeded) {
chaz_OS_run_local_redirected(chaz_CC.try_exe_name,
CHAZ_CC_TARGET_PATH);
captured_output = chaz_Util_slurp_file(CHAZ_CC_TARGET_PATH,
output_len);
}
else {
*output_len = 0;
}
/* Remove all the files we just created. */
chaz_Util_remove_and_verify(CHAZ_CC_TRY_SOURCE_PATH);
chaz_Util_remove_and_verify(chaz_CC.try_exe_name);
chaz_Util_remove_and_verify(CHAZ_CC_TARGET_PATH);
return captured_output;
}
const char*
chaz_CC_get_cc(void) {
return chaz_CC.cc_command;
}
const char*
chaz_CC_get_cflags(void) {
return chaz_CC.cflags;
}
chaz_CFlags*
chaz_CC_get_extra_cflags(void) {
return chaz_CC.extra_cflags;
}
chaz_CFlags*
chaz_CC_get_temp_cflags(void) {
return chaz_CC.temp_cflags;
}
chaz_CFlags*
chaz_CC_new_cflags(void) {
return chaz_CFlags_new(chaz_CC.cflags_style);
}
int
chaz_CC_binary_format(void) {
return chaz_CC.binary_format;
}
const char*
chaz_CC_exe_ext(void) {
return chaz_CC.exe_ext;
}
const char*
chaz_CC_shared_lib_ext(void) {
return chaz_CC.shared_lib_ext;
}
const char*
chaz_CC_static_lib_ext(void) {
return chaz_CC.static_lib_ext;
}
const char*
chaz_CC_import_lib_ext(void) {
return chaz_CC.import_lib_ext;
}
const char*
chaz_CC_obj_ext(void) {
return chaz_CC.obj_ext;
}
int
chaz_CC_gcc_version_num(void) {
return 10000 * chaz_CC.intval___GNUC__
+ 100 * chaz_CC.intval___GNUC_MINOR__
+ chaz_CC.intval___GNUC_PATCHLEVEL__;
}
const char*
chaz_CC_gcc_version(void) {
return chaz_CC.intval___GNUC__ ? chaz_CC.gcc_version_str : NULL;
}
int
chaz_CC_msvc_version_num(void) {
return chaz_CC.intval__MSC_VER;
}
int
chaz_CC_sun_c_version_num(void) {
return chaz_CC.intval___SUNPRO_C;
}
int
chaz_CC_is_cygwin(void) {
return chaz_CC.is_cygwin;
}
int
chaz_CC_is_mingw(void) {
return chaz_CC.is_mingw;
}
const char*
chaz_CC_link_command() {
if (chaz_CC.intval__MSC_VER) {
return "link";
}
else {
return chaz_CC.cc_command;
}
}
char*
chaz_CC_format_archiver_command(const char *target, const char *objects) {
if (chaz_CC.intval__MSC_VER) {
/* TODO: Write `objects` to a temporary file in order to avoid
* exceeding line length limits. */
char *out = chaz_Util_join("", "/OUT:", target, NULL);
char *command = chaz_Util_join(" ", "lib", "/NOLOGO", objects, out,
NULL);
free(out);
return command;
}
else {
return chaz_Util_join(" ", "ar", "rcs", target, objects, NULL);
}
}
char*
chaz_CC_format_ranlib_command(const char *target) {
if (chaz_CC.intval__MSC_VER) {
return NULL;
}
return chaz_Util_join(" ", "ranlib", target, NULL);
}
char*
chaz_CC_shared_lib_filename(const char *dir, const char *basename,
const char *version) {
/* Cygwin uses a "cyg" prefix for shared libraries. */
const char *prefix = chaz_CC_msvc_version_num()
? ""
: chaz_CC_is_cygwin() ? "cyg" : "lib";
return chaz_CC_build_lib_filename(dir, prefix, basename, version,
chaz_CC.shared_lib_ext);
}
char*
chaz_CC_import_lib_filename(const char *dir, const char *basename,
const char *version) {
const char *prefix = chaz_CC_msvc_version_num() ? "" : "lib";
return chaz_CC_build_lib_filename(dir, prefix, basename, version,
chaz_CC.import_lib_ext);
}
char*
chaz_CC_export_filename(const char *dir, const char *basename,
const char *version) {
/* Only for MSVC. */
return chaz_CC_build_lib_filename(dir, "", basename, version, ".exp");
}
static char*
chaz_CC_build_lib_filename(const char *dir, const char *prefix,
const char *basename, const char *version,
const char *ext) {
char *suffix;
char *retval;
if (version == NULL) {
suffix = chaz_Util_strdup(ext);
}
else {
int binary_format = chaz_CC_binary_format();
if (binary_format == CHAZ_CC_BINFMT_PE) {
suffix = chaz_Util_join("", "-", version, ext, NULL);
}
else if (binary_format == CHAZ_CC_BINFMT_MACHO) {
suffix = chaz_Util_join("", ".", version, ext, NULL);
}
else if (binary_format == CHAZ_CC_BINFMT_ELF) {
suffix = chaz_Util_join("", ext, ".", version, NULL);
}
else {
chaz_Util_die("Unsupported binary format");
return NULL;
}
}
if (dir == NULL || strcmp(dir, ".") == 0) {
retval = chaz_Util_join("", prefix, basename, suffix, NULL);
}
else {
const char *dir_sep = chaz_OS_dir_sep();
retval = chaz_Util_join("", dir, dir_sep, prefix, basename, suffix,
NULL);
}
free(suffix);
return retval;
}
char*
chaz_CC_static_lib_filename(const char *dir, const char *basename) {
const char *prefix = chaz_CC_msvc_version_num() ? "" : "lib";
if (dir == NULL || strcmp(dir, ".") == 0) {
return chaz_Util_join("", prefix, basename, chaz_CC.static_lib_ext,
NULL);
}
else {
const char *dir_sep = chaz_OS_dir_sep();
return chaz_Util_join("", dir, dir_sep, prefix, basename,
chaz_CC.static_lib_ext, NULL);
}
}