/* $Id: detect.c 1921 2006-10-26 20:09:13Z coudercd $ */

/*
 * Copyright (c) 2003-2006 Damien Couderc
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *    - Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    - Redistributions in binary form must reproduce the above
 *      copyright notice, this list of conditions and the following
 *      disclaimer in the documentation and/or other materials provided
 *      with the distribution.
 *    - Neither the name of the copyright holder(s) nor the names of its
 *      contributors may be used to endorse or promote products derived
 *      from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */


#include <errno.h>
#include <stdlib.h>

#include "compat/pmk_stdio.h"
#include "compat/pmk_unistd.h"
#include "common.h"
#include "detect.h"
#include "lang.h"


/***************
 * keyword data *
 ***********************************************************************/

/*
 * ADD_COMPILER options
 */
kw_t	req_addcomp[] = {
	{CC_KW_ID,	PO_STRING},
	{CC_KW_DESCR,	PO_STRING},
	{CC_KW_MACRO,	PO_STRING}
};

kw_t	opt_addcomp[] = {
	{CC_KW_VERSION,		PO_STRING},
	{CC_KW_SLCFLAGS,	PO_STRING},
	{CC_KW_SLLDFLAGS,	PO_STRING}
};

kwopt_t	kw_addcomp = {
	req_addcomp,
	sizeof(req_addcomp) / sizeof(kw_t),
	opt_addcomp,
	sizeof(opt_addcomp) / sizeof(kw_t)
};

/*
 * ADD_SYSTEM options
 */
kw_t	req_addsys[] = {
	{SYS_KW_NAME,		PO_STRING},
	{SYS_KW_SH_EXT,		PO_STRING},
	{SYS_KW_SH_NONE,	PO_STRING},
	{SYS_KW_SH_VERS,	PO_STRING},
	{SYS_KW_ST_EXT,		PO_STRING},
	{SYS_KW_ST_NONE,	PO_STRING},
	{SYS_KW_ST_VERS,	PO_STRING},
	{SYS_KW_VERSION,	PO_STRING}
};

kwopt_t	kw_addsys = {
	req_addsys,
	sizeof(req_addsys) / sizeof(kw_t),
	NULL,	/* allow 'custom' options */
	0
};

prskw	kw_pmkcomp[] = {
	{"ADD_COMPILER",	PCC_TOK_ADDC,	PRS_KW_CELL,	PRS_TOK_NULL,	&kw_addcomp},
	{"ADD_SYSTEM",		PCC_TOK_ADDS,	PRS_KW_CELL,	PRS_TOK_NULL,	&kw_addsys}
};
int	nbkwpc = sizeof(kw_pmkcomp) / sizeof(prskw);


/*************
 * procedures *
 ***********************************************************************/

/*****************************
 * %PROC init_compiler_data() *
 ***********************************************************************
 * %DESCR initialize compiler data structure
 *
 * %PARAM pcd:	compiler data structure
 * %PARAM sz:	number of languages
 *
 * %RETURN boolean status
 ***********************************************************************/

bool init_compiler_data(comp_data_t *pcd, size_t lnb) {
	int	i;

	/* record size of compiler profile array */
	pcd->sz = lnb;

	/* allocate array of compiler profiles */
	pcd->data = (compiler_t *) malloc(sizeof(compiler_t) * lnb);

	if (pcd->data == NULL) {
		return false;
	}

	/* initialize every cell of the array */
	for (i = 0 ; i < (int) lnb ; i++) {
		pcd->data[i].c_id = NULL;
		pcd->data[i].descr = NULL;
		pcd->data[i].c_macro = NULL;
		pcd->data[i].v_macro = NULL;
		pcd->data[i].slcflags = NULL;
		pcd->data[i].slldflags = NULL;
		pcd->data[i].version = NULL;
		pcd->data[i].lang = LANG_UNKNOWN;
	}

	return true;
}


/******************************
 * %PROC clean_compiler_cell() *
 ***********************************************************************
 * %DESCR initialize compiler data structure
 *
 * %PARAM pc:	compiler cell structure
 *
 * %RETURN boolean status
 ***********************************************************************/

void clean_compiler_cell(compiler_t *pc) {
	if (pc->c_id != NULL) {
		free(pc->c_id);
	}

	if (pc->descr != NULL) {
		free(pc->descr);
	}

	if (pc->c_macro != NULL) {
		free(pc->c_macro);
	}

	if (pc->v_macro != NULL) {
		free(pc->v_macro);
	}

	if (pc->slcflags != NULL) {
		free(pc->slcflags);
	}

	if (pc->slldflags != NULL) {
		free(pc->slldflags);
	}

	if (pc->version != NULL) {
		free(pc->version);
	}
}


/******************************
 * %PROC clean_compiler_data() *
 ***********************************************************************
 * %DESCR initialize compiler data structure
 *
 * %PARAM pcd:	compiler data structure
 *
 * %RETURN boolean status
 ***********************************************************************/

void clean_compiler_data(comp_data_t *pcd) {
	int	i;

	/* clean every cell of the array */
	for (i = 0 ; i < (int) pcd->sz ; i++) {
		clean_compiler_cell(&(pcd->data[i]));
	}

	/* free allocated memory of the	array */
	free(pcd->data);

	pcd->sz = 0;
}

	
/***************************
 * %PROC compcell_destroy() *
 ***********************************************************************
 * %DESCR clean comp_prscell_t structure
 *
 * %PARAM pcc: structure to clean
 *
 * %RETURN NONE
 ***********************************************************************/

void compcell_destroy(comp_prscell_t *pcc) {
	free(pcc->c_id);
	free(pcc->descr);
	free(pcc->c_macro);
	free(pcc->v_macro);
	free(pcc->slcflags);
	free(pcc->slldflags);
	free(pcc);
}


/**************************
 * %PROC init_comp_parse() *
 ***********************************************************************
 * %DESCR initialize comp_parse_t structure from compiler data file
 *
 * %RETURN new structure or NULL on failure
 ***********************************************************************/

comp_parse_t *init_comp_parse(void) {
	comp_parse_t	*pcp;

	/* allocate compiler data structure */
	pcp = (comp_parse_t *) malloc(sizeof(comp_parse_t));
	if (pcp == NULL) {
		return NULL;
	}

	/* init compilers hash table */
	pcp->cht = hash_init_adv(MAX_COMP, NULL, (void (*)(void *)) compcell_destroy, NULL);
	if (pcp->cht == NULL) {
		free(pcp);
		/*debugf("cannot initialize comp_data");*/
		return NULL;
	}

	/* init systems hash table */
	pcp->sht = hash_init(MAX_OS);
	if (pcp->sht == NULL) {
		free(pcp);
		hash_destroy(pcp->sht);
		/*debugf("cannot initialize comp_data");*/
		return NULL;
	}

	/* init ok */
	return pcp;
}


/*****************************
 * %PROC destroy_comp_parse() *
 ***********************************************************************
 * %DESCR destroy comp_parse_t structure
 *
 * %PARAM pcp:	compiler parsed data structure
 *
 * %RETURN none
 ***********************************************************************/

void destroy_comp_parse(comp_parse_t *pcp) {
	/* destroy compiler cell hash table */
	hash_destroy(pcp->cht);

	/* destroy system cell hash table */
	hash_destroy(pcp->sht);
}


/***********************
 * %PROC add_compiler() *
 ***********************************************************************
 * %DESCR add a new compiler cell
 *
 * %PARAM pcp:	compiler parsed data structure
 * %PARAM pht:	compiler cell hash table
 *
 * %RETURN boolean
 ***********************************************************************/

bool add_compiler(comp_parse_t *pcp, htable *pht) {
	comp_prscell_t	*pcell;
	char			*pstr,
					 tstr[TMP_BUF_LEN];

	pcell = (comp_prscell_t *) malloc(sizeof(comp_prscell_t));
	if (pcell == NULL)
		return false;

	pstr = po_get_str(hash_get(pht, CC_KW_ID));
	if (pstr == NULL) {
		free(pcell);
		return false;
	} else {
		pcell->c_id = strdup(pstr);
	}

	pstr = po_get_str(hash_get(pht, CC_KW_DESCR));
	if (pstr == NULL) {
		free(pcell);
		return false;
	} else {
		pcell->descr = strdup(pstr);
	}

	pstr = po_get_str(hash_get(pht, CC_KW_MACRO));
	if (pstr == NULL) {
		free(pcell);
		return false;
	} else {
		pcell->c_macro = strdup(pstr);
	}

	pstr = po_get_str(hash_get(pht, CC_KW_VERSION));
	if (pstr == NULL) {
		pcell->v_macro = strdup(DEF_NOVERSION);
	} else {
		if (snprintf_b(tstr, sizeof(tstr),
					DEF_VERSION, pstr) == false) {
			free(pcell);
			return false;
		}
		pcell->v_macro = strdup(tstr);
	}

	pstr = po_get_str(hash_get(pht, CC_KW_SLCFLAGS));
	if (pstr == NULL) {
		pcell->slcflags = strdup(""); /* default to empty string */
	} else {
		pcell->slcflags = strdup(pstr);
	}

	pstr = po_get_str(hash_get(pht, CC_KW_SLLDFLAGS));
	if (pstr == NULL) {
		pcell->slldflags = strdup(""); /* default to empty string */
	} else {
		pcell->slldflags = strdup(pstr);
	}

	/* no need to strdup */
	if (hash_update(pcp->cht, pcell->c_id, pcell) == HASH_ADD_FAIL) {
		return false;
	}

	return true;
}


/*********************
 * %PROC add_system() *
 ***********************************************************************
 * %DESCR add a new system cell
 *
 * %PARAM pcp:	compiler parsed data structure
 * %PARAM pht:	system cell hash table
 *
 * %RETURN boolean
 ***********************************************************************/

bool add_system(comp_parse_t *pcp, htable *pht, char *osname) {
	char			*name,
					*pstr;
	hkeys			*phk;
	unsigned int	 i;

	name = po_get_str(hash_get(pht, SYS_KW_NAME));
	if (name == NULL) {
		return false;
	}

	if (strncmp(osname, name, sizeof(osname)) != 0) {
		/* not the target system, skipping */
		return true;
	}

	hash_add(pht, SL_SYS_LABEL, po_mk_str(strdup(name))); /* XXX check */
	hash_delete(pht, SYS_KW_NAME); /* XXX check ? */

	/* remaining values are system overrides, save it */
	phk = hash_keys(pht);
	for (i = 0 ; i < phk->nkey ; i++) {
		pstr = po_get_str(hash_get(pht, phk->keys[i]))	;
		hash_update_dup(pcp->sht, phk->keys[i], pstr); /* XXX check */
	}

	return true;
}


/**************************
 * %PROC parse_comp_file() *
 ***********************************************************************
 * %DESCR parse compiler data file
 *
 * %PARAM cdfile:	compiler data file to parse
 * %PARAM osname:	name of the target OS
 *
 * %RETURN pointer to allocated compiler parsing structure
 ***********************************************************************/

comp_parse_t *parse_comp_file(char *cdfile, char *osname) {
	FILE			*fd;
	bool			 rval;
	comp_parse_t	*pcp;
	prscell			*pcell;
	prsdata			*pdata;

	/* try to open compilers data file */
	fd = fopen(cdfile, "r");
	if (fd == NULL) {
		errorf("cannot open '%s' : %s.", cdfile, strerror(errno));
		return NULL;
	}

	/* init compiler data structure */
	pcp = init_comp_parse();
	if (pcp == NULL) {
		fclose(fd);
		return NULL;
	}

	/* initialize parsing structure */
	pdata = prsdata_init();
	if (pdata == NULL) {
		errorf("cannot initialize prsdata.");
		destroy_comp_parse(pcp);
		fclose(fd);
		return NULL;
	}

	/* parse data file and fill prsdata strucutre */
	rval = parse_pmkfile(fd, pdata, kw_pmkcomp, nbkwpc);
	fclose(fd);

	if (rval == true) {
		pcell = pdata->tree->first;

		while (pcell != NULL) {
			switch(pcell->token) {
				case PCC_TOK_ADDC :
					if (add_compiler(pcp, pcell->data) == false) {
						errorf("add_compiler() failed in parse_comp_file_adv().");
						destroy_comp_parse(pcp);
						prsdata_destroy(pdata);
						return NULL;
					}
					break;

				case PCC_TOK_ADDS :
					if (osname != NULL) {
						if (add_system(pcp, pcell->data, osname) == false) {
							errorf("add_system() failed in parse_comp_file_adv().");
							destroy_comp_parse(pcp);
							prsdata_destroy(pdata);
							return NULL;
						}
					}
					break;

				default :
					errorf("parsing of data file failed.");
					destroy_comp_parse(pcp);
					prsdata_destroy(pdata);
					return NULL;
					break;
			}

			pcell = pcell->next;
		}
	} else {
		errorf("parsing of data file failed.");
		destroy_comp_parse(pcp);
		prsdata_destroy(pdata);
		return NULL;
	}

	/* parsing data no longer needed */
	prsdata_destroy(pdata);

	return pcp;
}


/************************
 * %PROC gen_test_file() *
 ***********************************************************************
 * %DESCR generate test file
 *
 * %PARAM pcp:		compiler data structure
 * %PARAM fnbuf:	filename buffer
 * %PARAM bsz:		buffer size
 *
 * %RETURN boolean status
 ***********************************************************************/

bool gen_test_file(comp_parse_t *pcp, char *fnbuf, size_t bsz) {
	FILE				*fp;
	comp_prscell_t		*pcell;
	hkeys				*phk;
	unsigned int		 i;

	/* get key list of parsed compiler data */
	phk = hash_keys(pcp->cht);
	if (phk == NULL) {
		errorf("no parsed compiler data found");
		return false;
	}

	/* open source file for writing */
	fp = tmps_open(CC_TEST_FILE, "w", fnbuf, bsz, strlen(CC_TFILE_EXT));
	if (fp == NULL) {
		errorf("cannot open compiler test file ('%s').", fnbuf);
		hash_free_hkeys(phk);
		return false;
	}

	/*
	 * fill the source test file with the code that try to identify the compiler
	 */
	fprintf(fp, COMP_TEST_HEADER);

	for (i = 0 ; i < phk->nkey ; i++) {
		pcell = hash_get(pcp->cht, phk->keys[i]);
		fprintf(fp, COMP_TEST_FORMAT, pcell->descr, pcell->c_macro, pcell->c_id, pcell->v_macro);
	}

	fprintf(fp, COMP_TEST_FOOTER);

	fclose(fp);
	hash_free_hkeys(phk);

	return true;
}


/**********************
 * %PROC comp_identify *
 ***********************************************************************
 * %DESCR identify given compiler
 *
 * %PARAM cpath:	path of the compiler to identify
 * %PARAM blog:		build log file name
 * %PARAM pc:		compiler cell to populate
 * %PARAM pcp:		compiler parsing structure
 *
 * %RETURN boolean
 ***********************************************************************/

bool comp_identify(char *cpath, char *blog, compiler_t *pc, comp_parse_t *pcp) {
	FILE		*rpipe;
	bool		 failed = false;
	char		 cfgcmd[MAXPATHLEN],
				 ftmp[MAXPATHLEN],
				 pipebuf[TMP_BUF_LEN];
	int			 r;

	if (gen_test_file(pcp, ftmp, sizeof(ftmp)) == false) {
		return false;
	}

	/* build compiler command */
	if (snprintf_b(cfgcmd, sizeof(cfgcmd), CC_TEST_FORMAT,
				cpath, CC_TEST_BIN, ftmp, blog) == false) {
		errorf("failed to build compiler command line.");
		return false;
	}

	/* get result */
	r = system(cfgcmd);

	/* test file no longer needed */
	if (unlink(ftmp) == -1) {
		/* cannot remove temporary file */
		errorf("cannot remove %s : %s.", ftmp, strerror(errno));
	}

	/* if compiled without error */
	if (r == 0) {
		rpipe = popen(CC_TEST_BIN, "r");
		if (rpipe != NULL) {
			if (get_line(rpipe, pipebuf, sizeof(pipebuf)) == false) {
				errorf("cannot get compiler id.");
				failed = true;
			} else {
				/* get compiler id */
				pc->c_id = strdup(pipebuf);

				if (get_line(rpipe, pipebuf, sizeof(pipebuf)) == false) {
					errorf("cannot get compiler version.");
					failed = true;
				} else {
					pc->version = strdup(pipebuf);
				}
			}

			pclose(rpipe);

		} else {
			errorf("failed to get output from test binary.");
		}

		/* delete binary */
		if (unlink(CC_TEST_BIN) == -1) {
			errorf("cannot remove %s : %s.", CC_TEST_BIN, strerror(errno));
		}

		if (failed == true) {
			return false;
		}
	} else {
		errorf("failed to build test binary : %s.", strerror(errno));
		return false;
	}

	return true;
}


/********************
 * %PROC comp_detect *
 ***********************************************************************
 * %DESCR identify given compiler
 *
 * %PARAM cpath:	path of the compiler to identify
 * %PARAM blog:		build log file name
 * %PARAM pc:		compiler cell to populate
 * %PARAM pcp:		compiler parsing structure
 * %PARAM label:		XXX
 *
 * %RETURN boolean
 ***********************************************************************/

bool comp_detect(char *cpath, char *blog, compiler_t *pc, comp_parse_t *pcp, char *label) {
	char			 buf[TMP_BUF_LEN],
					*pstr;
	comp_prscell_t	*pcell;

	if (comp_identify(cpath, blog, pc, pcp) == false) {
		return false;
	}

	/* look for the detected compiler data */
	pcell = hash_get(pcp->cht, pc->c_id);
	if (pcell == NULL) {
		errorf("unknown compiler identifier : %s\n", pc->c_id);
		return false;
	}

	/*
	 * populate compiler cell with identified compiler data
	 *
	 * c_id and version have already been set by comp_identify
	 * lang XXX ?
	 */
	pc->descr = strdup(pcell->descr);
	pc->c_macro = strdup(pcell->c_macro); /* XXX useless ? */
	pc->v_macro = strdup(pcell->v_macro); /* XXX useless ? */

	/*
	 * check system specific flags for shared library support
	 */

	/* generate tag for system specific compiler flags */
	if (snprintf_b(buf, sizeof(buf), "%s_%s", label, pc->c_id) == false) {
		errorf("overflow.\n");
		return(false);
	}

	/* check if an override exists for compiler flags */
	pstr = hash_get(pcp->sht, buf);
	if (pstr == NULL) {
		/* take compiler's generic flags */
		pc->slcflags = strdup(pcell->slcflags);
	} else {
		/* take system specific flags */
		pc->slcflags = strdup(pstr);
	}

	/* generate tag for system specific linker flags */
	if (snprintf_b(buf, sizeof(buf), "%s_%s", SL_LDFLAG_VARNAME, pc->c_id) == false) {
		errorf("overflow.\n");
		return(false);
	}

	/* check if an override exists for linker flags */
	pstr = hash_get(pcp->sht, buf);
	if (pstr == NULL) {
		/* take compiler's generic flags */
		pc->slldflags = strdup(pcell->slldflags);
	} else {
		/* take system specific flags */
		pc->slldflags = strdup(pstr);
	}
	
	/*pcd->data[i].lang = LANG_UNKNOWN;*/

	return true;
}

/* vim: set noexpandtab tabstop=4 softtabstop=4 shiftwidth=4: */


syntax highlighted by Code2HTML, v. 0.9.1