Skip to content
Snippets Groups Projects
xpather.cc 134 KiB
Newer Older
Elemer Lelik's avatar
Elemer Lelik committed
/******************************************************************************
 * Copyright (c) 2000-2021 Ericsson Telecom AB
Elemer Lelik's avatar
Elemer Lelik committed
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
Elemer Lelik's avatar
Elemer Lelik committed
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
Elemer Lelik's avatar
Elemer Lelik committed
 *
 * Contributors:
 *   
 *   Baji, Laszlo
 *   Balasko, Jeno
 *   Baranyi, Botond
 *   Beres, Szabolcs
 *   Delic, Adam
 *   Kovacs, Ferenc
 *   Ormandi, Matyas
 *   Pandi, Krisztian
 *   Raduly, Csaba
 *   Szabados, Kristof
 *   Szabo, Bence Janos
 *   Pandi, Krisztian
 *
 ******************************************************************************/
#include "xpather.h"

#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string>

#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>

#define LIBXML_SCHEMAS_ENABLED
#include <libxml/xmlschemastypes.h>

#include "../common/memory.h"
#include "vector.hh"
// Do _NOT_ #include "string.hh", it drags in ustring.o, common/Quadruple.o,
// Int.o, ttcn3/PatternString.o, and then the entire AST :(
#include "map.hh"
Elemer Lelik's avatar
Elemer Lelik committed
#include "ProjectGenHelper.hh"
#include "../common/path.h"
Elemer Lelik's avatar
Elemer Lelik committed
#include "ttcn3/ttcn3_preparser.h"
#include "asn1/asn1_preparser.h"

// in makefile.c
void ERROR  (const char *fmt, ...);
void WARNING(const char *fmt, ...);
void NOTIFY (const char *fmt, ...);
void DEBUG  (const char *fmt, ...);

// for vector and map
void fatal_error(const char * filename, int lineno, const char * fmt, ...)
__attribute__ ((__format__ (__printf__, 3, 4), __noreturn__));

void fatal_error(const char * filename, int lineno, const char * fmt, ...)
{
  fputs(filename, stderr);
  fprintf(stderr, ":%d: ", lineno);
  va_list va;
  va_start(va, fmt);
  vfprintf(stderr, fmt, va);
  va_end(va);
  abort();
}

Kristof Szabados's avatar
Kristof Szabados committed
static ProjectGenHelper& projGenHelper = ProjectGenHelper::Instance();
Elemer Lelik's avatar
Elemer Lelik committed

/// Run an XPath query and return an xmlXPathObjectPtr, which must be freed
xmlXPathObjectPtr run_xpath(xmlXPathContextPtr xpathCtx, const char *xpathExpr)
{
  xmlXPathObjectPtr xpathObj = xmlXPathEvalExpression(
    (const xmlChar *)xpathExpr, xpathCtx);
  if(xpathObj == NULL) {
    fprintf(stderr,"Error: unable to evaluate xpath expression \"%s\"\n", xpathExpr);
    return 0;
  }

  return xpathObj;
}

// RAII classes

class XmlDoc {
public:
  explicit XmlDoc(xmlDocPtr p) : doc_(p) {}
  ~XmlDoc() {
    if (doc_ != NULL) xmlFreeDoc(doc_);
  }
  operator xmlDocPtr() const { return doc_; }
private:
  xmlDocPtr doc_;
};

class XPathContext {
public:
  explicit XPathContext(xmlXPathContextPtr c) : ctx_(c) {}
  ~XPathContext() {
    if (ctx_ != NULL) xmlXPathFreeContext(ctx_);
  }
  operator xmlXPathContextPtr() const { return ctx_; }
private:
  xmlXPathContextPtr ctx_;
};

class XPathObject {
public:
  explicit XPathObject(xmlXPathObjectPtr o) : xpo_(o) {}
  ~XPathObject() {
    if (xpo_ != NULL) xmlXPathFreeObject(xpo_);
  }
  operator   xmlXPathObjectPtr() const { return xpo_; }
  xmlXPathObjectPtr operator->() const { return xpo_; }
private:
  xmlXPathObjectPtr xpo_;
};

//------------------------------------------------------------------
/// compare-by-content wrapper of a plain C string
struct cstring {
  explicit cstring(const char *s) : str(s) {}
  void destroy() const;
  operator const char*() const { return str; }
protected:
  const char *str;
  friend boolean operator<(const cstring& l, const cstring& r);
  friend boolean operator==(const cstring& l, const cstring& r);
};

void cstring::destroy() const {
  Free(const_cast<char*>(str)); // assumes valid pointer or NULL
}

boolean operator<(const cstring& l, const cstring& r) {
  return strcmp(l.str, r.str) < 0;
}

boolean operator==(const cstring& l, const cstring& r) {
  return strcmp(l.str, r.str) == 0;
}

/// RAII for C string
struct autostring : public cstring {
  /// Constructor; takes over ownership
  explicit autostring(const char *s = 0) : cstring(s) {}
  ~autostring() {
    // He who can destroy a thing, controls that thing -- Paul Muad'Dib
    Free(const_cast<char*>(str)); // assumes valid pointer or NULL
  }
  /// %Assignment; takes over ownership
  const autostring& operator=(const char *s) {
    Free(const_cast<char*>(str)); // assumes valid pointer or NULL
    str = s;
    return *this;
  }
  /// Relinquish ownership
  const char *extract() {
    const char *retval = str;
    str = 0;
    return retval;
  }
private:
  autostring(const autostring&);
  autostring& operator=(const autostring&);
};


bool validate_tpd(const XmlDoc& xml_doc, const char* tpd_file_name, const char* xsd_file_name)
{
  xmlLineNumbersDefault(1);

  xmlSchemaParserCtxtPtr ctxt = xmlSchemaNewParserCtxt(xsd_file_name);
  if (ctxt==NULL) {
    ERROR("Unable to create xsd context for xsd file `%s'", xsd_file_name);
    return false;
  }
  xmlSchemaSetParserErrors(ctxt, (xmlSchemaValidityErrorFunc)fprintf, (xmlSchemaValidityWarningFunc)fprintf, stderr);

  xmlSchemaPtr schema = xmlSchemaParse(ctxt);
  if (schema==NULL) {
    ERROR("Unable to parse xsd file `%s'", xsd_file_name);
    xmlSchemaFreeParserCtxt(ctxt);
    return false;
  }

  xmlSchemaValidCtxtPtr xsd = xmlSchemaNewValidCtxt(schema);
  if (xsd==NULL) {
    ERROR("Schema validation error for xsd file `%s'", xsd_file_name);
    xmlSchemaFree(schema);
    xmlSchemaFreeParserCtxt(ctxt);
    return false;
  }
  xmlSchemaSetValidErrors(xsd, (xmlSchemaValidityErrorFunc) fprintf, (xmlSchemaValidityWarningFunc) fprintf, stderr);

  int ret = xmlSchemaValidateDoc(xsd, xml_doc);

  xmlSchemaFreeValidCtxt(xsd);
  xmlSchemaFree(schema);
  xmlSchemaFreeParserCtxt(ctxt);
  xmlSchemaCleanupTypes();

  if (ret==0) {
    return true; // successful validation
  } else if (ret>0) {
    ERROR("TPD file `%s' is invalid according to schema `%s'", tpd_file_name, xsd_file_name);
    return false;
  } else {
    ERROR("TPD validation of `%s' generated an internal error in libxml2", tpd_file_name);
    return false;
  }
}

/** Extract a boolean value from the XML, if it exists otherwise flag is unchanged
 *
 * @param xpathCtx XPath context object
 * @param actcfg name of the active configuration
 * @param option name of the value
 * @param flag pointer to the variable to receive the value
 */
void xsdbool2boolean(const XPathContext& xpathCtx, const char *actcfg,
  const char *option, boolean* flag)
{
  char *xpath = mprintf(
    "/TITAN_Project_File_Information/Configurations/Configuration[@name='%s']"
    "/ProjectProperties/MakefileSettings/%s[text()='true']",
    actcfg, option);
  XPathObject xpathObj(run_xpath(xpathCtx, xpath));
  Free(xpath);

  if (xpathObj->nodesetval && xpathObj->nodesetval->nodeNr > 0) {
    *flag = TRUE;
  }
}

Elemer Lelik's avatar
Elemer Lelik committed
extern "C" string_list* getExternalLibs(const char* projName)
{
  if (!projGenHelper.getZflag()) return NULL;
  ProjectDescriptor* proj = projGenHelper.getTargetOfProject(projName);
  if (!proj) return NULL;

  std::vector<const char*> externalLibs;
  projGenHelper.getExternalLibs(externalLibs);

  if (0 == externalLibs.size()) return NULL;

  struct string_list* head = (struct string_list*)Malloc(sizeof(struct string_list));
  struct string_list* last_elem = head;
  struct string_list* tail = head;

  for (size_t i = 0; i < externalLibs.size(); ++i) {
     tail = last_elem;
     last_elem->str = mcopystr(externalLibs[i]);
     last_elem->next = (struct string_list*)Malloc(sizeof(struct string_list));
     last_elem = last_elem->next;
  }
  Free(last_elem);
  tail->next = NULL;
  return head;
}

Elemer Lelik's avatar
Elemer Lelik committed
extern "C" string_list* getExternalLibPaths(const char* projName)
Elemer Lelik's avatar
Elemer Lelik committed
{
  if (!projGenHelper.getZflag()) return NULL;
  ProjectDescriptor* proj = projGenHelper.getTargetOfProject(projName);
  if (!proj) return NULL;

  std::vector<const char*> externalLibs;
Elemer Lelik's avatar
Elemer Lelik committed
  projGenHelper.getExternalLibSearchPaths(externalLibs);
Elemer Lelik's avatar
Elemer Lelik committed

  if (0 == externalLibs.size()) return NULL;

  struct string_list* head = (struct string_list*)Malloc(sizeof(struct string_list));
  struct string_list* last_elem = head;
  struct string_list* tail = head;

  for (size_t i = 0; i < externalLibs.size(); ++i) {
     tail = last_elem;
     last_elem->str = mcopystr(externalLibs[i]);
     last_elem->next = (struct string_list*)Malloc(sizeof(struct string_list));
     last_elem = last_elem->next;
  }
  Free(last_elem);
  tail->next = NULL;
  return head;
}

extern "C" string_list* getRefWorkingDirs(const char* projName)
{
  if (!projGenHelper.getZflag()) return NULL;
  ProjectDescriptor* proj = projGenHelper.getTargetOfProject(projName);
  if (!proj) FATAL_ERROR("Project \"%s\" was not found in the project list", projName);

  struct string_list* head = (struct string_list*)Malloc(sizeof(struct string_list));
  struct string_list* last_elem = head;
  struct string_list* tail = head;
  last_elem->str = NULL;
  last_elem->next = NULL;
  for (size_t i = 0; i < proj->numOfRefProjWorkingDirs(); ++i) {
    tail = last_elem;
    last_elem->str = mcopystr(proj->getRefProjWorkingDir(i).c_str());
    last_elem->next = (struct string_list*)Malloc(sizeof(struct string_list));
    last_elem = last_elem->next;
  }
  Free(last_elem);
  tail->next = NULL;
  return head;
}

extern "C" string2_list* getLinkerLibs(const char* projName)
{

  if (!projGenHelper.getZflag()) return NULL;
  if (1 == projGenHelper.numOfProjects() || 0 == projGenHelper.numOfLibs()){
    return NULL; //no library
  }
  ProjectDescriptor* projLib = projGenHelper.getTargetOfProject(projName);
  if (!projLib) FATAL_ERROR("Project \"%s\" was not found in the project list", projName);

  struct string2_list* head = (struct string2_list*)Malloc(sizeof(struct string2_list));
  struct string2_list* last_elem = head;
  struct string2_list* tail = head;
  last_elem->next = NULL;
  last_elem->str1 = NULL;
  last_elem->str2 = NULL;
  for (std::map<std::string, ProjectDescriptor>::const_iterator it = projGenHelper.getHead();
       it != projGenHelper.getEnd(); ++it) {
    if ((it->second).isLibrary()) {
      if (!(it->second).getLinkingStrategy() &&
          !projLib->hasLinkerLibTo((it->second).getProjectName())) { // static linked library
          continue;
      }
      std::string relPath = projLib->setRelativePathTo((it->second).getProjectAbsWorkingDir());
      if (relPath == std::string(".")) {
        continue; // the relpath shows to itself
      }
      tail = last_elem;
      last_elem->str1 = mcopystr(relPath.c_str());
      last_elem->str2 = mcopystr((it->second).getTargetExecName().c_str());
      last_elem->next = (struct string2_list*)Malloc(sizeof(struct string2_list));
      last_elem = last_elem->next;
    }
  }
  tail->next = NULL;
  Free(last_elem);

  if (head->str1 && head->str2)
    return head;
  else
    return NULL;
}

extern "C" const char* getLibFromProject(const char* projName)
{
  if (!projGenHelper.getZflag()) return NULL;
  ProjectDescriptor* lib = projGenHelper.getTargetOfProject(projName);
  if (lib) return lib->getTargetExecName().c_str();
  return NULL;
}

extern "C" void erase_libs(void) {
Elemer Lelik's avatar
Elemer Lelik committed
  projGenHelper.cleanUp();
}

extern "C" void print_libs(void) {
Elemer Lelik's avatar
Elemer Lelik committed
  projGenHelper.print();
}


extern "C" boolean hasSubProject(const char* projName) {
  if (!projGenHelper.getZflag()) return FALSE;
  if (projGenHelper.getHflag())
    return static_cast<boolean>(projGenHelper.hasReferencedProject());
  else if(std::string(projName) == projGenHelper.getToplevelProjectName())
    return static_cast<boolean>(projGenHelper.hasReferencedProject());
  else
    return FALSE;
}

extern "C" boolean hasExternalLibrary(const char* libName, const char* projName) {
  if (!projGenHelper.getZflag()) return FALSE;
  ProjectDescriptor* projLib = projGenHelper.getTargetOfProject(projName);
  if (projLib && projLib->hasLinkerLib(libName))
    return TRUE;
  else
    return FALSE;
}

extern "C" boolean isTopLevelExecutable(const char* projName) {
  if (!projGenHelper.getZflag()) return false;
  ProjectDescriptor* proj = projGenHelper.getTargetOfProject(projName);
  if (projGenHelper.getToplevelProjectName() != std::string(projName)) return FALSE;
  if (proj && proj->isLibrary())
    return FALSE;
  else
    return TRUE;
}

extern "C" boolean isDynamicLibrary(const char* key) {
  if (!projGenHelper.getZflag()) return false;
  ProjectDescriptor* proj = projGenHelper.getProjectDescriptor(key);
  if (proj) return proj->getLinkingStrategy();
  FATAL_ERROR("Library \"%s\" was not found", key);
  return false;
}

extern "C" const char* getTPDFileName(const char* projName) {
  if (!projGenHelper.getZflag()) return NULL;
  ProjectDescriptor* proj = projGenHelper.getTargetOfProject(projName);
  if (proj) return proj->getTPDFileName().c_str();
  FATAL_ERROR("TPD file name to project \"%s\" was not found", projName);
}

extern "C" const char* getPathToRootDir(const char* projName) {
  if (!projGenHelper.getZflag()) return NULL;
  ProjectDescriptor* proj = projGenHelper.getTargetOfProject(projName);
  const char* rootDir = projGenHelper.getRootDirOS(projName).c_str();
  if (proj && rootDir) {
    return rootDir;
  }
  FATAL_ERROR("Project \"%s\": no relative path was found to top directory at OS level.", projName);
}

extern "C" const char* findLibraryPath(const char* libraryName, const char* projName)
{
  if (!projGenHelper.getZflag()) return NULL;
  ProjectDescriptor* projLib = projGenHelper.getTargetOfProject(projName);
  if (!projLib) FATAL_ERROR("Project \"%s\" was not found in the project list", projName);
  ProjectDescriptor* libLib = projGenHelper.getProjectDescriptor(libraryName);
  if (!libLib) return NULL;
  std::string str = projLib->setRelativePathTo(libLib->getProjectAbsWorkingDir());
  size_t refIndex = projLib->getLibSearchPathIndex(libLib->getProjectName());
  if (refIndex > projLib->numOfLibSearchPaths()) return NULL;
  projLib->setLibSearchPath(refIndex, str);
  return projLib->getLibSearchPath(libLib->getProjectName());
}

extern "C" const char* findLibraryName(const char* libraryName, const char* projName)
{
  if (!projGenHelper.getZflag()) return NULL;
  ProjectDescriptor* projLib = projGenHelper.getTargetOfProject(projName);
  if (!projLib) FATAL_ERROR("Project \"%s\" was not found in the project list", projName);
  ProjectDescriptor* libLib = projGenHelper.getProjectDescriptor(libraryName);
  if (!libLib) return NULL;
  for (size_t i = 0; i < projLib->numOfReferencedProjects(); ++i) {
    const std:: string refProjName = projLib->getReferencedProject(i);
    ProjectDescriptor* refLib = projGenHelper.getTargetOfProject(refProjName.c_str());
    if (refLib->getTargetExecName() == std::string(libraryName))
      return libraryName;
  }
  return NULL;
}

extern "C" boolean isTtcn3ModuleInLibrary(const char* moduleName)
{
  if (!projGenHelper.getZflag()) return FALSE;
  return (boolean)projGenHelper.isTtcn3ModuleInLibrary(moduleName);
}

extern "C" boolean isAsn1ModuleInLibrary(const char* moduleName)
{
  if (!projGenHelper.getZflag()) return FALSE;
  return (boolean)projGenHelper.isAsn1ModuleInLibrary(moduleName);
}

extern "C" boolean isSourceFileInLibrary(const char* fileName)
{
  if (!projGenHelper.getZflag()) return FALSE;
  return (boolean)projGenHelper.isSourceFileInLibrary(fileName);
}

extern "C" boolean isHeaderFileInLibrary(const char* fileName)
{
  if (!projGenHelper.getZflag()) return FALSE;
  return (boolean)projGenHelper.isHeaderFileInLibrary(fileName);
}

extern "C" boolean isTtcnPPFileInLibrary(const char* fileName)
{
  if (!projGenHelper.getZflag()) return FALSE;
  return (boolean)projGenHelper.isTtcnPPFileInLibrary(fileName);
}

extern "C" boolean isXSDModuleInLibrary(const char* fileName)
{
  if (!projGenHelper.getZflag()) return FALSE;
  return (boolean)projGenHelper.isXSDModuleInLibrary(fileName);
}
Elemer Lelik's avatar
Elemer Lelik committed

extern "C" boolean buildObjects(const char* projName, boolean add_referenced)
{
  if (!projGenHelper.getZflag()) return FALSE;
  if (projGenHelper.getHflag()) return FALSE;
  if (add_referenced) return FALSE;
  ProjectDescriptor* desc =projGenHelper.getTargetOfProject(projName);
  if (desc && desc->isLibrary()) return FALSE;
  return TRUE;
}

void append_to_library_list (const char* prjName,
                             const XPathContext& xpathCtx,
                             const char *actcfg)
{
  if (!projGenHelper.getZflag()) return;

  char *exeXpath = mprintf(
    "/TITAN_Project_File_Information/Configurations/Configuration[@name='%s']"
    "/ProjectProperties/MakefileSettings/targetExecutable/text()",
    actcfg);
  XPathObject exeObj(run_xpath(xpathCtx, exeXpath));
  Free(exeXpath);
  if (exeObj->nodesetval && exeObj->nodesetval->nodeNr > 0) {
    const char* target_executable = (const char*)exeObj->nodesetval->nodeTab[0]->content;
    autostring target_exe_dir(get_dir_from_path(target_executable));
    autostring target_exe_file(get_file_from_path(target_executable));
    std::string lib_name;
Elemer Lelik's avatar
Elemer Lelik committed
    lib_name = target_exe_file;
    ProjectDescriptor* projDesc = projGenHelper.getTargetOfProject(prjName);
    if (projDesc) {
      projDesc->setTargetExecName(lib_name.c_str());
    }
  }
}

// data structures and functions to manage excluded folders/files

Kristof Szabados's avatar
Kristof Szabados committed
static map<cstring, const char> excluded_files;
Elemer Lelik's avatar
Elemer Lelik committed
boolean is_excluded_file(const cstring& path, const char* project) {
  if (!excluded_files.has_key(path)) return false;
  const char* proj = excluded_files[path];
  if (0 == strcmp(project, proj)) return true;
  return false;
Kristof Szabados's avatar
Kristof Szabados committed
static vector<const char> excluded_folders;

// Unfortunately, when "docs" is excluded, we need to drop
// files in "docs/", "docs/pdf/", "docs/txt/", "docs/txt/old/" etc;
// so it's not as simple as using a map :(

/** Checks whether a file is under an excluded folder
 *
 * @param path (relative) path of the file
 * @return true if file is excluded, false otherwise
 */
boolean is_excluded_folder(const char *path) {
  boolean answer = FALSE;
  size_t pathlen = strlen(path);

  for (size_t i = 0, end = excluded_folders.size(); i < end; ++i) {
    const char *xdir = excluded_folders[i];
    size_t xdlen = strlen(xdir);
    if (pathlen > xdlen && path[xdlen] == '/') {
      // we may have a winner
      if ((answer = !strncmp(path, xdir, xdlen))) break;
    }
  }
  return answer;
}

// How do you treat a raw info? You cook it, of course!
// Returns a newly allocated string.
char *cook(const char *raw, const map<cstring, const char>& path_vars)
{
  const char *slash = strchr(raw, '/');
  if (!slash) { // Pretend that the slash is at the end of the string.
    slash = raw + strlen(raw);
  }

  // Assume that a path variable reference is the first (or only) component
  // of the path: ROOT in "ROOT/etc/issue".
  autostring prefix(mcopystrn(raw, slash - raw));
  if (path_vars.has_key(prefix)) {
    char *cooked = mcopystr(path_vars[prefix]);
    bool ends_with_slash = cooked[strlen(cooked)-1] == '/';
    if (ends_with_slash && *slash == '/') {
      // Avoid paths with two slashes at the start; Cygwin thinks it's UNC
      ++slash;
    }
    // If there was no '/' (only the path variable reference e.g "ROOT")
    // then slash points to a null byte and the mputstr is a no-op.
    cooked = mputstr(cooked, slash);
    return cooked;
  }

  // If the path variable could not be substituted,
  // return (a copy of) the original.
  return mcopystr(raw);
}

void replacechar(char** content) {

  std::string s= *content;
  size_t found = 0;
  while ((found = s.find('['))!= std::string::npos){
    s.replace(found,1, "${");
  }
  while ((found = s.find(']')) != std::string::npos){
    s.replace(found,1, "}");
  }
  *content = mcopystr(s.c_str());
}

/** Determines the suffix (i.e. the character sequence following the last dot)
 * of file or path name \a file_name. NULL pointer is returned if \a file_name
 * does not contain any dot character or the last character of it is a dot.
 * The suffix is not copied, the returned pointer points to the tail of
 * \a file_name. */
const char *get_suffix(const char *file_name)
{
  size_t last_dot = (size_t)-1;
  size_t i;
  for (i = 0; file_name[i] != '\0'; i++)
    if (file_name[i] == '.') last_dot = i;
  if (last_dot == (size_t)-1 || file_name[last_dot + 1] == '\0') return NULL;
  else return file_name + last_dot + 1;
}

int is_xsd_module(const char *file_name, char **module_name) {
  const char * extension = get_suffix(file_name);
  if (extension == NULL) {
    return 0;
  }
  if (strcmp(extension, "xsd") != 0) {
    return 0;
  }
  if (module_name != NULL) *module_name = NULL;
  FILE *fp;
  char line[1024];
  char *command = NULL;
  char *ttcn3_dir = getenv("TTCN3_DIR");
  command = mputprintf(command, "%s%sxsd2ttcn -q -n %s",
    ttcn3_dir != NULL ? ttcn3_dir : "",
    ttcn3_dir != NULL ? "/bin/" : "",
    file_name);
  fp = popen(command, "r");
  if (fp == NULL) {
    ERROR("Could not get the module names of the XSD modules");
    return 0;
  }
  while (fgets(line, sizeof(line)-1, fp) != NULL) {
    *module_name = mputstr(*module_name, line);
  }
  int rv= pclose(fp);
  if (rv > 0) {
    return 0;
  }
  return *module_name != NULL;
}

static void clear_seen_tpd_files(map<cstring, int>& seen_tpd_files) {
  for (size_t i = 0, num = seen_tpd_files.size(); i < num; ++i) {
    const cstring& key = seen_tpd_files.get_nth_key(i);
    int *elem = seen_tpd_files.get_nth_elem(i);
    key.destroy();
    delete elem;
  }
  seen_tpd_files.clear();
}

Elemer Lelik's avatar
Elemer Lelik committed
const char* get_act_config(struct string2_list* cfg, const char* project_name) {
  while (cfg && cfg->str1 && project_name) {
    if (!strcmp(cfg->str1, project_name)) return cfg->str2;
    cfg = cfg->next;
  }
  return NULL;
}

void free_string_list(struct string_list* act_elem)
{
  while (act_elem) {
    struct string_list* next_elem = act_elem->next;
    Free(act_elem->str);
    Free(act_elem);
    act_elem = next_elem;
  }
}

void free_string2_list(struct string2_list* act_elem)
{
  while (act_elem) {
    struct string2_list* next_elem = act_elem->next;
    Free(act_elem->str1);
    Free(act_elem->str2);
    Free(act_elem);
    act_elem = next_elem;
  }
}

void free_config_list(struct config_list* act_elem) {
  while (act_elem) {
    struct config_list* next_elem = act_elem->next;
    Free(act_elem->str1);
    Free(act_elem->str2);
    Free(act_elem);
    act_elem = next_elem;
  }
}

void free_config_struct(struct config_struct* act_elem) {
  while (act_elem) {
    struct config_struct* next_elem = act_elem->next;
    Free(act_elem->project_name);
    Free(act_elem->project_conf);
    free_string_list(act_elem->dependencies);
    free_string2_list(act_elem->requirements);
    free_string_list(act_elem->children);
    Free(act_elem);
    act_elem = next_elem;
  }
}

// Initialize a config_struct to NULL-s
static void config_struct_init(struct config_struct* const list) {
  list->project_name = list->project_conf = NULL;
  list->is_active = FALSE;
  list->dependencies = (struct string_list*)Malloc(sizeof(struct string_list));
  list->dependencies->str = NULL;
  list->dependencies->next = NULL;
  list->requirements = (struct string2_list*)Malloc(sizeof(struct string2_list));
  list->requirements->str1 = NULL;
  list->requirements->str2 = NULL;
  list->requirements->next = NULL;
  list->children = (struct string_list*)Malloc(sizeof(struct string_list));
  list->children->str = NULL;
  list->children->next = NULL;
  list->processed = FALSE;
  list->next = NULL;
}

// This function fills up the dependencies field of the config_structs
// using the children fields.
static void config_struct_fillup_deps(struct config_struct* const list) {
  struct config_struct* last = list;
  while (last && last->project_name != NULL) {  // Go through the projects
    struct config_struct* lastest = list;
    while (lastest && lastest->project_name != NULL) { // Go through the projects again n^2 complexity
      struct string_list* lastest_child = lastest->children;
      while (lastest_child && lastest_child->str != NULL) {
        if (strcmp(last->project_name, lastest_child->str) == 0) { // If a project is a child of another project
          // Add the other project to the project's dependencies
          // But first check if it is already in the dependencies
          boolean already_in = FALSE;
          struct string_list* last_dep = last->dependencies;
          while (last_dep && last_dep->str != NULL) {
            if (strcmp(last_dep->str, lastest->project_name) == 0) {
              already_in = TRUE;
              break;
            }
            last_dep = last_dep->next;
          }
          if (already_in == FALSE) {
            last_dep->str = mcopystr(lastest->project_name);
            struct string_list* last_dep_next = (struct string_list*)Malloc(sizeof(string_list));
            last_dep_next->str = NULL;
            last_dep_next->next = NULL;
            last_dep->next = last_dep_next;
            break;
          }
        }
        lastest_child = lastest_child->next;
      }
      lastest = lastest->next;
    }
    last = last->next;
  }
}

// This function inserts project_name project's project_config configuration
// into the required configurations. This function can detect errors.
// If an error is detected FALSE is returned, otherwise TRUE.
static boolean insert_to_required_config(const struct config_struct* all_configs, const char* project_name, const char* project_config, struct string2_list* const required_configs) {

  boolean found_project = FALSE;
  boolean found_config = FALSE;
  boolean result = TRUE;
  
  //Check that it is a valid configuration for a valid project
  const struct config_struct* last = all_configs;
  while(last && last->project_name != NULL && last->project_conf != NULL) {
    if (strcmp(last->project_name, project_name) == 0) {
      found_project = TRUE;
      if (strcmp(last->project_conf, project_config) == 0) {
        found_config = TRUE;
        break;
      }
    }
    last = last->next;
  }
  // If the project or the configuration is not found
  if (found_project == FALSE || found_config == FALSE) {
    result = FALSE;
  }
  
  // Check if the project is already in the required configurations
  // or if the project is present with a different configuration
  boolean already_in = FALSE;
  struct string2_list* last_required_config = required_configs;
  while (last_required_config && last_required_config->str1 != NULL && last_required_config->str2 != NULL) {
    // If we have a record of this project
    if (strcmp(last_required_config->str1, project_name) == 0) {
      if (strcmp(last_required_config->str2, project_config) == 0) {
        // This project configuration is already in the required_configs
        already_in = TRUE;
      } else {
        // This project configuration is different than it is in the required_configs
        result = FALSE;
      }
    }
    last_required_config = last_required_config->next;
  }
  // If the project's configuration is not already present in the required
  // configs then we insert it. We insert it even when the result is FALSE.
  if (last_required_config && already_in == FALSE) {
    // Make copies of strings
    last_required_config->str1 = mcopystr(project_name);
    last_required_config->str2 = mcopystr(project_config);
    // Init next required configuration
    struct string2_list* last_required_config_next = (struct string2_list*)Malloc(sizeof(struct string2_list));
    last_required_config_next->str1 = NULL;
    last_required_config_next->str2 = NULL;
    last_required_config_next->next = NULL;
    last_required_config->next = last_required_config_next;
  }
  return result;
}

// Inserts project_name project with project_config configuration to the tmp_configs
// Return TRUE if the configuration is inserted
// Returns FALSE if the configuration is not inserted
// Returns 2 if the configuration is already in the tmp_configs (still inserted)
static boolean insert_to_tmp_config(struct config_list* const tmp_configs, const char* project_name, const char* project_config, const boolean is_active) {
  //First we check that it is a valid configuration for a valid project
  boolean found_project = FALSE;
  boolean found_config = FALSE;
  boolean active = FALSE;
  boolean result = TRUE;
  
  //Check if we have the same project with same configuration in the tmp_configs
  struct config_list* last = tmp_configs;
  while(last && last->str1 != NULL && last->str2 != NULL) {
    if (strcmp(last->str1, project_name) == 0) {
      found_project = TRUE;
      active = last->is_active;
      if (strcmp(last->str2, project_config) == 0) {
        found_config = TRUE;
        break;
      }
    }
    last = last->next;
  }
  
  //The case of same project with same configuration
  if (found_project == TRUE && found_config == TRUE) {
    result = 2;
    
  // The case of same project different configuration and the configuration
  // is not default
  } else if(found_project == TRUE && active == FALSE) {
    return FALSE;
  }
  // Go to the end of list
  while (last->next) {
    last = last->next;
  }
  // Insert new config into the tmp_configs
  last->str1 = mcopystr(project_name); 
  last->str2 = mcopystr(project_config);
  last->is_active = is_active;
  last->next = (struct config_list*)Malloc(sizeof(config_list));
  last->next->str1 = NULL;
  last->next->str2 = NULL;
  last->next->is_active = FALSE;
  last->next->next = NULL;

  return result;
}

// Removes the last element from the tmp_configs
static void remove_from_tmp_config(struct config_list* const tmp_configs, const char* /*project_name*/, const char* /*project_config*/) {
  struct config_list* last = tmp_configs;
  while (last->next && last->next->next != NULL) {
    last = last->next;
  }

  Free(last->str1);
  Free(last->str2);
  last->str1 = NULL;
  last->str2 = NULL;
  last->is_active = FALSE;
  Free(last->next);
  last->next = NULL;
}

// This function detects a circle originating from start_project.
// act_project is the next project which might be in the circle
// needed_in is a project that is needed to be inside the circle
// list is a temporary list which contains the elements of circle
static boolean is_circular_dep(const struct config_struct* all_configs, const char* start_project, const char* act_project, const char* needed_in, struct string_list** list) {
  if (*list == NULL) {
    *list = (struct string_list*)Malloc(sizeof(string_list));
    (*list)->str = mcopystr(start_project);
    (*list)->next = (struct string_list*)Malloc(sizeof(string_list));
    (*list)->next->str = NULL;
    (*list)->next->next = NULL;
  }
  //Circle detection
  struct string_list* last_list = *list;
  while (last_list && last_list->str != NULL) {
    struct string_list* last_list2 = last_list;
    while (last_list2 && last_list2->str != NULL) {
      // If the pointers are different but the project name is the same
      if (last_list != last_list2 && strcmp(last_list->str, last_list2->str) == 0) {
        // We look for the circle which starts from the start_project
        if (strcmp(last_list->str, start_project) != 0) {
          return FALSE;
        } else {
          // Check if needed_in is inside the circle
          while (last_list != last_list2) {
            if (needed_in != NULL && strcmp(last_list->str, needed_in) == 0) {
              return TRUE;
            }
            last_list = last_list->next;
          }
          return FALSE;
        }
      }
      last_list2 = last_list2->next;
    }
    last_list = last_list->next;
  }
  // Insert next element and call recursively for all the referenced projects
  const struct config_struct* last = all_configs;
  while (last && last->project_name) {
    //Find an act_project configuration
    if (strcmp(last->project_name, act_project) == 0) {
      // Go through its children
      struct string_list* children = last->children;
      while (children && children->str != NULL) {
        // Insert child into list
        last_list->str = mcopystr(children->str);
        last_list->next = (struct string_list*)Malloc(sizeof(string_list));
        last_list->next->str = NULL;
        last_list->next->next = NULL;
        // Call recursively
        if (is_circular_dep(all_configs, start_project, children->str, needed_in, list) == TRUE) {
          return TRUE;
        }
        // Remove child
        last_list = *list;
        while(last_list && last_list->next != NULL && last_list->next->next != NULL) {
          last_list = last_list->next;
        }
        Free(last_list->str);
        Free(last_list->next);
        last_list->str = NULL;
        last_list->next = NULL;
        children = children->next;
      }
    }
    last = last->next;
  }
  return FALSE;
}

// check if project_name project does not exists todo
// Project config == NULL means that we need to analyse the children of the project, todo why
// This function analyses project_name project to get the required configuration
// of this project.
// project_config may be NULL. It is not null when we are not certain of the
// project_name project's configuration.
// tmp_configs acts like a stack. It contains the history calls of anal_child. It
// is used to detect circles therefore prevents infinite recursions.
static boolean analyse_child(struct config_struct* const all_configs, const char* project_name, const char* project_config, struct string2_list* const required_configs, struct config_list* const tmp_configs) {
  boolean result = TRUE;
  const char* act_config = get_act_config(required_configs, project_name);
  // If the required configuration is known of project_name project
  if (act_config != NULL) {
    struct config_struct* tmp = all_configs;
    // Get the project_name's act_config config_struct (tmp holds it)
    while (tmp && tmp->project_name != NULL && tmp->project_conf != NULL) {
      if (strcmp(tmp->project_name, project_name) == 0 && strcmp(tmp->project_conf, act_config) == 0) {
        break;
      }
      tmp = tmp->next;
    }
    if (tmp->processed == TRUE) {
      // We already processed this project there is nothing to be done
      return result;
    }
    // Set the project to processed
    tmp->processed = TRUE;