6a793ddeb7
[CONFIGURATION] * NEW: Replace my old depracted gmail mail by the new one.
823 lines
14 KiB
C
823 lines
14 KiB
C
/***************************************************************************
|
|
|
|
gbc_compile.c
|
|
|
|
(c) 2000-2017 Benoît Minisini <benoit.minisini@gambas-basic.org>
|
|
|
|
This program 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 2, or (at your option)
|
|
any later version.
|
|
|
|
This program 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 this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
MA 02110-1301, USA.
|
|
|
|
***************************************************************************/
|
|
|
|
#define __GBC_COMPILE_C
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <dirent.h>
|
|
#include <pwd.h>
|
|
#include <unistd.h>
|
|
|
|
#include "gb_common.h"
|
|
#include "gb_error.h"
|
|
#include "gb_str.h"
|
|
#include "gb_file.h"
|
|
#include "gb_component.h"
|
|
|
|
#include "gbc_compile.h"
|
|
#include "gb_reserved.h"
|
|
#include "gbc_read.h"
|
|
#include "gbc_trans.h"
|
|
#include "gbc_header.h"
|
|
#include "gb_code.h"
|
|
#include "gbc_output.h"
|
|
#include "gbc_form.h"
|
|
#include "gbc_chown.h"
|
|
#include "gb_arch.h"
|
|
|
|
/*#define DEBUG*/
|
|
|
|
bool COMP_verbose = FALSE;
|
|
|
|
char *COMP_root = NULL;
|
|
char *COMP_dir;
|
|
char *COMP_project;
|
|
char *COMP_project_name;
|
|
char *COMP_info_path;
|
|
char *COMP_lib_path;
|
|
char *COMP_classes = NULL;
|
|
COMPILE COMP_current;
|
|
uint COMP_version = GAMBAS_PCODE_VERSION;
|
|
char *COMP_default_namespace = NULL;
|
|
bool COMP_do_not_lock = FALSE;
|
|
|
|
#define STARTUP_MAX_LINE 256
|
|
|
|
const FORM_FAMILY COMP_form_families[] =
|
|
{
|
|
{ "form", FORM_NORMAL },
|
|
{ "report", FORM_NORMAL },
|
|
{ "webpage", FORM_WEBPAGE },
|
|
{ "webform", FORM_NORMAL },
|
|
{ "termform", FORM_NORMAL },
|
|
{ NULL }
|
|
};
|
|
|
|
static bool read_line(FILE *f, char *dir, int max)
|
|
{
|
|
char *p;
|
|
int c;
|
|
|
|
p = dir;
|
|
|
|
for(;;)
|
|
{
|
|
max--;
|
|
|
|
c = fgetc(f);
|
|
if (c == EOF)
|
|
return TRUE;
|
|
|
|
if (c == '\n' || max == 0)
|
|
{
|
|
*p = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
*p++ = (char)c;
|
|
}
|
|
}
|
|
|
|
static void add_memory_list(char *p, int size)
|
|
{
|
|
char *pe = p + size;
|
|
char *p2;
|
|
int len;
|
|
|
|
for(;;)
|
|
{
|
|
if (p >= pe)
|
|
break;
|
|
|
|
p2 = p;
|
|
while (p2 < pe && *p2 != '\n')
|
|
p2++;
|
|
|
|
if (p2 >= pe)
|
|
break;
|
|
|
|
len = p2 - p;
|
|
/*if (len > 2 && p[len - 1] == '?')
|
|
len--;*/
|
|
|
|
COMPILE_add_class(p, len);
|
|
|
|
p = p2 + 1;
|
|
}
|
|
}
|
|
|
|
static bool add_file_list(const char *path)
|
|
{
|
|
FILE *f;
|
|
char line[256];
|
|
int len;
|
|
|
|
f = fopen(path, "r");
|
|
|
|
if (!f)
|
|
return TRUE;
|
|
|
|
for(;;)
|
|
{
|
|
if (read_line(f, line, sizeof(line)))
|
|
break;
|
|
|
|
len = strlen(line);
|
|
/*if (len > 2 && line[len - 1] == '?')
|
|
len--;*/
|
|
|
|
COMPILE_add_class(line, len);
|
|
}
|
|
|
|
fclose(f);
|
|
return FALSE;
|
|
}
|
|
|
|
static void add_library_list_file(const char *path, bool ref)
|
|
{
|
|
ARCH *arch;
|
|
ARCH_FIND find;
|
|
const char *name;
|
|
char *rpath = NULL;
|
|
bool is_file = FALSE;
|
|
|
|
if (*path == ':')
|
|
{
|
|
name = &path[1];
|
|
|
|
rpath = STR_cat(FILE_cat(COMP_lib_path, name, NULL), ".gambas", NULL);
|
|
if (!FILE_exist(rpath))
|
|
{
|
|
STR_free(rpath);
|
|
rpath = STR_cat(FILE_cat(COMP_root, "lib/gambas" GAMBAS_VERSION_STRING, name, NULL), ".gambas", NULL);
|
|
}
|
|
|
|
if (!FILE_exist(rpath))
|
|
path = NULL;
|
|
else
|
|
path = rpath;
|
|
}
|
|
else
|
|
{
|
|
name = path;
|
|
if (!FILE_exist(path))
|
|
path = NULL;
|
|
/*
|
|
{
|
|
is_file = TRUE;
|
|
path = FILE_cat(FILE_get_dir(path), ".list", NULL);
|
|
if (!FILE_exist(path))
|
|
path = NULL;
|
|
}
|
|
*/
|
|
}
|
|
|
|
if (path)
|
|
{
|
|
if (is_file)
|
|
{
|
|
add_file_list(path);
|
|
}
|
|
else
|
|
{
|
|
arch = ARCH_open(path);
|
|
|
|
if (!ARCH_find(arch, ".list", 0, &find))
|
|
add_memory_list(&arch->addr[find.pos], find.len);
|
|
|
|
ARCH_close(arch);
|
|
}
|
|
}
|
|
else
|
|
ERROR_warning((ref ? "cannot find reference: %s" : "cannot find library: %s"), name);
|
|
|
|
if (rpath)
|
|
STR_free(rpath);
|
|
}
|
|
|
|
|
|
void COMPILE_add_component(const char *name)
|
|
{
|
|
char *path;
|
|
|
|
if (COMP_verbose)
|
|
fprintf(stderr, "Loading information from component '%s'\n", name);
|
|
|
|
path = (char *)FILE_cat(COMP_info_path, name, NULL);
|
|
strcat(path, ".list");
|
|
|
|
if (add_file_list(path))
|
|
{
|
|
// Do not raise an error if a component self-reference is not found
|
|
if (strcmp(name, COMP_project_name))
|
|
//fprintf(stderr, "warning: cannot read component list file: %s.list\n", name);
|
|
THROW("Component not found: &1", name);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
static FILE *open_project_file()
|
|
{
|
|
FILE *fp = fopen(COMP_project, "r");
|
|
if (!fp)
|
|
THROW(E_OPEN, COMP_project);
|
|
return fp;
|
|
}
|
|
|
|
static bool line_begins_with(const char *line, const char *key, int len)
|
|
{
|
|
if (len < 0)
|
|
len = strlen(key);
|
|
|
|
return strncmp(line, key, len) == 0;
|
|
}
|
|
|
|
static void startup_print(FILE *fs, const char *key, const char *def)
|
|
{
|
|
FILE *fp;
|
|
char line[256];
|
|
int len = strlen(key);
|
|
bool print = FALSE;
|
|
|
|
fp = open_project_file();
|
|
|
|
for(;;)
|
|
{
|
|
if (read_line(fp, line, sizeof(line)))
|
|
break;
|
|
|
|
if (line_begins_with(line, key, len))
|
|
{
|
|
fprintf(fs, "%s\n", &line[len]);
|
|
print = TRUE;
|
|
}
|
|
}
|
|
|
|
fclose(fp);
|
|
|
|
if (!print && def)
|
|
fprintf(fs, "%s\n", def);
|
|
}
|
|
|
|
static char *find_version_in_file(void)
|
|
{
|
|
char *dir, *pdir;
|
|
FILE *fv;
|
|
char line[256];
|
|
const char *path;
|
|
int len;
|
|
|
|
dir = STR_copy(COMP_project);
|
|
|
|
for(;;)
|
|
{
|
|
pdir = STR_copy(FILE_get_dir(dir));
|
|
STR_free(dir);
|
|
dir = pdir;
|
|
|
|
if (dir[0] == '/' && dir[1] == 0)
|
|
{
|
|
STR_free(dir);
|
|
return NULL;
|
|
}
|
|
|
|
path = FILE_cat(dir, "VERSION", NULL);
|
|
|
|
if (FILE_exist(path))
|
|
{
|
|
STR_free(dir);
|
|
break;
|
|
}
|
|
}
|
|
|
|
fv = fopen(path, "r");
|
|
if (!fv)
|
|
return NULL;
|
|
|
|
len = fread(line, 1, sizeof(line) - 1, fv);
|
|
while (len > 0 && isspace(line[len - 1]))
|
|
len--;
|
|
line[len] = 0;
|
|
return STR_copy(line);
|
|
}
|
|
|
|
static void startup_print_version(FILE *fs)
|
|
{
|
|
FILE *fp;
|
|
char line[256];
|
|
char *version = NULL;
|
|
char *branch = NULL;
|
|
bool add_branch = FALSE;
|
|
|
|
fp = open_project_file();
|
|
|
|
for(;;)
|
|
{
|
|
if (read_line(fp, line, sizeof(line)))
|
|
break;
|
|
|
|
if (line_begins_with(line, "Version=", 8))
|
|
{
|
|
version = STR_copy(&line[8]);
|
|
branch = index(version, ' ');
|
|
}
|
|
else if (line_begins_with(line, "VersionFile=", 12))
|
|
{
|
|
if (line[12] == '1')
|
|
{
|
|
version = find_version_in_file();
|
|
add_branch = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
fclose(fp);
|
|
|
|
if (version)
|
|
{
|
|
fputs(version, fs);
|
|
if (add_branch && branch)
|
|
fputs(branch, fs);
|
|
fputc('\n', fs);
|
|
STR_free(version);
|
|
}
|
|
else
|
|
fputs("0.0.0\n", fs);
|
|
}
|
|
|
|
static void create_startup_file()
|
|
{
|
|
const char *name;
|
|
FILE *fs;
|
|
|
|
name = FILE_cat(FILE_get_dir(COMP_project), ".startup", NULL);
|
|
fs = fopen(name, "w");
|
|
if (!fs)
|
|
THROW("Cannot create .startup file");
|
|
|
|
// Do that now, otherwise file buffer can be erased
|
|
FILE_set_owner(name, COMP_project);
|
|
|
|
startup_print(fs, "Startup=", "");
|
|
startup_print(fs, "Title=", "");
|
|
startup_print(fs, "Stack=", "0");
|
|
startup_print(fs, "StackTrace=", "0");
|
|
|
|
startup_print_version(fs);
|
|
|
|
fputc('\n', fs);
|
|
startup_print(fs, "Component=", NULL);
|
|
startup_print(fs, "Library=", NULL);
|
|
fputc('\n', fs);
|
|
|
|
if (fclose(fs))
|
|
THROW("Cannot create .startup file");
|
|
}
|
|
|
|
#undef isdigit
|
|
|
|
static int read_version_digits(const char **pstr)
|
|
{
|
|
const char *p = *pstr;
|
|
int n;
|
|
int i;
|
|
|
|
if (!isdigit(*p))
|
|
return -1;
|
|
|
|
n = 0;
|
|
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
n = (n << 4) + *p++ - '0';
|
|
if (!isdigit(*p))
|
|
break;
|
|
}
|
|
|
|
*pstr = p;
|
|
return n;
|
|
}
|
|
|
|
static void init_version(void)
|
|
{
|
|
const char *ver;
|
|
int n, v;
|
|
|
|
ver = getenv("GB_PCODE_VERSION");
|
|
if (ver && *ver)
|
|
{
|
|
v = 0;
|
|
n = read_version_digits(&ver);
|
|
if (n <= 0 || n > GAMBAS_VERSION)
|
|
return;
|
|
|
|
v = n << 24;
|
|
|
|
if (*ver++ != '.')
|
|
return;
|
|
|
|
n = read_version_digits(&ver);
|
|
if (n < 0 || n > 0x99)
|
|
return;
|
|
|
|
v |= n << 16;
|
|
|
|
if (*ver++ == '.')
|
|
{
|
|
n = read_version_digits(&ver);
|
|
if (n > 0)
|
|
{
|
|
if (n > 0x9999)
|
|
return;
|
|
|
|
v |= n;
|
|
}
|
|
}
|
|
|
|
COMP_version = v;
|
|
}
|
|
}
|
|
|
|
void COMPILE_init(void)
|
|
{
|
|
FILE *fp;
|
|
char line[256];
|
|
char *env;
|
|
|
|
RESERVED_init();
|
|
|
|
if (!COMP_root)
|
|
COMP_root = STR_copy(FILE_get_dir(FILE_get_dir(FILE_find_gambas())));
|
|
|
|
// Component directory
|
|
|
|
COMP_info_path = STR_copy(FILE_cat(COMP_root, "share/gambas" GAMBAS_VERSION_STRING "/info", NULL));
|
|
|
|
// Local libraries directory
|
|
|
|
env = getenv("XDG_DATA_HOME");
|
|
if (env && *env)
|
|
COMP_lib_path = STR_copy(FILE_cat(env, "gambas3/lib", NULL));
|
|
else
|
|
COMP_lib_path = STR_copy(FILE_cat(FILE_get_home(), ".local/share/gambas3/lib", NULL));
|
|
|
|
// Project name
|
|
|
|
COMP_project_name = STR_copy(FILE_get_name(FILE_get_dir(COMP_project)));
|
|
|
|
// Bytecode version
|
|
|
|
init_version();
|
|
|
|
// Project classes
|
|
|
|
BUFFER_create(&COMP_classes);
|
|
|
|
COMPILE_add_component("gb");
|
|
|
|
fp = open_project_file();
|
|
|
|
for(;;)
|
|
{
|
|
if (read_line(fp, line, sizeof(line)))
|
|
break;
|
|
|
|
/*printf("%s\n", line);*/
|
|
|
|
if (strncmp(line, "Component=", 10) == 0)
|
|
COMPILE_add_component(&line[10]);
|
|
else if (strncmp(line, "Library=", 8) == 0)
|
|
add_library_list_file(&line[8], FALSE);
|
|
else if (strncmp(line, "Reference=", 10) == 0)
|
|
add_library_list_file(&line[10], TRUE);
|
|
}
|
|
|
|
fclose(fp);
|
|
|
|
// Add local ".list" file
|
|
// Not possible at the moment.
|
|
// add_file_list(FILE_cat(FILE_get_dir(COMP_project), ".list", NULL));
|
|
|
|
// Startup file
|
|
|
|
create_startup_file();
|
|
|
|
// Adds a separator to make the difference between classes from components
|
|
// (they must be searched in the global symbol table) and classes from the
|
|
// project (they must be searched in the project symbol table)
|
|
|
|
COMPILE_add_class("-", 1);
|
|
|
|
/*
|
|
dir = opendir(FILE_get_dir(COMP_project));
|
|
if (dir)
|
|
{
|
|
while ((dirent = readdir(dir)) != NULL)
|
|
{
|
|
name = dirent->d_name;
|
|
if (*name == '.')
|
|
continue;
|
|
|
|
if ((strcasecmp(FILE_get_ext(name), "module") == 0)
|
|
|| (strcasecmp(FILE_get_ext(name), "class") == 0))
|
|
{
|
|
name = FILE_get_basename(name);
|
|
BUFFER_add(&COMP_classes, name, strlen(name));
|
|
BUFFER_add(&COMP_classes, "\n", 1);
|
|
}
|
|
}
|
|
|
|
closedir(dir);
|
|
}
|
|
|
|
BUFFER_add(&COMP_classes, "\n", 1);
|
|
*/
|
|
}
|
|
|
|
|
|
void COMPILE_begin(const char *file, bool trans, bool debug)
|
|
{
|
|
CLEAR(JOB);
|
|
|
|
JOB->name = STR_copy(file);
|
|
JOB->debug = debug;
|
|
JOB->form = FORM_get_file_family(JOB->name, &JOB->family);
|
|
JOB->output = OUTPUT_get_file(JOB->name);
|
|
|
|
if (trans)
|
|
{
|
|
JOB->trans = TRUE;
|
|
JOB->tname = OUTPUT_get_trans_file(JOB->name);
|
|
}
|
|
}
|
|
|
|
|
|
void COMPILE_alloc()
|
|
{
|
|
struct stat info;
|
|
off_t size;
|
|
|
|
BUFFER_create(&JOB->source);
|
|
CLASS_create(&JOB->class);
|
|
|
|
JOB->default_library = NO_SYMBOL;
|
|
|
|
size = 0;
|
|
|
|
if (stat(JOB->name, &info))
|
|
ERROR_warning("cannot stat file: %s", JOB->name);
|
|
else
|
|
size += info.st_size;
|
|
|
|
if (JOB->form)
|
|
{
|
|
if (stat(JOB->form, &info))
|
|
ERROR_warning("cannot stat file: %s", JOB->form);
|
|
else
|
|
size += info.st_size * 2;
|
|
}
|
|
|
|
ALLOC(&JOB->pattern, sizeof(PATTERN) * (16 + size));
|
|
ALLOC(&JOB->pattern_pos, sizeof(int) * (16 + size));
|
|
JOB->pattern_count = 0;
|
|
|
|
JOB->current = NULL;
|
|
}
|
|
|
|
|
|
void COMPILE_load(void)
|
|
{
|
|
if (BUFFER_load_file(&JOB->source, JOB->name))
|
|
THROW("Cannot load source file: &1", strerror(errno));
|
|
|
|
//BUFFER_add(&JOB->source, "\n", 1);
|
|
}
|
|
|
|
|
|
void COMPILE_free(void)
|
|
{
|
|
CLASS_delete(&JOB->class);
|
|
BUFFER_delete(&JOB->source);
|
|
FREE(&JOB->pattern);
|
|
FREE(&JOB->pattern_pos);
|
|
|
|
if (JOB->help)
|
|
ARRAY_delete(&JOB->help);
|
|
}
|
|
|
|
void COMPILE_end(void)
|
|
{
|
|
STR_free(JOB->name);
|
|
STR_free(JOB->form);
|
|
STR_free(JOB->output);
|
|
if (JOB->trans)
|
|
STR_free(JOB->tname);
|
|
if (JOB->help)
|
|
STR_free(JOB->hname);
|
|
}
|
|
|
|
|
|
void COMPILE_exit(bool can_dump_count)
|
|
{
|
|
if (COMP_verbose && can_dump_count)
|
|
PCODE_dump_count(stdout);
|
|
|
|
RESERVED_exit();
|
|
BUFFER_delete(&COMP_classes);
|
|
STR_free(COMP_project_name);
|
|
STR_free(COMP_project);
|
|
STR_free(COMP_info_path);
|
|
STR_free(COMP_dir);
|
|
STR_free(COMP_root);
|
|
STR_free(COMP_default_namespace);
|
|
}
|
|
|
|
static void add_class(const char *name, int len)
|
|
{
|
|
unsigned char clen = (unsigned char)len;
|
|
|
|
if (clen != len)
|
|
ERROR_panic("Class name is too long");
|
|
|
|
//fprintf(stderr, "add_class: %.*s\n", len, name);
|
|
|
|
BUFFER_add(&COMP_classes, &clen, 1);
|
|
BUFFER_add(&COMP_classes, name, len);
|
|
}
|
|
|
|
void COMPILE_add_class(const char *name, int len)
|
|
{
|
|
char *p;
|
|
|
|
p = memchr(name, ' ', len);
|
|
if (!p)
|
|
add_class(name, len);
|
|
else
|
|
add_class(name, p - name);
|
|
}
|
|
|
|
void COMPILE_end_class(void)
|
|
{
|
|
unsigned char clen = 0;
|
|
BUFFER_add(&COMP_classes, &clen, 1);
|
|
}
|
|
|
|
int COMPILE_lock_file(const char *name)
|
|
{
|
|
const char *path;
|
|
int fd;
|
|
|
|
if (COMP_do_not_lock)
|
|
return -1;
|
|
|
|
path = FILE_cat(COMP_dir, name, NULL);
|
|
|
|
fd = open(path, O_CREAT | O_WRONLY | O_CLOEXEC, 0666);
|
|
if (fd < 0)
|
|
goto __ERROR;
|
|
if (lockf(fd, F_LOCK, 0) < 0)
|
|
goto __ERROR;
|
|
|
|
return fd;
|
|
|
|
__ERROR:
|
|
|
|
ERROR_fail("unable to lock file: %s: %s", path, strerror(errno));
|
|
}
|
|
|
|
|
|
void COMPILE_unlock_file(int fd)
|
|
{
|
|
if (!COMP_do_not_lock)
|
|
close(fd);
|
|
}
|
|
|
|
|
|
void COMPILE_remove_lock(const char *name)
|
|
{
|
|
const char *path;
|
|
|
|
if (COMP_do_not_lock)
|
|
return;
|
|
|
|
path = FILE_cat(COMP_dir, name, NULL);
|
|
if (FILE_exist(path))
|
|
FILE_unlink(path);
|
|
}
|
|
|
|
|
|
void COMPILE_print(int type, int line, const char *msg, ...)
|
|
{
|
|
int i;
|
|
va_list args;
|
|
const char *arg[4];
|
|
int col = -1;
|
|
int lock;
|
|
|
|
if (!JOB->warnings && type == MSG_WARNING)
|
|
return;
|
|
|
|
lock = COMPILE_lock_file(".gbc.stderr");
|
|
|
|
va_start(args, msg);
|
|
|
|
if (line < 0)
|
|
{
|
|
if (JOB->step == JOB_STEP_READ)
|
|
{
|
|
line = JOB->line;
|
|
col = READ_get_column();
|
|
}
|
|
else if (JOB->step == JOB_STEP_TREE)
|
|
{
|
|
col = TRANS_get_column(&line);
|
|
}
|
|
else if (JOB->step == JOB_STEP_CODE)
|
|
{
|
|
line = JOB->line;
|
|
if (JOB->current)
|
|
col = COMPILE_get_column(JOB->current);
|
|
}
|
|
}
|
|
|
|
if (JOB->name)
|
|
{
|
|
const char *name = FILE_get_name(JOB->name);
|
|
if (line)
|
|
{
|
|
if (line > JOB->max_line && JOB->form)
|
|
{
|
|
name = FILE_get_name(JOB->form);
|
|
fprintf(stderr, "%s:%d: ", name, line - FORM_FIRST_LINE + 1);
|
|
}
|
|
else
|
|
{
|
|
if (col >= 0)
|
|
fprintf(stderr, "%s:%d:%d: ", name, line, col + 1);
|
|
else
|
|
fprintf(stderr, "%s:%d: ", name, line);
|
|
}
|
|
}
|
|
else
|
|
fprintf(stderr, "%s: ", name);
|
|
}
|
|
else
|
|
fprintf(stderr, "gbc" GAMBAS_VERSION_STRING ": ");
|
|
|
|
fprintf(stderr, "%s: ", type ? "warning" : "error");
|
|
|
|
if (msg)
|
|
{
|
|
for (i = 0; i < 4; i++)
|
|
arg[i] = va_arg(args, const char *);
|
|
|
|
ERROR_define(msg, arg);
|
|
fputs(ERROR_info.msg, stderr);
|
|
putc('\n', stderr);
|
|
}
|
|
|
|
COMPILE_unlock_file(lock);
|
|
|
|
va_end(args);
|
|
}
|
|
|
|
|
|
void COMPILE_create_file(FILE **fw, const char *file)
|
|
{
|
|
if (!*fw)
|
|
{
|
|
*fw = fopen(file, "w");
|
|
if (!*fw)
|
|
THROW("Cannot create file: &1: &2", FILE_cat(FILE_get_dir(COMP_project), file, NULL), strerror(errno));
|
|
}
|
|
}
|
|
|
|
|