/*
    scconfig - detection of cc and compiler features
    Copyright (C) 2009..2012  Tibor Palinkas

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

		Project page: http://repo.hu/projects/scconfig
		Contact via email: scconfig [at] igor2.repo.hu
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "libs.h"
#include "log.h"
#include "db.h"
#include "dep.h"



static int try_flags(int logdepth, const char *cc, const char *test_c, const char *cflags, const char *ldflags, const char *expected)
{
	char *out;

	logprintf(logdepth, "trying cc:try_flags with cc='%s' cflags='%s' ldflags='%s'\n", (cc == NULL ? get("cc/cc") : cc), cflags == NULL ? "" : cflags, ldflags == NULL ? "" : ldflags);

	if (compile_run(logdepth+1, test_c, cc, cflags, ldflags, &out) == 0) {
		if (((out == NULL) && (iscross)) || (strncmp(out, expected, strlen(expected)) == 0)) {
			free(out);
			return 1;
		}
		free(out);
	}
	return 0;
}

static int try_flags_inv(int logdepth, const char *cc, const char *test_c, const char *cflags, const char *ldflags, const char *expected_bad)
{
	char *out;

	logprintf(logdepth, "trying cc:try_flags with cc='%s' cflags='%s' ldflags='%s'\n", (cc == NULL ? get("cc/cc") : cc), cflags == NULL ? "" : cflags, ldflags == NULL ? "" : ldflags);

	if (compile_run(logdepth+1, test_c, cc, cflags, ldflags, &out) == 0) {
		if (((out == NULL) && (iscross)) || (strncmp(out, expected_bad, strlen(expected_bad)) != 0)) {
			free(out);
			return 1;
		}
		free(out);
	}
	return 0;
}

static int try(int logdepth, const char *cc, const char *test_c, const char *expected)
{
	return try_flags(logdepth, cc, test_c, NULL, NULL, expected);
}


static int trycc(int logdepth, const char *cc, const char *test_c)
{
	int ret;

	if (cc == NULL)
		return 0;

	ret = try(logdepth, cc, test_c, "OK");
	if (ret)
		put("cc/cc", cc);
	return ret;
}

int find_cc(int logdepth, int fatal)
{
	char *test_c = "#include <stdio.h>\nint main() { printf(\"OK\\n\");\nreturn 0;}\n";
	char *out = NULL, *targetcc;
	const char *cc, *cflags, *ldflags, *target, *sys;
	int len;

	require("sys/name", logdepth, fatal);

	sys = istarget(db_cwd) ? "target" : "host";
	report("Checking for cc (%s)... ", sys);
	logprintf(logdepth, "find_cc: trying to find cc (%s)...\n", sys);
	logdepth++;

	cc = get("/arg/cc/cc");
	if (cc == NULL) {
		target = get("sys/target");
		if (target != NULL) {
			logprintf(logdepth+1, "find_cc: crosscompiling for '%s', looking for target cc\n", target);
			len = strlen(target);
			targetcc = malloc(len + 8);
			memcpy(targetcc, target, len);
			strcpy(targetcc + len, "-gcc");
			if (!trycc(logdepth+1, targetcc, test_c)) {
				strcpy(targetcc + len, "-cc");
				if (!trycc(logdepth+1, targetcc, test_c)) {
					report("FAILED: failed to find crosscompiler for target '%s'\n", target);
					logprintf(logdepth, "find_cc: FAILED to find a crosscompiler for target '%s'\n", target);
					return 1;
				}
			}
			put("cc/cc", targetcc);
		}
		else {
			cc = getenv("CC");
			logprintf(logdepth, "find_cc: Detecting cc (host)\n");
			/* Find a working cc (no arguments) */
			if (!(((cc != NULL) && (trycc(logdepth+1, cc, test_c))) || trycc(logdepth+1, "gcc", test_c) || trycc(logdepth+1, "cc", test_c))) {
				report("FAILED to find a compiler\n");
				logprintf(logdepth, "find_cc: FAILED to find a compiler\n");
				return 1;
			}
		}
	}
	else {
		put("cc/cc", cc);
		logprintf(logdepth+1, "using user supplied '%s' (will test later)\n", cc);
	}

	/* cflags */
	cflags = get("/arg/cc/cflags");
	if (cflags == NULL) {
		logprintf(logdepth, "find_cc: Detecting -pipe\n");

		if (compile_run(logdepth+1, test_c, NULL, "-pipe", "", &out) == 0) {
			if (target_emu_fail(out) || (strncmp(out, "OK", 2) == 0)) {
				append("cc/cflags", " -pipe");
			}
			free(out);
		}
	}
	else {
		logprintf(logdepth+1, "using user supplied cflags '%s' (will test later)\n", cflags);
	}
	if (get("cc/cflags") == NULL)
		put("cc/cflags", "");

	/* ldflags */
	ldflags = get("/arg/cc/ldflags");
	if (ldflags != NULL) {
		logprintf(logdepth+1, "using user supplied ldflags '%s' (will test later)\n", cflags);
		put("cc/ldflags", ldflags);
	}
	if (get("cc/ldflags") == NULL)
		put("cc/ldflags", "");

	/* Final test of all arguments together */
	logprintf(logdepth, "find_cc: final test on cc and all flags \n");
	if (compile_run(logdepth+1, test_c, NULL, NULL, NULL, &out) != 0) {
		report("FAILED to get the compiler and all flags to work together\n");
		logprintf(logdepth, "find_cc: the compiler and all the flags don't work well together, aborting\n");
		if (out != NULL)
			free(out);
		return 1;
	}

	report("OK ('%s', '%s', '%s')\n", get("cc/cc"), get("cc/cflags"), get("cc/ldflags"));
	logprintf(logdepth, "find_cc: conclusion: cc='%s' cflags='%s' ldflags='%s'\n", get("cc/cc"), get("cc/cflags"), get("cc/ldflags"));
	if (out != NULL)
		free(out);
	return 0;
}

int find_cc_argstd(int logdepth, int fatal)
{
	char *test_c = "#include <stdio.h>\nint main() { printf(\"OK\\n\");\nreturn 0;}\n";
	char *out = NULL;
	char **flg, *flags[] = {"-ansi", "-pedantic", "-Wall", NULL};

	require("cc/cc", logdepth, fatal);

	logprintf(logdepth, "find_cc: Detecting CC args\n");
	report("Checking for cc args for std... ");

	for(flg = flags; *flg != NULL; flg++) {
		char name[128];
		const char *found = "";
		sprintf(name, "cc/argstd/%s", (*flg)+1);
		if (compile_run(logdepth+1, test_c, NULL, *flg, "", &out) == 0) {
			if (target_emu_fail(out) || (strncmp(out, "OK", 2) == 0)) {
				found = *flg;
				report(" ");
				report(found);
			}
			free(out);
		}
		put(name, found);
	}

	report("\n");
	return 0;
}

int find_inline(int logdepth, int fatal)
{
	const char *test_c =
		NL "#include <stdio.h>"
		NL "static inline void test_inl()"
		NL "{"
		NL "	puts(\"OK\");"
		NL "}"
		NL "int main() {"
		NL "	test_inl();"
		NL "	return 0;"
		NL "}"
		NL ;
	require("cc/cc", logdepth, fatal);

	report("Checking for inline... ");
	logprintf(logdepth, "find_inline: trying to find inline...\n");
	logdepth++;
	if (try(logdepth, NULL, test_c, "OK")) {
		put("cc/inline", strue);
		report("Found.\n");
		return 0;
	}
	put("cc/inline", sfalse);
	report("Not found.\n");
	return 1;
}

int find_varargmacro(int logdepth, int fatal)
{
	const char *test_c =
		NL "#include <stdio.h>"
		NL "#define pr(fmt, x...) {printf(\"PR \"); printf(fmt, x); }"
		NL "int main() {"
		NL "	pr(\"%d %d %s\", 42, 8192, \"test\");"
		NL "	puts(\"\");"
		NL "	return 0;"
		NL "}"
		NL ;
	require("cc/cc", logdepth, fatal);

	report("Checking for vararg macro... ");
	logprintf(logdepth, "find_varargmacro: trying to find vararg macro...\n");
	logdepth++;
	if (try(logdepth, NULL, test_c, "PR 42 8192 test")) {
		put("cc/varargmacro", strue);
		report("Found.\n");
		return 0;
	}
	put("cc/varargmacro", sfalse);
	report("Not found.\n");
	return 1;
}

int find_funcmacro(int logdepth, int fatal)
{
	const char *test_c =
		NL "#include <stdio.h>"
		NL "int main() {"
		NL "	printf(\"%s\\n\", __func__);"
		NL "	return 0;"
		NL "}"
		NL ;
	require("cc/cc", logdepth, fatal);

	report("Checking for __func__ macro... ");
	logprintf(logdepth, "find_funcmacro: trying to find __func__ macro...\n");
	logdepth++;
	if (try(logdepth, NULL, test_c, "main")) {
		put("cc/funcmacro", strue);
		report("Found.\n");
		return 0;
	}
	put("cc/funcmacro", sfalse);
	report("Not found.\n");
	return 1;
}

int find_constructor(int logdepth, int fatal)
{
	const char *test_c =
		NL "#include <stdio.h>"
		NL "void startup() __attribute__ ((constructor));"
		NL "void startup()"
		NL "{"
		NL "	puts(\"OK\");"
		NL "}"
		NL "int main() {"
		NL "	return 0;"
		NL "}"
		NL ;

	require("cc/cc", logdepth, fatal);

	report("Checking for constructor... ");
	logprintf(logdepth, "find_constructor: trying to find constructor...\n");
	logdepth++;
	if (try(logdepth, NULL, test_c, "OK")) {
		put("cc/constructor", strue);
		report("Found.\n");
		return 0;
	}
	put("cc/constructor", sfalse);
	report("Not found.\n");
	return 1;
}

int find_destructor(int logdepth, int fatal)
{
	const char *test_c =
		NL "#include <stdio.h>"
		NL "void startup() __attribute__ ((destructor));"
		NL "void startup()"
		NL "{"
		NL "	puts(\"OK\");"
		NL "}"
		NL "int main() {"
		NL "	return 0;"
		NL "}"
		NL ;

	require("cc/cc", logdepth, fatal);

	report("Checking for destructor... ");
	logprintf(logdepth, "find_destructor: trying to find destructor...\n");
	logdepth++;
	if (try(logdepth, NULL, test_c, "OK")) {
		put("cc/destructor", strue);
		report("Found.\n");
		return 0;
	}
	put("cc/destructor", sfalse);
	report("Not found.\n");
	return 1;
}

static int test_fattr(int logdepth, int fatal, const char *fattr)
{
	char path[64];
	char test_c[256];
	const char *test_c_tmp =
		NL "#include <stdio.h>"
		NL "static void test1() __attribute__ ((%s));"
		NL "static void test1()"
		NL "{"
		NL "	puts(\"OK\");"
		NL "}"
		NL "int main() {"
		NL "	puts(\"OK\");"
		NL "	return 0;"
		NL "}"
		NL ;

	require("cc/cc", logdepth, fatal);

	sprintf(test_c, test_c_tmp, fattr);
	sprintf(path, "cc/func_attr/%s/presents", fattr);

	report("Checking for function attribute %s... ", fattr);
	logprintf(logdepth, "test_fattr: trying to find %s...\n", fattr);
	logdepth++;
	if (try(logdepth, NULL, test_c, "OK")) {
		put(path, strue);
		report("Found.\n");
		return 0;
	}
	put(path, sfalse);
	report("Not found.\n");
	return 1;
}

int find_fattr_unused(int logdepth, int fatal)
{
	return test_fattr(logdepth, fatal, "unused");
}

/* Hello world program to test compiler flags */
static const char *test_hello_world =
		NL "#include <stdio.h>"
		NL "int main() {"
		NL "	puts(\"OK\");"
		NL "	return 0;"
		NL "}"
		NL ;

static int try_hello(int logdepth, const char *cflags, const char *ldflags, const char *name, const char *value)
{
	if (try_flags(logdepth, NULL, test_hello_world, cflags, ldflags, "OK")) {
		put(name, value);
		report("OK (%s)\n", value);
		return 1;
	}
	return 0;
}

int find_rdynamic(int logdepth, int fatal)
{

	require("cc/cc", logdepth, fatal);

	report("Checking for rdynamic... ");
	logprintf(logdepth, "find_rdynamic: trying to find rdynamic...\n");
	logdepth++;

	if (try_hello(logdepth, NULL, "+-rdynamic", "cc/rdynamic", "-rdynamic")) return 0;
	if (try_hello(logdepth, NULL, NULL,         "cc/rdynamic", ""))          return 0;

	report("Not found.\n");
	return 1;
}

int find_soname(int logdepth, int fatal)
{

	require("cc/cc", logdepth, fatal);

	report("Checking for soname... ");
	logprintf(logdepth, "find_soname: trying to find soname...\n");
	logdepth++;

	if (try_hello(logdepth, NULL, "+-Wl,-soname,libscconfig.0", "cc/soname", "-Wl,-soname,")) return 0;
	if (try_hello(logdepth, NULL, NULL,                         "cc/soname", ""))             return 0;

	report("Not found.\n");
	return 1;
}


int find_wlrpath(int logdepth, int fatal)
{

	require("cc/cc", logdepth, fatal);

	report("Checking for soname... ");
	logprintf(logdepth, "find_soname: trying to find soname...\n");
	logdepth++;

	if (try_hello(logdepth, NULL, "+-Wl,-rpath=.",  "cc/wlrpath", "-Wl,-rpath=")) return 0;

	report("Not found.\n");
	return 1;
}

int find_fpic(int logdepth, int fatal)
{

	require("cc/cc", logdepth, fatal);

	report("Checking for -fpic... ");
	logprintf(logdepth, "find_fpic: trying to find -fpic...\n");
	logdepth++;

	if (try_hello(logdepth, NULL, "+-fPIC", "cc/fpic", "-fPIC")) return 0;
	if (try_hello(logdepth, NULL, "+-fpic", "cc/fpic", "-fpic")) return 0;
	if (try_hello(logdepth, NULL, NULL,     "cc/fpic", ""))      return 0;

	report("Not found.\n");
	return 1;
}

/* Hello world lib... */
static const char *test_lib =
		NL "#include <stdio.h>"
		NL "int hello() {"
		NL "	puts(\"OK\");"
		NL "	return 0;"
		NL "}"
		NL ;

/* ...and the corresponding host application */
static const char *test_host =
		NL "#include <stdlib.h>"
		NL "#include <stdio.h>"
		NL "#include %s"
		NL "int main() {"
		NL "	void *handle = NULL;"
		NL "	void (*func)() = NULL;"
		NL "	char *error;"
		NL
		NL "	handle = dlopen(\"%s\", RTLD_NOW);"
		NL "	if (handle == NULL) {"
		NL "		printf(\"dlopen fails: \", dlerror());"
		NL "		return 1;"
		NL "	}"
		NL "	func = dlsym(handle, \"hello\");"
		NL "	if (func == NULL) {"
		NL "		printf(\"dlsym fails: \", dlerror());"
		NL "		return 1;"
		NL "	}"
		NL "	func();"
		NL "	return 0;"
		NL "}"
		NL ;

static int try_dynlib(int logdepth, const char *cflags, char *concated_ldflags, const char *name, const char *value, const char *host_app_cflags, const char *host_app_ldflags)
{
	char test_host_app[1024];
	const char *fpic;
	const char *ld_include;
	const char *dlc;
	char *libname;
	char *cflags_c;
	char *oname = ".o";
	int ret = 0;

	dlc = get("libs/dl-compat");
	if ((dlc != NULL) && (strcmp(dlc, strue) == 0))
		ld_include = "<dl-compat.h>";
	else
		ld_include = "<dlfcn.h>";

	fpic = get("cc/fpic");

	if (cflags == NULL)
		cflags="";
	else if (*cflags == '+')
		cflags++;

	cflags_c = malloc(strlen(cflags) + 8 + sizeof(fpic));
	sprintf(cflags_c, "+%s -c %s", cflags, fpic);


	libname = (char *)get("sys/ext_dynlib");
	if ((compile_code(logdepth, test_lib, &oname, NULL, cflags_c, NULL) != 0) ||
			(compile_file(logdepth, oname, &libname, NULL, NULL, concated_ldflags) != 0)) {
			report("FAILED (compiling dynlib)\n");
	}
	else {
		sprintf(test_host_app, test_host, ld_include, libname);
		if (try_flags(logdepth, NULL, test_host_app, host_app_cflags, host_app_ldflags, "OK")) {
			put(name, value);
			report("OK (%s)\n", value);
			ret = 1;
		}
	}
	unlink(libname);
	unlink(oname);
	free(libname);
	free(oname);
	free(concated_ldflags);
	free(cflags_c);
	return ret;
}


int find_ldflags_dynlib(int logdepth, int fatal)
{

	require("cc/cc",       logdepth, fatal);
	require("cc/rdynamic", logdepth, fatal);
	require("cc/fpic",     logdepth, fatal);
	require("libs/ldl",    logdepth, fatal);

	report("Checking for dynamic library ldflags... ");
	logprintf(logdepth, "find_ldflags_dynlib: trying to find dynamic library ldflags...\n");
	logdepth++;

	if (try_dynlib(logdepth, NULL, concat_nodes("+-dynamic -shared", "cc/rdynamic", "libs/ldl", NULL), "cc/ldflags_dynlib", "-dynamic -shared", NULL, get("libs/ldl"))) return 0;
	if (try_dynlib(logdepth, NULL, concat_nodes("+-shared",          "cc/rdynamic", "libs/ldl", NULL), "cc/ldflags_dynlib", "-shared",          NULL, get("libs/ldl"))) return 0;
	report("Not found.\n");
	return 1;
}

/* Hello world program to test compiler flags */
static const char *test_alloca =
		NL "#include <stdio.h>"
		NL "int main() {"
		NL "	char *s;"
		NL "	s = alloca(128);"
		NL "	if (s != NULL)"
		NL "		puts(\"OK\");"
		NL "	return 0;"
		NL "}"
		NL ;

static int try_alloca(int logdepth, const char *cflags, const char *ldflags, const char *name, const char *value)
{
	if (try_flags(logdepth, NULL, test_alloca, cflags, ldflags, "OK")) {
		put(name, value);
		report("OK (%s)\n", value);
		return 1;
	}
	return 0;
}

int find_alloca(int logdepth, int fatal)
{
	require("cc/cc", logdepth, fatal);

	report("Checking for alloca()... ");
	logprintf(logdepth, "find_alloca: trying to find alloca()...\n");
	logdepth++;

	if (try_alloca(logdepth, NULL, NULL,     "cc/alloca/presents", "true"))      return 0;

	put("cc/alloca/presents", "false");
	report("Not found.\n");
	return 1;
}


int find__exit(int logdepth, int fatal)
{
	const char *test_c =
		NL "#include <stdio.h>"
		NL "int main() {"
		NL "	_exit(0);"
		NL "	puts(\"BAD\");"
		NL "	return 0;"
		NL "}"
		NL ;

	require("cc/cc", logdepth, fatal);

	report("Checking for _exit()... ");
	logprintf(logdepth, "find__exit: trying to find _exit()...\n");
	logdepth++;

	if (try_flags_inv(logdepth, NULL, test_c, "+", "+", "BAD")) {
		put("cc/_exit/presents", strue);
		report("found\n");
		return 0;
	}

	put("cc/_exit/presents", sfalse);
	report("Not found.\n");
	return 1;
}

