Skip to content
Snippets Groups Projects
makefile.c 221 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
 *   Forstner, Matyas
 *   Koppany, Csaba
 *   Kovacs, Ferenc
 *   Kremer, Peter
 *   Lovassy, Arpad
 *   Pandi, Krisztian
 *   Raduly, Csaba
 *   Szabados, Kristof
 *   Szabo, Bence Janos
 *   Szabo, Janos Zoltan – initial implementation
 *   Szalay, Akos
 *   Zalanyi, Balazs Andor
 *   Pandi, Krisztian
 *
 ******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#if defined SOLARIS || defined SOLARIS8
Elemer Lelik's avatar
Elemer Lelik committed
#include <sys/utsname.h>
#endif

#include "../common/memory.h"
#include "../common/path.h"
#include "../common/version_internal.h"
#include "../common/userinfo.h"
#include "ttcn3/ttcn3_preparser.h"
#include "asn1/asn1_preparser.h"

#ifdef LICENSE
#include "../common/license.h"
#endif

#include "xpather.h"

static const char *program_name = NULL;
static unsigned int error_count = 0;
static boolean suppress_warnings = FALSE;
Elemer Lelik's avatar
Elemer Lelik committed
void free_string2_list(struct string2_list* act_elem);
void free_string_list(struct string_list* act_elem);
void ERROR(const char *fmt, ...)
{
  va_list parameters;
  fprintf(stderr, "%s: error: ", program_name);
  va_start(parameters, fmt);
  vfprintf(stderr, fmt, parameters);
  va_end(parameters);
  fprintf(stderr, "\n");
  fflush(stderr);
  error_count++;
}

void WARNING(const char *fmt, ...)
{
  va_list parameters;
  if (suppress_warnings) return;
  fprintf(stderr, "%s: warning: ", program_name);
  va_start(parameters, fmt);
  vfprintf(stderr, fmt, parameters);
  va_end(parameters);
  putc('\n', stderr);
  fflush(stderr);
}

void NOTIFY(const char *fmt, ...)
{
  va_list parameters;
  va_start(parameters, fmt);
  vfprintf(stderr, fmt, parameters);
  va_end(parameters);
  putc('\n', stderr);
  fflush(stderr);
}

void DEBUG(unsigned level, const char *fmt, ...)
{
  va_list parameters;
  fprintf(stderr, "%*s", 2 * level, "");
  va_start(parameters, fmt);
  vfprintf(stderr, fmt, parameters);
  va_end(parameters);
  putc('\n', stderr);
  fflush(stderr);
}

void path_error(const char *fmt, ...)
{
  va_list ap;
  char *err_msg;
  va_start(ap, fmt);
  err_msg = mprintf_va_list(fmt, ap);
  va_end(ap);
  ERROR("%s", err_msg);
  Free(err_msg);
}


#if defined SOLARIS || defined SOLARIS8
/** Automatic detection of Solaris version based on uname() system call.
 * Distinguishing is needed because some socket functions use socklen_t
 * (which is an alias for unsigned int) as length arguments on Solaris 8.
 * On Solaris 2.6 the argument type is simply int and no socklen_t or other
 * alias exists.
 * Note: It was discovered later that Solaris 7 (which is used rarely within
 * Ericsson) already uses socklen_t thus the SOLARIS8 platform identifier is a
 * bit misleading. */
static const char *get_platform_string(void)
{
    struct utsname name;
    int major, minor;
    if (uname(&name) < 0) {
	WARNING("System call uname() failed: %s", strerror(errno));
	errno = 0;
	return "SOLARIS";
    }
    if (sscanf(name.release, "%d.%d", &major, &minor) == 2 && major == 5) {
	if (minor <= 6) return "SOLARIS";
	else return "SOLARIS8";
    } else {
	ERROR("Invalid OS release: %s", name.release);
	return "SOLARIS";
    }
}
#elif defined LINUX
#define get_platform_string() "LINUX"
#elif defined FREEBSD
#define get_platform_string() "FREEBSD"
#elif defined WIN32
#define get_platform_string() "WIN32"
#elif defined INTERIX
#define get_platform_string() "INTERIX"
#else
#error Platform was not set.
#endif

/** structure for describing TTCN-3 and ASN.1 modules */
struct module_struct {
  char *dir_name; /* directory of the TTCN-3 or ASN.1 file, it is NULL if the
		     file is in the current working directory */
  char *file_name; /* name of the TTCN-3 or ASN.1 file */
  char *module_name; /* name of the TTCN-3 or ASN.1 module */
  boolean is_regular; /* indicates whether the name of the source file follows
			 the default naming convention */
};

/** structure for describing test ports and other C/C++ modules */
struct user_struct {
  char *dir_name; /* directory of the C/C++ source files, it is NULL if the
		     files are in the current working directory */
  char *file_prefix; /* the common prefix of the header and source file */
  char *header_name; /* name of the C/C++ header file, which has .hh or .h or .hpp
			suffix, it is NULL if there is no header file */
  char *source_name; /* name of the C/C++ source file, which has .cc or .c or .cpp
			suffix, it is NULL if there is no source file */
  boolean has_hh_suffix; /* indicates whether the header file is present and
			    has .hh or .hpp suffix */
  boolean has_cc_suffix; /* indicates whether the source file is present and
			    has .cc or .cpp suffix */
};

/** structure for directories that pre-compiled files are taken from */
struct base_dir_struct {
  const char *dir_name; /* name of the directory */
  boolean has_modules; /* indicates whether there are TTCN-3/ASN.1 modules in
			  the directory (it is set to FALSE if dir_name
			  contains user C/C++ files only */
};

/** data structure that describes the information needed for the Makefile */
struct makefile_struct {
Elemer Lelik's avatar
Elemer Lelik committed
  char *project_name;
  size_t nTTCN3Modules;
  struct module_struct *TTCN3Modules;

  boolean preprocess;
  size_t nTTCN3PPModules;
  struct module_struct *TTCN3PPModules;

  boolean TTCN3ModulesRegular;
  boolean BaseTTCN3ModulesRegular;
  size_t nTTCN3IncludeFiles;
  char **TTCN3IncludeFiles;

  size_t nASN1Modules;
  struct module_struct *ASN1Modules;
  boolean ASN1ModulesRegular;
  boolean BaseASN1ModulesRegular;
  
  size_t nXSDModules;
  struct module_struct *XSDModules;
  // No XSDModulesRegular and BaseXSDModulesRegular: it would be always false

  size_t nUserFiles;
  struct user_struct *UserFiles;
  boolean UserHeadersRegular;
  boolean UserSourcesRegular;
  boolean BaseUserHeadersRegular;
  boolean BaseUserSourcesRegular;

  size_t nOtherFiles;
  char **OtherFiles;

  boolean central_storage;
  size_t nBaseDirs;
  struct base_dir_struct *BaseDirs;
  char *working_dir;
  boolean gnu_make;
  boolean single_mode;
  char *output_file;
  char *ets_name;
  boolean force_overwrite;
  boolean use_runtime_2;
  boolean dynamic;
  boolean gcc_dep;
  char *code_splitting_mode;
  boolean coverage;
  char *tcov_file_name;
Elemer Lelik's avatar
Elemer Lelik committed
  struct string_list* profiled_file_list; /* not owned */
  boolean library;
Elemer Lelik's avatar
Elemer Lelik committed
  boolean linkingStrategy;
  boolean hierarchical;
  struct string_list* sub_project_dirs; /* not owned */
  struct string_list* ttcn3_prep_includes; /* not owned */
  struct string_list* ttcn3_prep_defines; /* not owned */
Elemer Lelik's avatar
Elemer Lelik committed
  struct string_list* ttcn3_prep_undefines; /* not owned */
  struct string_list* prep_includes; /* not owned */
  struct string_list* prep_defines; /* not owned */
Elemer Lelik's avatar
Elemer Lelik committed
  struct string_list* prep_undefines; /* not owned */
  boolean quietly;
  boolean disablesubtypecheck;
  const char *cxxcompiler;
  const char *optlevel;
  const char *optflags;
  boolean semanticcheckonly;
  boolean disableattibutevalidation;
  boolean disableber;
  boolean disableraw;
  boolean disabletext;
  boolean disablexer;
  boolean disablejson;
  boolean forcexerinasn;
  boolean defaultasomit;
  boolean gccmsgformat;
  boolean linenumbersonlymsg;
  boolean includesourceinfo;
  boolean addsourcelineinfo;
  boolean suppresswarnings;
  boolean outparamboundness;
Elemer Lelik's avatar
Elemer Lelik committed
  boolean omit_in_value_list;
Elemer Lelik's avatar
Elemer Lelik committed
  boolean warnings_for_bad_variants;
  boolean activate_debugger;
  boolean ignore_untagged_on_top_union;
Elemer Lelik's avatar
Elemer Lelik committed
  boolean disable_predef_ext_folder;
  boolean enable_legacy_encoding;
  boolean disable_userinfo;
  struct string_list* solspeclibraries; /* not owned */
  struct string_list* sol8speclibraries; /* not owned */
  struct string_list* linuxspeclibraries; /* not owned */
  struct string_list* freebsdspeclibraries; /* not owned */
  struct string_list* win32speclibraries; /* not owned */
  const char *ttcn3preprocessor;
  struct string_list* linkerlibraries; /* not owned */
  struct string_list* additionalObjects; /* not owned */
  struct string_list* linkerlibsearchpath; /* not owned */
  char* generatorCommandOutput; /* not owned */
  struct string2_list* target_placement_list; /* not owned */
};

/** Initializes structure \a makefile with empty lists and default settings. */
static void init_makefile_struct(struct makefile_struct *makefile)
{
Elemer Lelik's avatar
Elemer Lelik committed
  makefile->project_name = NULL;
  makefile->nTTCN3Modules = 0;
  makefile->TTCN3Modules = NULL;
  makefile->preprocess = FALSE;
  makefile->nTTCN3PPModules = 0;
  makefile->TTCN3PPModules = NULL;
  makefile->TTCN3ModulesRegular = TRUE;
  makefile->BaseTTCN3ModulesRegular = TRUE;
  makefile->nTTCN3IncludeFiles = 0;
  makefile->TTCN3IncludeFiles = NULL;
  makefile->nASN1Modules = 0;
  makefile->ASN1Modules = NULL;
  makefile->ASN1ModulesRegular = TRUE;
  makefile->BaseASN1ModulesRegular = TRUE;
  makefile->nXSDModules = 0;
  makefile->XSDModules = NULL;
  makefile->nUserFiles = 0;
  makefile->UserFiles = NULL;
  makefile->UserHeadersRegular = TRUE;
  makefile->UserSourcesRegular = TRUE;
  makefile->BaseUserHeadersRegular = TRUE;
  makefile->BaseUserSourcesRegular = TRUE;
  makefile->nOtherFiles = 0;
  makefile->OtherFiles = NULL;
  makefile->central_storage = FALSE;
  makefile->nBaseDirs = 0;
  makefile->BaseDirs = NULL;
  makefile->working_dir = get_working_dir();
  makefile->gnu_make = FALSE;
  makefile->single_mode = FALSE;
  makefile->ets_name = NULL;
  makefile->output_file = NULL;
  makefile->force_overwrite = FALSE;
  makefile->use_runtime_2 = FALSE;
  makefile->dynamic = FALSE;
  makefile->gcc_dep = FALSE;
  makefile->code_splitting_mode = NULL;
  makefile->coverage = FALSE;
  makefile->tcov_file_name = NULL;
Elemer Lelik's avatar
Elemer Lelik committed
  makefile->profiled_file_list = NULL;
  makefile->library = FALSE;
Elemer Lelik's avatar
Elemer Lelik committed
  makefile->linkingStrategy = FALSE;
  makefile->hierarchical = FALSE;
  makefile->sub_project_dirs = NULL;
  makefile->ttcn3_prep_includes = NULL;
  makefile->prep_includes = NULL;
  makefile->prep_defines = NULL;
  makefile->outparamboundness = FALSE;
Elemer Lelik's avatar
Elemer Lelik committed
  makefile->omit_in_value_list = FALSE;
Elemer Lelik's avatar
Elemer Lelik committed
  makefile->warnings_for_bad_variants = FALSE;
  makefile->activate_debugger = FALSE;
  makefile->ignore_untagged_on_top_union = FALSE;
  makefile->disable_predef_ext_folder = FALSE;
  makefile->enable_legacy_encoding = FALSE;
  makefile->disable_userinfo = FALSE;
  makefile->oop_features = FALSE;
  makefile->solspeclibraries = NULL;
  makefile->sol8speclibraries = NULL;
  makefile->linuxspeclibraries = NULL;
  makefile->freebsdspeclibraries = NULL;
  makefile->win32speclibraries = NULL;
  makefile->linkerlibraries = NULL;
  makefile->additionalObjects = NULL;
  makefile->linkerlibsearchpath = NULL;
  makefile->generatorCommandOutput = NULL;
  makefile->target_placement_list = NULL;
}

/** Deallocates all memory associated with structure \a makefile. */
static void free_makefile_struct(const struct makefile_struct *makefile)
{
Elemer Lelik's avatar
Elemer Lelik committed
  Free(makefile->project_name);
  size_t i;
  for (i = 0; i < makefile->nTTCN3Modules; i++) {
    Free(makefile->TTCN3Modules[i].dir_name);
    Free(makefile->TTCN3Modules[i].file_name);
    Free(makefile->TTCN3Modules[i].module_name);
  }
  Free(makefile->TTCN3Modules);
  for (i = 0; i < makefile->nTTCN3PPModules; i++) {
    Free(makefile->TTCN3PPModules[i].dir_name);
    Free(makefile->TTCN3PPModules[i].file_name);
    Free(makefile->TTCN3PPModules[i].module_name);
  }
  Free(makefile->TTCN3PPModules);
  for (i = 0; i < makefile->nTTCN3IncludeFiles; i++)
    Free(makefile->TTCN3IncludeFiles[i]);
  Free(makefile->TTCN3IncludeFiles);
  for (i = 0; i < makefile->nASN1Modules; i++) {
    Free(makefile->ASN1Modules[i].dir_name);
    Free(makefile->ASN1Modules[i].file_name);
    Free(makefile->ASN1Modules[i].module_name);
  }
  Free(makefile->ASN1Modules);
  for (i = 0; i < makefile->nXSDModules; i++) {
    Free(makefile->XSDModules[i].dir_name);
    Free(makefile->XSDModules[i].file_name);
    Free(makefile->XSDModules[i].module_name);
  }
  Free(makefile->XSDModules);
  for (i = 0; i < makefile->nUserFiles; i++) {
    Free(makefile->UserFiles[i].dir_name);
    Free(makefile->UserFiles[i].file_prefix);
    Free(makefile->UserFiles[i].header_name);
    Free(makefile->UserFiles[i].source_name);
  }
  Free(makefile->UserFiles);
  for (i = 0; i < makefile->nOtherFiles; i++) Free(makefile->OtherFiles[i]);
  Free(makefile->OtherFiles);
  Free(makefile->BaseDirs);
  Free(makefile->working_dir);
  Free(makefile->ets_name);
  Free(makefile->output_file);
  Free(makefile->code_splitting_mode);
  Free(makefile->tcov_file_name);
}

/** Displays the contents of structure \a makefile as debug messages. */
static void dump_makefile_struct(const struct makefile_struct *makefile,
  unsigned level)
{
  size_t i;
  DEBUG(level, "Data used for Makefile generation:");
Elemer Lelik's avatar
Elemer Lelik committed
  DEBUG(level + 1, "TTCN-3 project name: %s", makefile->project_name);
  DEBUG(level + 1, "TTCN-3 modules: (%u pcs.)", makefile->nTTCN3Modules);
  for (i = 0; i < makefile->nTTCN3Modules; i++) {
    const struct module_struct *module = makefile->TTCN3Modules + i;
    DEBUG(level + 2, "Module name: %s", module->module_name);
    if (module->dir_name != NULL)
      DEBUG(level + 3, "Directory: %s", module->dir_name);
    DEBUG(level + 3, "File name: %s", module->file_name);
    DEBUG(level + 3, "Follows the naming convention: %s",
      module->is_regular ? "yes" : "no");
  }
  DEBUG(level + 1, "TTCN-3 preprocessing: %s",
        makefile->preprocess ? "yes" : "no");
  if (makefile->preprocess) {
    DEBUG(level + 1, "TTCN-3 modules to be preprocessed: (%u pcs.)",
          makefile->nTTCN3PPModules);
    for (i = 0; i < makefile->nTTCN3PPModules; i++) {
      const struct module_struct *module = makefile->TTCN3PPModules + i;
      DEBUG(level + 2, "Module name: %s", module->module_name);
      if (module->dir_name != NULL)
        DEBUG(level + 3, "Directory: %s", module->dir_name);
      DEBUG(level + 3, "File name: %s", module->file_name);
      DEBUG(level + 3, "Follows the naming convention: %s",
        module->is_regular ? "yes" : "no");
    }
    DEBUG(level + 1, "TTCN-3 include files: (%u pcs.)",
      makefile->nTTCN3IncludeFiles);
    for (i = 0; i < makefile->nTTCN3IncludeFiles; i++)
      DEBUG(level + 2, "File name: %s", makefile->TTCN3IncludeFiles[i]);
  }
  DEBUG(level + 1, "All local TTCN-3 modules follow the naming convention: %s",
    makefile->TTCN3ModulesRegular ? "yes" : "no");
  if (makefile->central_storage) DEBUG(level + 1, "All TTCN-3 modules from other "
    "directories follow the naming convention: %s",
    makefile->BaseTTCN3ModulesRegular ? "yes" : "no");
  DEBUG(level + 1, "ASN.1 modules: (%u pcs.)", makefile->nASN1Modules);
  for (i = 0; i < makefile->nASN1Modules; i++) {
    const struct module_struct *module = makefile->ASN1Modules + i;
    DEBUG(level + 2, "Module name: %s", module->module_name);
    if (module->dir_name != NULL)
      DEBUG(level + 3, "Directory: %s", module->dir_name);
    DEBUG(level + 3, "File name: %s", module->file_name);
    DEBUG(level + 3, "Follows the naming convention: %s",
      module->is_regular ? "yes" : "no");
  }
  DEBUG(level + 1, "All local ASN.1 modules follow the naming convention: %s",
    makefile->ASN1ModulesRegular ? "yes" : "no");
  if (makefile->central_storage) DEBUG(level + 1, "All ASN.1 modules from other "
    "directories follow the naming convention: %s",
    makefile->BaseASN1ModulesRegular ? "yes" : "no");
  DEBUG(level + 1, "User C/C++ modules: (%u pcs.)", makefile->nUserFiles);
  for (i = 0; i < makefile->nUserFiles; i++) {
    const struct user_struct *user = makefile->UserFiles + i;
    DEBUG(level + 2, "File prefix: %s", user->file_prefix);
    if (user->dir_name != NULL)
      DEBUG(level + 3, "Directory: %s", user->dir_name);
    if (user->header_name != NULL) {
      DEBUG(level + 3, "Header file: %s", user->header_name);
      DEBUG(level + 3, "Header file has .hh or .hpp suffix: %s",
Elemer Lelik's avatar
Elemer Lelik committed
      user->has_hh_suffix ? "yes" : "no");
    }
    if (user->source_name != NULL) {
      DEBUG(level + 3, "Source file: %s", user->source_name);
      DEBUG(level + 3, "Source file has .cc or .cpp suffix: %s",
Elemer Lelik's avatar
Elemer Lelik committed
      user->has_cc_suffix ? "yes" : "no");
      DEBUG(level + 3, "Object file: %s.o", user->file_prefix);
    }
  }
  DEBUG(level + 1, "All local C/C++ header files follow the naming "
    "convention: %s", makefile->UserHeadersRegular ? "yes" : "no");
  DEBUG(level + 1, "All local C/C++ source files follow the naming "
    "convention: %s", makefile->UserSourcesRegular ? "yes" : "no");
  if (makefile->central_storage) {
    DEBUG(level + 1, "All C/C++ header files from other directories follow the "
      "naming convention: %s", makefile->BaseUserHeadersRegular ? "yes" : "no");
    DEBUG(level + 1, "All C/C++ source files from other directories follow the "
      "naming convention: %s", makefile->BaseUserSourcesRegular ? "yes" : "no");
  }
  DEBUG(level + 1, "Other files: (%u pcs.)", makefile->nOtherFiles);
  for (i = 0; i < makefile->nOtherFiles; i++)
    DEBUG(level + 2, "File name: %s", makefile->OtherFiles[i]);
  DEBUG(level + 1, "Use pre-compiled files from central storage: %s",
    makefile->central_storage ? "yes" : "no");
  if (makefile->central_storage) {
    DEBUG(level + 1, "Directories of pre-compiled files: (%u pcs.)",
      makefile->nBaseDirs);
    for (i = 0; i < makefile->nBaseDirs; i++) {
      const struct base_dir_struct *base_dir = makefile->BaseDirs + i;
      DEBUG(level + 2, "Directory: %s", base_dir->dir_name);
      DEBUG(level + 3, "Has TTCN-3/ASN.1 modules: %s",
Elemer Lelik's avatar
Elemer Lelik committed
      base_dir->has_modules ? "yes" : "no");
    }
  }
  DEBUG(level + 1, "Working directory: %s",
    makefile->working_dir != NULL ? makefile->working_dir : "<unknown>");
  DEBUG(level + 1, "GNU make: %s", makefile->gnu_make ? "yes" : "no");
  DEBUG(level + 1, "Execution mode: %s",
    makefile->single_mode ? "single" : "parallel");
  DEBUG(level + 1, "Name of executable: %s",
    makefile->ets_name != NULL ? makefile->ets_name : "<unknown>");
  DEBUG(level + 1, "Output file: %s",
    makefile->output_file != NULL ? makefile->output_file : "<unknown>");
  DEBUG(level + 1, "Force overwrite: %s",
    makefile->force_overwrite ? "yes" : "no");
  DEBUG(level + 1, "Use function test runtime: %s",
    makefile->use_runtime_2 ? "yes" : "no");
  DEBUG(level + 1, "Use dynamic linking: %s",
    makefile->dynamic ? "yes" : "no");
  DEBUG(level + 1, "Code splitting mode: %s",
    makefile->code_splitting_mode != NULL ?
      makefile->code_splitting_mode : "<unknown>");
  DEBUG(level + 1, "Code coverage file: %s",
    makefile->tcov_file_name != NULL ?
      makefile->tcov_file_name : "<unknown>");
Elemer Lelik's avatar
Elemer Lelik committed
  if (makefile->profiled_file_list) {
    char* lists = mcopystr(makefile->profiled_file_list->str);
    struct string_list* iter = makefile->profiled_file_list->next;
    while(iter != NULL) {
      lists = mputprintf(lists, " %s", iter->str);
      iter = iter->next;
    }
    DEBUG(level + 1, "Profiled file list(s): %s", lists);
    Free(lists);
  }
#ifdef COVERAGE_BUILD
  DEBUG(level + 1, "Enable coverage: %s", makefile->coverage ? "yes" : "no");
#endif
}

/** Returns the name of an existing file that is related to command line
 * argument \a argument. Tries the given list of suffixes. NULL pointer is
 * returned if no file was found. The returned string shall be deallocated
 * by the caller. */
static char *get_file_name_for_argument(const char *argument)
{
  static const char * const suffix_list[] = {
    "", ".ttcnpp", ".ttcnin", ".ttcn", ".ttcn3", ".3mp", ".asn", ".asn1",
    ".cc", ".c", ".cpp", ".hh", ".h",".hpp", ".cfg", ".prj", NULL
  };
  const char * const *suffix_ptr;
  for (suffix_ptr = suffix_list; *suffix_ptr != NULL; suffix_ptr++) {
    char *file_name = mputstr(mcopystr(argument), *suffix_ptr);
    if (get_path_status(file_name) == PS_FILE) return file_name;
    Free(file_name);
  }
  return NULL;
}

/** Converts \a path_name to an absolute directory using \a working_dir.
 * NULL pointer is returned if \a path_name does not contain a directory or
 * the resulting absolute directory is identical to \a working_dir.
 * The returned string shall be deallocated by the caller. */
static char *get_dir_name(const char *path_name, const char *working_dir)
{
  char *dir_name = get_dir_from_path(path_name);
  if (dir_name != NULL) {
Elemer Lelik's avatar
Elemer Lelik committed
    char *absolute_dir = get_absolute_dir(dir_name, working_dir, TRUE);
    Free(dir_name);
    if (absolute_dir == NULL || working_dir == NULL) {
      /* an error occurred */
      return NULL;
    } else if (!strcmp(absolute_dir, working_dir)) {
      /* the directory is identical to the working dir */
      Free(absolute_dir);
      return NULL;
    } else return absolute_dir;
  } else return NULL;
}

/** Returns whether \a dirname1 and \a dirname2 contain the same (canonized
 * absolute) directories. NULL pointer is handled in a special way: it is
 * identical only to itself. */
static boolean is_same_directory(const char *dirname1, const char *dirname2)
{
  if (dirname1 == NULL) {
    if (dirname2 == NULL) return TRUE;
    else return FALSE;
  } else {
    if (dirname2 == NULL) return FALSE;
    else if (strcmp(dirname1, dirname2)) return FALSE;
    else return TRUE;
  }
}

/** Returns whether the file \a filename1 in directory \a dirname1 is identical
 * to file \a filename2 in directory \a dirname2. Only the directory names can
 * be NULL. */
static boolean is_same_file(const char *dirname1, const char *filename1,
  const char *dirname2, const char *filename2)
{
  /* first examine the file names for efficiency reasons */
  if (strcmp(filename1, filename2)) return FALSE;
  else return is_same_directory(dirname1, dirname2);
}

/** Determines whether the TTCN-3 or ASN.1 module identifiers \a module1 and
 * \a module2 are the same. Characters '-' and '_' in module names are not
 * distinguished. */
static boolean is_same_module(const char *module1, const char *module2)
{
  size_t i;
  for (i = 0; ; i++) {
    switch (module1[i]) {
    case '\0':
      if (module2[i] == '\0') return TRUE;
      else return FALSE;
    case '-':
    case '_':
      if (module2[i] != '-' && module2[i] != '_') return FALSE;
      break;
    default:
      if (module1[i] != module2[i]) return FALSE;
      break;
    }
  }
  return FALSE; /* to avoid warnings */
}

/** Truncates the suffix (i.e. the last dot and the characters following it)
 * from \a file_name and returns a copy of the prefix of \a file_name.
 * If \a file_name does not have a suffix an exact copy of it is returned.
 * The returned string shall be deallocated by the caller. */
static char *cut_suffix(const char *file_name)
{
  char *ret_val;
  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;
  ret_val = mcopystr(file_name);
  if (last_dot != (size_t)-1) ret_val = mtruncstr(ret_val, last_dot);
  return ret_val;
}

/** Determines the name of the preprocessed file from \a file_name.
 *  It is assumed that \a file_name has ttcnpp suffix.
 *  The returned string shall be deallocated by the caller. */
static char *get_preprocessed_file_name(const char *file_name)
{
  char *ret_val = cut_suffix(file_name);
  ret_val = mputstr(ret_val, ".ttcn");
  return ret_val;
}

/** Check if any of the preprocessed ttcn file names with the preprocessed
 * (TTCN-3) suffix is equal to any other file given in the \a makefile */
static void check_preprocessed_filename_collision(
  struct makefile_struct *makefile)
{
  size_t i;
  if (makefile->nTTCN3PPModules == 0) {
    WARNING("TTCN-3 preprocessing (option `-p') is enabled, but no TTCN-3 "
      "files to be preprocessed were given for the Makefile.");
  }
  for (i = 0; i < makefile->nTTCN3PPModules; i++) {
    const struct module_struct *pp_module = makefile->TTCN3PPModules + i;
    /* name of the intermediate preprocessed file */
    char *preprocessed_name = get_preprocessed_file_name(pp_module->file_name);
    size_t j;
    for (j = 0; j < makefile->nTTCN3Modules; j++) {
      struct module_struct *module = makefile->TTCN3Modules + j;
      if (is_same_file(pp_module->dir_name, preprocessed_name,
	module->dir_name, module->file_name)) {
	if (is_same_module(pp_module->module_name, module->module_name)) {
          /* same file with the same module */
	  char *pp_pathname = compose_path_name(pp_module->dir_name,
	    pp_module->file_name);
	  char *m_pathname = compose_path_name(module->dir_name,
	    module->file_name);
	  WARNING("File `%s' containing TTCN-3 module `%s' is generated by "
	    "the preprocessor from `%s'. Removing the file from the list of "
	    "normal TTCN-3 modules.", m_pathname, module->module_name,
	    pp_pathname);
	  Free(pp_pathname);
	  Free(m_pathname);
	  Free(module->dir_name);
	  Free(module->file_name);
	  Free(module->module_name);
	  makefile->nTTCN3Modules--;
	  memmove(module, module + 1, (makefile->nTTCN3Modules - j) *
	    sizeof(*makefile->TTCN3Modules));
	  makefile->TTCN3Modules =
	    (struct module_struct*)Realloc(makefile->TTCN3Modules,
	    makefile->nTTCN3Modules * sizeof(*makefile->TTCN3Modules));
	} else {
          /* same file with different module */
	  char *pp_pathname = compose_path_name(pp_module->dir_name,
	    pp_module->file_name);
	  char *m_pathname = compose_path_name(module->dir_name,
	    module->file_name);
	  ERROR("Preprocessed intermediate file of `%s' (module `%s') clashes "
	    "with file `%s' containing TTCN-3 module `%s'.", pp_pathname,
	    pp_module->module_name, m_pathname, module->module_name);
	  Free(pp_pathname);
	  Free(m_pathname);
	}
      } else if (is_same_module(pp_module->module_name, module->module_name)) {
        /* different file with the same module */
	char *pp_pathname = compose_path_name(pp_module->dir_name,
	  pp_module->file_name);
	char *m_pathname = compose_path_name(module->dir_name,
	  module->file_name);
	ERROR("Both files `%s' and `%s' contain TTCN-3 module `%s'.",
	  pp_pathname, m_pathname, pp_module->module_name);
	Free(pp_pathname);
	Free(m_pathname);
      }
    }
    for (j = 0; j < makefile->nASN1Modules; j++) {
      struct module_struct *module = makefile->ASN1Modules + j;
      if (is_same_file(pp_module->dir_name, preprocessed_name,
	module->dir_name, module->file_name)) {
	char *pp_pathname = compose_path_name(pp_module->dir_name,
	  pp_module->file_name);
	char *m_pathname = compose_path_name(module->dir_name,
	  module->file_name);
	ERROR("Preprocessed intermediate file of `%s' (module `%s') clashes "
	  "with file `%s' containing ASN.1 module `%s'.", pp_pathname,
	  pp_module->module_name, m_pathname, module->module_name);
	Free(pp_pathname);
	Free(m_pathname);
      }
    }
    for (j = 0; j < makefile->nXSDModules; j++) {
      struct module_struct *module = makefile->XSDModules + j;
      if (module->dir_name == NULL || module->file_name == NULL) {
        continue;
      }
      if (is_same_file(pp_module->dir_name, preprocessed_name,
	module->dir_name, module->file_name)) {
	char *pp_pathname = compose_path_name(pp_module->dir_name,
	  pp_module->file_name);
	char *m_pathname = compose_path_name(module->dir_name,
	  module->file_name);
	ERROR("Preprocessed intermediate file of `%s' (module `%s') clashes "
	  "with file `%s' containing TTCN-3 module `%s'.", pp_pathname,
	  pp_module->module_name, m_pathname, module->module_name);
	Free(pp_pathname);
	Free(m_pathname);
      }
    }
    for (j = 0; j < makefile->nOtherFiles; j++) {
      char *dir_name = get_dir_name(makefile->OtherFiles[j],
                                    makefile->working_dir);
      char *file_name = get_file_from_path(makefile->OtherFiles[j]);
      if (is_same_file(pp_module->dir_name, preprocessed_name, dir_name,
	file_name)) {
	char *pp_pathname = compose_path_name(pp_module->dir_name,
	  pp_module->file_name);
	ERROR("Preprocessed intermediate file of `%s' (module `%s') clashes "
	  "with other file `%s'.", pp_pathname, pp_module->module_name,
	  makefile->OtherFiles[j]);
	Free(pp_pathname);
      }
      Free(dir_name);
      Free(file_name);
    }
    Free(preprocessed_name);
  }
}

/** Checks the name clash between existing module \a module and newly added
 * module with parameters \a path_name, \a dir_name, \a file_name,
 * \a module_name. Both the existing and the new module shall be of the same
 * kind, parameter \a kind shall contain the respective string (either "ASN.1"
 * or "TTCN-3"). If a clash is found the parameters of the new module except
 * \a path_name are deallocated and TRUE is returned. Otherwise FALSE is
 * returned. */
static boolean check_module_clash_same(const struct module_struct *module,
  const char *kind, const char *path_name, char *dir_name, char *file_name,
  char *module_name)
{
  if (is_same_module(module_name, module->module_name)) {
    if (is_same_file(dir_name, file_name,
        module->dir_name, module->file_name)) {
      /* the same file was given twice: just issue a warning */
      WARNING("File `%s' was given more than once for the Makefile.",
              path_name);
    } else {
      /* two different files contain the same module: this cannot be
       * resolved as the generated C++ files will clash */
      char *path_name1 = compose_path_name(module->dir_name,
	module->file_name);
      char *path_name2 = compose_path_name(dir_name, file_name);
      ERROR("Both files `%s' and `%s' contain %s module `%s'.",
	path_name1, path_name2, kind, module_name);
      Free(path_name1);
      Free(path_name2);
    }
    Free(file_name);
    Free(dir_name);
    Free(module_name);
    return TRUE;
  } else return FALSE;
}

/** Checks the name clash between existing module \a module and newly added
 * module with parameters \a dir_name, \a file_name, \a module_name. The two
 * modules shall be of different kinds (one is ASN.1, the other is TTCN-3).
 * Parameters \a kind1 and \a kind2 shall contain the respective strings. If a
 * clash is found the parameters of the new module are deallocated and TRUE is
 * returned. Otherwise FALSE is returned. */
static boolean check_module_clash_different(const struct module_struct *module,
  const char *kind1, char *dir_name, char *file_name, char *module_name,
  const char *kind2)
{
  if (is_same_module(module_name, module->module_name)) {
    /* two different files contain the same module: this cannot be resolved
     * as the generated C++ files will clash */
    char *path_name1 = compose_path_name(module->dir_name, module->file_name);
    char *path_name2 = compose_path_name(dir_name, file_name);
    ERROR("File `%s' containing %s module `%s' and file `%s' containing "
      "%s module `%s' cannot be used together in the same Makefile.",
      path_name1, kind1, module->module_name, path_name2, kind2, module_name);
    Free(path_name1);
    Free(path_name2);
    Free(file_name);
    Free(dir_name);
    Free(module_name);
    return TRUE;
  } else return FALSE;
}

/** Adds a TTCN-3 module to Makefile descriptor structure \a makefile.
 * The name of the TTCN-3 source file is \a path_name, the module identifier
 * is \a module_name. It is checked whether a file or module with the same name
 * already exists in \a makefile and an appropriate warning or error is
 * reported. */
static void add_ttcn3_module(struct makefile_struct *makefile,
  const char *path_name, char *module_name)
{
  struct module_struct *module;
  char *dir_name = get_dir_name(path_name, makefile->working_dir);
  char *file_name = get_file_from_path(path_name);
  const char *suffix = get_suffix(file_name);
  size_t i;
  boolean is_preprocessed = FALSE;

  if (suffix != NULL) {
    if (!strcmp(suffix, "ttcnpp")) {
      if (makefile->preprocess) is_preprocessed = TRUE;
      else WARNING("The suffix of TTCN-3 file `%s' indicates that it should be "
        "preprocessed, but TTCN-3 preprocessing is not enabled. The file "
        "will be added to the list of normal TTCN-3 modules in the Makefile.",
        path_name);
    } else if (!strcmp(suffix, "ttcnin")) {
      WARNING("The suffix of file `%s' indicates that it should be a "
        "preprocessor include file, but it contains a TTCN-3 module named `%s'. "
        "The file will be added to the list of normal TTCN-3 modules in the "
        "Makefile.", path_name, module_name);
    }
  }

  for (i = 0; i < makefile->nASN1Modules; i++) {
    if (check_module_clash_different(makefile->ASN1Modules + i, "ASN.1",
      dir_name, file_name, module_name, "TTCN-3")) return;
  }
  /* never entered if suffix is NULL */
  if (is_preprocessed) {
    char *file_prefix;
    for (i = 0; i < makefile->nTTCN3PPModules; i++) {
      if (check_module_clash_same(makefile->TTCN3PPModules + i, "TTCN-3",
          path_name, dir_name, file_name, module_name)) return;
    }
    for (i = 0; i < makefile->nXSDModules; i++) {
      if (check_module_clash_different(makefile->XSDModules + i, "TTCN-3",
        dir_name, file_name, module_name, "TTCN-3")) return;
    }
    /* clashes with normal TTCN-3 modules will be checked (and maybe resolved)
     * in \a check_preprocessed_filename_collision() */
    /* add it to the list of TTCN-3 modules to be preprocessed */
    makefile->TTCN3PPModules = (struct module_struct*)
      Realloc(makefile->TTCN3PPModules,
        (makefile->nTTCN3PPModules + 1) * sizeof(*makefile->TTCN3PPModules));
    module = makefile->TTCN3PPModules + makefile->nTTCN3PPModules;
    makefile->nTTCN3PPModules++;
    module->dir_name = dir_name;
    module->file_name = file_name;
    module->module_name = module_name;
    file_prefix = cut_suffix(file_name);
    if (!strcmp(file_prefix, module_name)) module->is_regular = TRUE;
    else module->is_regular = FALSE;
    Free(file_prefix);
  } else {
    /* the file is not preprocessed */
    for (i = 0; i < makefile->nTTCN3Modules; i++) {
      if (check_module_clash_same(makefile->TTCN3Modules + i, "TTCN-3",
          path_name, dir_name, file_name, module_name)) return;
    }
    /* clashes with preprocessed TTCN-3 modules will be checked (and maybe
     * resolved) in \a check_preprocessed_filename_collision() */
    /* add it to the list of normal TTCN-3 modules */
    makefile->TTCN3Modules = (struct module_struct*)
      Realloc(makefile->TTCN3Modules,
        (makefile->nTTCN3Modules + 1) * sizeof(*makefile->TTCN3Modules));
    module = makefile->TTCN3Modules + makefile->nTTCN3Modules;
    makefile->nTTCN3Modules++;
    module->dir_name = dir_name;
    module->file_name = file_name;
    module->module_name = module_name;
    if (suffix != NULL && !strcmp(suffix, "ttcn")) {
      char *file_prefix = cut_suffix(file_name);
      if (!strcmp(file_prefix, module_name)) module->is_regular = TRUE;
      else module->is_regular = FALSE;
      Free(file_prefix);
    } else {
      module->is_regular = FALSE;
    }
  }
}

/** ASN.1 filename shall contain no hyphen */
static boolean is_valid_asn1_filename(const char* file_name)
{
  if (0 == strchr(file_name, '-')) {
    return TRUE;
  }
  return FALSE;
}

/** Adds an ASN.1 module to Makefile descriptor structure \a makefile.
 * The name of the ASN.1 source file is \a path_name, the module identifier
 * is \a module_name. It is checked whether a file or module with the same name
 * already exists in \a makefile and an appropriate warning or error is
 * reported. */
static void add_asn1_module(struct makefile_struct *makefile,
  const char *path_name, char *module_name)
{
  struct module_struct *module;
  char *dir_name = get_dir_name(path_name, makefile->working_dir);
  char *file_name = get_file_from_path(path_name);
  const char *suffix = get_suffix(file_name);
  size_t i;
  for (i = 0; i < makefile->nASN1Modules; i++) {
    if (check_module_clash_same(makefile->ASN1Modules + i, "ASN.1", path_name,
      dir_name, file_name, module_name)) return;
  }
  for (i = 0; i < makefile->nTTCN3Modules; i++) {
    if (check_module_clash_different(makefile->TTCN3Modules + i, "TTCN-3",
      dir_name, file_name, module_name, "ASN.1")) return;
  }
  if (makefile->preprocess) {
    for (i = 0; i < makefile->nTTCN3PPModules; i++) {
      if (check_module_clash_different(makefile->TTCN3PPModules + i, "TTCN-3",
	dir_name, file_name, module_name, "ASN.1")) return;
    }
  }
  for (i = 0; i < makefile->nXSDModules; i++) {
    if (check_module_clash_different(makefile->XSDModules + i, "TTCN-3",
      dir_name, file_name, module_name, "ASN.1")) return;
  }
  makefile->ASN1Modules = (struct module_struct*)
    Realloc(makefile->ASN1Modules,
      (makefile->nASN1Modules + 1) * sizeof(*makefile->ASN1Modules));
  module = makefile->ASN1Modules + makefile->nASN1Modules;
  makefile->nASN1Modules++;
  module->dir_name = dir_name;
  module->file_name = file_name;
  module->module_name = module_name;
  if (suffix != NULL && !strcmp(suffix, "asn")) {
    char *file_prefix = cut_suffix(file_name);
    /* replace all '_' with '-' in file name prefix */
    for (i = 0; file_prefix[i] != '\0'; i++)
      if (file_prefix[i] == '_') file_prefix[i] = '-';
    if (!strcmp(file_prefix, module_name)) module->is_regular = TRUE;
    else module->is_regular = FALSE;
    Free(file_prefix);
  } else {
    module->is_regular = FALSE;
  }
}

/** Adds an XSD module to Makefile descriptor structure \a makefile.
 * The name of the XSD source file is \a path_name, the module identifier
 * is \a module_name. It is checked whether a file or module with the same name
 * already exists in \a makefile and an appropriate warning or error is
 * reported. */
static void add_xsd_module(struct makefile_struct *makefile,