/* $Id: kbuild-read.c 2717 2013-12-30 00:58:43Z bird $ */ /** @file * kBuild specific make functionality related to read.c. */ /* * Copyright (c) 2011-2013 knut st. osmundsen * * This file is part of kBuild. * * kBuild is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * kBuild 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 kBuild. If not, see * */ /* No GNU coding style here! */ /******************************************************************************* * Header Files * *******************************************************************************/ #include "make.h" #include "filedef.h" #include "variable.h" #include "dep.h" #include "debug.h" #include "kbuild.h" #include #include /******************************************************************************* * Defined Constants And Macros * *******************************************************************************/ #define WORD_IS(a_pszWord, a_cchWord, a_szWord2) \ ( (a_cchWord) == sizeof(a_szWord2) - 1 && memcmp((a_pszWord), a_szWord2, sizeof(a_szWord2) - 1) == 0) /******************************************************************************* * Structures and Typedefs * *******************************************************************************/ /** kBuild object type. */ enum kBuildType { kBuildType_Invalid, kBuildType_Target, kBuildType_Template, kBuildType_Tool, kBuildType_Sdk, kBuildType_Unit }; enum kBuildSeverity { kBuildSeverity_Warning, kBuildSeverity_Error, kBuildSeverity_Fatal }; /** * kBuild object data. */ struct kbuild_object { /** The object type. */ enum kBuildType enmType; /** Object name length. */ size_t cchName; /** The bare name of the define. */ char *pszName; /** The file location where this define was declared. */ struct floc FileLoc; /** Pointer to the next element in the global list. */ struct kbuild_object *pGlobalNext; /** The variable set associated with this define. */ struct variable_set_list *pVariables; /** The parent name, NULL if none. */ char *pszParent; /** The length of the parent name. */ size_t cchParent; /** Pointer to the parent. Resolved lazily, so it can be NULL even if we * have a parent. */ struct kbuild_object *pParent; /** The template, NULL if none. Only applicable to targets. Only covers the * primary template, not target or type specific templates. * @todo not sure if this is really necessary. */ char const *pszTemplate; /** The variable prefix. */ char *pszVarPrefix; /** The length of the variable prefix. */ size_t cchVarPrefix; }; /** * The data we stack during eval. */ struct kbuild_eval_data { /** Pointer to the element below us on the stack. */ struct kbuild_eval_data *pStackDown; /** Pointer to the object. */ struct kbuild_object *pObj; /** The saved current variable set, for restoring in kBuild-endef. */ struct variable_set_list *pVariablesSaved; }; /******************************************************************************* * Header Files * *******************************************************************************/ /** Linked list (LIFO) of kBuild defines. * @todo use a hash! */ static struct kbuild_object *g_pHeadKbObjs = NULL; /** Stack of kBuild evalutation contexts. * This is for dealing with potential recursive kBuild object definition, * generally believed to only happen via $(eval ) or include similar. */ struct kbuild_eval_data *g_pTopKbEvalData = NULL; /** Cached variable name '_TEMPLATE'. */ static const char *g_pszVarNmTemplate = NULL; /** Zero if compatibility mode is disabled, non-zero if enabled. * If explicitily enabled, the value will be greater than 1. */ int g_fKbObjCompMode = 1; /******************************************************************************* * Internal Functions * *******************************************************************************/ static struct kbuild_object * eval_kbuild_resolve_parent(struct kbuild_object *pObj, int fQuiet); static struct kbuild_object * parse_kbuild_object_variable_accessor(const char *pchExpr, size_t cchExpr, enum kBuildSeverity enmSeverity, const struct floc *pFileLoc, const char **ppchVarNm, size_t *pcchVarNm, enum kBuildType *penmType); /** * Initializes the kBuild object stuff. * * Requires the variable_cache to be initialized. */ void init_kbuild_object(void) { g_pszVarNmTemplate = strcache2_add(&variable_strcache, STRING_SIZE_TUPLE("_TEMPLATE")); } /** * Reports a problem with dynamic severity level. * * @param enmSeverity The severity level. * @param pFileLoc The file location. * @param pszFormat The format string. * @param ... Arguments for the format string. */ static void kbuild_report_problem(enum kBuildSeverity enmSeverity, const struct floc *pFileLoc, const char *pszFormat, ...) { char szBuf[8192]; va_list va; va_start(va, pszFormat); #ifdef _MSC_VER _vsnprintf(szBuf, sizeof(szBuf), pszFormat, va); #else vsnprintf(szBuf, sizeof(szBuf), pszFormat, va); #endif va_end(va); switch (enmSeverity) { case kBuildSeverity_Warning: message(0, "%s", szBuf); break; case kBuildSeverity_Error: error(pFileLoc, "%s", szBuf); break; default: case kBuildSeverity_Fatal: fatal(pFileLoc, "%s", szBuf); break; } } /** * Helper function for caching variable name strings. * * @returns The string cache variable name. * @param pszName The variable name. * @param ppszCache Cache variable, static or global. Initialize to * NULL. */ static const char * kbuild_variable_name(const char *pszName, const char **ppszCache) { const char *pszRet = *ppszCache; if (!pszRet) *ppszCache = pszRet = strcache2_add(&variable_strcache, pszName, strlen(pszName)); return pszRet; } static struct kbuild_object * lookup_kbuild_object(enum kBuildType enmType, const char *pchName, size_t cchName) { /* Linear lookup for now. */ struct kbuild_object *pCur = g_pHeadKbObjs; while (pCur) { if ( pCur->enmType == enmType && pCur->cchName == cchName && !memcmp(pCur->pszName, pchName, cchName)) return pCur; pCur = pCur->pGlobalNext; } return NULL; } /** @name Defining and modifying variables * @{ */ /** * Checks if the variable name is valid. * * @returns 1 if valid, 0 if not. * @param pchName The variable name. * @param cchName The length of the variable name. */ static int is_valid_kbuild_object_variable_name(const char *pchName, size_t cchName) { if (cchName > 0) { if (!memchr(pchName, '[', cchName)) { /** @todo more? */ return 1; } } return 0; } static struct variable * define_kbuild_object_variable_cached(struct kbuild_object *pObj, const char *pszName, const char *pchValue, size_t cchValue, int fDuplicateValue, enum variable_origin enmOrigin, int fRecursive, const struct floc *pFileLoc) { struct variable *pVar; size_t cchName = strcache2_get_len(&variable_strcache, pszName); pVar = define_variable_in_set(pszName, cchName, pchValue, cchValue, fDuplicateValue, enmOrigin, fRecursive, pObj->pVariables->set, pFileLoc); /* Single underscore prefixed variables gets a global alias. */ if ( pszName[0] == '_' && pszName[1] != '_' && g_fKbObjCompMode) { size_t cchPrefixed = pObj->cchVarPrefix + cchName; char *pszPrefixed = xmalloc(cchPrefixed + 1); memcpy(pszPrefixed, pObj->pszVarPrefix, pObj->cchVarPrefix); memcpy(&pszPrefixed[pObj->cchVarPrefix], pszName, cchName); pszPrefixed[cchPrefixed] = '\0'; /** @todo implement variable aliases or something. */ define_variable_in_set(pszPrefixed, cchPrefixed, pchValue, cchValue, 1 /*duplicate_value*/, enmOrigin, fRecursive, &global_variable_set, pFileLoc); } return pVar; } #if 0 struct variable * define_kbuild_object_variable(struct kbuild_object *pObj, const char *pchName, size_t cchName, const char *pchValue, size_t cchValue, int fDuplicateValue, enum variable_origin enmOrigin, int fRecursive, const struct floc *pFileLoc) { return define_kbuild_object_variable_cached(pObj, strcache2_add(&variable_strcache, pchName, cchName), pchValue, cchValue, fDuplicateValue, enmOrigin, fRecursive, pFileLoc); } #endif /** * Try define a kBuild object variable via a possible accessor * ([type@object]var). * * @returns Pointer to the defined variable on success. * @retval VAR_NOT_KBUILD_ACCESSOR if it isn't an accessor. * * @param pchName The variable name, not cached. * @param cchName The variable name length. This will not be ~0U. * @param pszValue The variable value. If @a fDuplicateValue is clear, * this should be assigned as the actual variable * value, otherwise it will be duplicated. In the * latter case it might not be properly null * terminated. * @param cchValue The value length. * @param fDuplicateValue Whether @a pszValue need to be duplicated on the * heap or is already there. * @param enmOrigin The variable origin. * @param fRecursive Whether it's a recursive variable. * @param pFileLoc The location of the variable definition. */ struct variable * try_define_kbuild_object_variable_via_accessor(const char *pchName, size_t cchName, const char *pszValue, size_t cchValue, int fDuplicateValue, enum variable_origin enmOrigin, int fRecursive, struct floc const *pFileLoc) { struct kbuild_object *pObj; const char *pchVarNm; size_t cchVarNm; pObj = parse_kbuild_object_variable_accessor(pchName, cchName, kBuildSeverity_Fatal, pFileLoc, &pchVarNm, &cchVarNm, NULL); if (pObj != KOBJ_NOT_KBUILD_ACCESSOR) { assert(pObj != NULL); if (!is_valid_kbuild_object_variable_name(pchVarNm, cchVarNm)) fatal(pFileLoc, _("Invalid kBuild object variable name: '%.*s' ('%s')"), (int)cchVarNm, pchVarNm, (int)cchName, pchName); return define_kbuild_object_variable_cached(pObj, strcache2_add(&variable_strcache, pchVarNm, cchVarNm), pszValue, cchValue, fDuplicateValue, enmOrigin, fRecursive, pFileLoc); } return VAR_NOT_KBUILD_ACCESSOR; } /** * Define a kBuild object variable in the topmost kBuild object. * * This won't be an variable accessor. * * @returns Pointer to the defined variable on success. * * @param pchName The variable name, not cached. * @param cchName The variable name length. This will not be ~0U. * @param pszValue The variable value. If @a fDuplicateValue is clear, * this should be assigned as the actual variable * value, otherwise it will be duplicated. In the * latter case it might not be properly null * terminated. * @param cchValue The value length. * @param fDuplicateValue Whether @a pszValue need to be duplicated on the * heap or is already there. * @param enmOrigin The variable origin. * @param fRecursive Whether it's a recursive variable. * @param pFileLoc The location of the variable definition. */ struct variable * define_kbuild_object_variable_in_top_obj(const char *pchName, size_t cchName, const char *pszValue, size_t cchValue, int fDuplicateValue, enum variable_origin enmOrigin, int fRecursive, struct floc const *pFileLoc) { assert(g_pTopKbEvalData != NULL); if (!is_valid_kbuild_object_variable_name(pchName, cchName)) fatal(pFileLoc, _("Invalid kBuild object variable name: '%.*s'"), (int)cchName, pchName); return define_kbuild_object_variable_cached(g_pTopKbEvalData->pObj, strcache2_add(&variable_strcache, pchName, cchName), pszValue, cchValue, fDuplicateValue, enmOrigin, fRecursive, pFileLoc); } /** * Implements appending and prepending to a kBuild object variable. * * The variable is either accessed thru an accessor or by the topmost kBuild * object. * * @returns Pointer to the defined variable on success. * * @param pchName The variable name, not cached. * @param cchName The variable name length. This will not be ~0U. * @param pszValue The variable value. Must be duplicated. * @param cchValue The value length. * @param fSimpleValue Whether we've already figured that it's a simple * value. This is for optimizing appending/prepending * to an existing simple value variable. * @param enmOrigin The variable origin. * @param fAppend Append if set, prepend if clear. * @param pFileLoc The location of the variable definition. */ struct variable * kbuild_object_variable_pre_append(const char *pchName, size_t cchName, const char *pchValue, size_t cchValue, int fSimpleValue, enum variable_origin enmOrigin, int fAppend, const struct floc *pFileLoc) { struct kbuild_object *pObj; struct variable VarKey; /* * Resolve the relevant kBuild object first. */ if (cchName > 3 && pchName[0] == '[') { const char *pchVarNm; size_t cchVarNm; pObj = parse_kbuild_object_variable_accessor(pchName, cchName, kBuildSeverity_Fatal, pFileLoc, &pchVarNm, &cchVarNm, NULL); if (pObj != KOBJ_NOT_KBUILD_ACCESSOR) { pchName = pchVarNm; cchName = cchVarNm; } else pObj = g_pTopKbEvalData->pObj; } else pObj = g_pTopKbEvalData->pObj; /* * Make sure the variable name is valid. Raise fatal error if not. */ if (!is_valid_kbuild_object_variable_name(pchName, cchName)) fatal(pFileLoc, _("Invalid kBuild object variable name: '%.*s'"), (int)cchName, pchName); /* * Get the cached name and look it up in the object's variables. */ VarKey.name = strcache2_lookup(&variable_strcache, pchName, cchName); if (VarKey.name) { struct variable *pVar; VarKey.length = cchName; pVar = (struct variable *)hash_find_item_strcached(&pObj->pVariables->set->table, &VarKey); if (pVar) { /* Append/prepend to existing variable. */ return do_variable_definition_append(pFileLoc, pVar, pchValue, cchValue, fSimpleValue, enmOrigin, fAppend); } /* * Not found. Check ancestors if the 'override' directive isn't applied. */ if (pObj->pszParent && enmOrigin != o_override) { struct kbuild_object *pParent = pObj; for (;;) { pParent = eval_kbuild_resolve_parent(pParent, 0 /*fQuiet*/); if (!pParent) break; pVar = (struct variable *)hash_find_item_strcached(&pParent->pVariables->set->table, &VarKey); if (pVar) { if (pVar->value_length != ~0U) assert(pVar->value_length == strlen(pVar->value)); else pVar->value_length = strlen(pVar->value); /* * Combine the two values and define the variable in the * specified child object. We must disregard 'origin' a * little here, so we must do the gritty stuff our selves. */ if ( pVar->recursive || fSimpleValue || !cchValue || memchr(pchValue, '$', cchValue) == NULL ) { size_t cchNewValue; char *pszNewValue; char *pszTmp; /* Just join up the two values. */ if (pVar->value_length == 0) { cchNewValue = cchValue; pszNewValue = xstrndup(pchValue, cchValue); } else if (!cchValue) { cchNewValue = pVar->value_length; pszNewValue = xmalloc(cchNewValue + 1); memcpy(pszNewValue, pVar->value, cchNewValue + 1); } else { cchNewValue = pVar->value_length + 1 + cchValue; pszNewValue = xmalloc(cchNewValue + 1); if (fAppend) { memcpy(pszNewValue, pVar->value, pVar->value_length); pszTmp = pszNewValue + pVar->value_length; *pszTmp++ = ' '; memcpy(pszTmp, pchValue, cchValue); pszTmp[cchValue] = '\0'; } else { memcpy(pszNewValue, pchValue, cchValue); pszTmp = pszNewValue + cchValue; *pszTmp++ = ' '; memcpy(pszNewValue, pVar->value, pVar->value_length); pszTmp[pVar->value_length] = '\0'; } } /* Define the new variable in the child. */ return define_kbuild_object_variable_cached(pObj, VarKey.name, pszNewValue, cchNewValue, 0 /*fDuplicateValue*/, enmOrigin, pVar->recursive, pFileLoc); } else { /* Lazy bird: Copy the variable from the ancestor and then do a normal append/prepend on it. */ pVar = define_kbuild_object_variable_cached(pObj, VarKey.name, pVar->value, pVar->value_length, 1 /*fDuplicateValue*/, enmOrigin, pVar->recursive, pFileLoc); append_expanded_string_to_variable(pVar, pchValue, cchValue, fAppend); return pVar; } } } } } else VarKey.name = strcache2_add(&variable_strcache, pchName, cchName); /* Variable not found. */ return define_kbuild_object_variable_cached(pObj, VarKey.name, pchValue, cchValue, 1 /*fDuplicateValue*/, enmOrigin, 1 /*fRecursive */, pFileLoc); } /** @} */ static const char * eval_kbuild_type_to_string(enum kBuildType enmType) { switch (enmType) { case kBuildType_Target: return "target"; case kBuildType_Template: return "template"; case kBuildType_Tool: return "tool"; case kBuildType_Sdk: return "sdk"; case kBuildType_Unit: return "unit"; default: case kBuildType_Invalid: return "invalid"; } } /** * Converts a string into an kBuild object type. * * @returns The type on success, kBuildType_Invalid on failure. * @param pchWord The pchWord. Not necessarily zero terminated. * @param cchWord The length of the word. */ static enum kBuildType eval_kbuild_type_from_string(const char *pchWord, size_t cchWord) { if (cchWord >= 3) { if (*pchWord == 't') { if (WORD_IS(pchWord, cchWord, "target")) return kBuildType_Target; if (WORD_IS(pchWord, cchWord, "template")) return kBuildType_Template; if (WORD_IS(pchWord, cchWord, "tool")) return kBuildType_Tool; } else { if (WORD_IS(pchWord, cchWord, "sdk")) return kBuildType_Sdk; if (WORD_IS(pchWord, cchWord, "unit")) return kBuildType_Unit; } } return kBuildType_Invalid; } static char * allocate_expanded_next_token(const char **ppszCursor, const char *pszEos, size_t *pcchToken, int fStrip) { unsigned int cchToken; char *pszToken = find_next_token_eos(ppszCursor, pszEos, &cchToken); if (pszToken) { pszToken = allocated_variable_expand_2(pszToken, cchToken, &cchToken); if (pszToken) { if (fStrip) { unsigned int off = 0; while (MY_IS_BLANK(pszToken[off])) off++; if (off) { cchToken -= off; memmove(pszToken, &pszToken[off], cchToken + 1); } while (cchToken > 0 && MY_IS_BLANK(pszToken[cchToken - 1])) pszToken[--cchToken] = '\0'; } assert(cchToken == strlen(pszToken)); if (pcchToken) *pcchToken = cchToken; return pszToken; } } if (pcchToken) *pcchToken = 0; return NULL; } static struct kbuild_object * eval_kbuild_resolve_parent(struct kbuild_object *pObj, int fQuiet) { if ( !pObj->pParent && pObj->pszParent) { struct kbuild_object *pCur = g_pHeadKbObjs; while (pCur) { if ( pCur->enmType == pObj->enmType && !strcmp(pCur->pszName, pObj->pszParent)) { if ( pCur->pszParent && ( pCur->pParent == pObj || !strcmp(pCur->pszParent, pObj->pszName)) ) fatal(&pObj->FileLoc, _("'%s' and '%s' are both trying to be each other children..."), pObj->pszName, pCur->pszName); pObj->pParent = pCur; pObj->pVariables->next = pObj->pVariables; return pCur; } pCur = pCur->pGlobalNext; } /* Not found. */ if (!fQuiet) error(&pObj->FileLoc, _("Could not locate parent '%s' of '%s'"), pObj->pszParent, pObj->pszName); } return pObj->pParent; } static int eval_kbuild_define_xxxx(struct kbuild_eval_data **ppData, const struct floc *pFileLoc, const char *pszLine, const char *pszEos, int fIgnoring, enum kBuildType enmType) { unsigned int cch; char ch; char *psz; const char *pszPrefix; struct kbuild_object *pObj; struct kbuild_eval_data *pData; if (fIgnoring) return 0; /* * Create a new kBuild object. */ pObj = xmalloc(sizeof(*pObj)); pObj->enmType = enmType; pObj->pszName = NULL; pObj->cchName = 0; pObj->FileLoc = *pFileLoc; pObj->pGlobalNext = g_pHeadKbObjs; g_pHeadKbObjs = pObj; pObj->pVariables = create_new_variable_set(); pObj->pszParent = NULL; pObj->cchParent = 0; pObj->pParent = NULL; pObj->pszTemplate = NULL; pObj->pszVarPrefix = NULL; pObj->cchVarPrefix = 0; /* * The first word is the name. */ pObj->pszName = allocate_expanded_next_token(&pszLine, pszEos, &pObj->cchName, 1 /*strip*/); if (!pObj->pszName || !*pObj->pszName) fatal(pFileLoc, _("The kBuild define requires a name")); psz = pObj->pszName; while ((ch = *psz++) != '\0') if (!isgraph(ch)) { error(pFileLoc, _("The 'kBuild-define-%s' name '%s' contains one or more invalid characters"), eval_kbuild_type_to_string(enmType), pObj->pszName); break; } /* * Calc the variable prefix. */ switch (enmType) { case kBuildType_Target: pszPrefix = ""; break; case kBuildType_Template: pszPrefix = "TEMPLATE_"; break; case kBuildType_Tool: pszPrefix = "TOOL_"; break; case kBuildType_Sdk: pszPrefix = "SDK_"; break; case kBuildType_Unit: pszPrefix = "UNIT_"; break; default: fatal(pFileLoc, _("enmType=%d"), enmType); return -1; } cch = strlen(pszPrefix); pObj->cchVarPrefix = cch + pObj->cchName; pObj->pszVarPrefix = xmalloc(pObj->cchVarPrefix + 1); memcpy(pObj->pszVarPrefix, pszPrefix, cch); memcpy(&pObj->pszVarPrefix[cch], pObj->pszName, pObj->cchName); /* * Parse subsequent words. */ psz = find_next_token_eos(&pszLine, pszEos, &cch); while (psz) { if (WORD_IS(psz, cch, "extending")) { /* Inheritance directive. */ if (pObj->pszParent != NULL) fatal(pFileLoc, _("'extending' can only occure once")); pObj->pszParent = allocate_expanded_next_token(&pszLine, pszEos, &pObj->cchParent, 1 /*strip*/); if (!pObj->pszParent || !*pObj->pszParent) fatal(pFileLoc, _("'extending' requires a parent name")); } else if (WORD_IS(psz, cch, "using")) { char *pszTemplate; size_t cchTemplate; /* Template directive. */ if (enmType != kBuildType_Target) fatal(pFileLoc, _("'using