Major refactoring (and overcomplicating) of compiler and messages to prepare for threaded dynamic code.
This commit is contained in:
parent
cc6eb18baf
commit
721b23d584
8 changed files with 351 additions and 177 deletions
|
@ -24,7 +24,57 @@
|
||||||
#define COMPILER_H
|
#define COMPILER_H
|
||||||
|
|
||||||
|
|
||||||
int compilerRunRecipe(char *recipe, char *input, char *outputPath, void *context);
|
#include "libtcc.h"
|
||||||
|
#include "sigsegv.h"
|
||||||
|
|
||||||
|
#if HAVE_SIGSEGV_RECOVERY
|
||||||
|
#include <setjmp.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#if defined _WIN32 && !defined __CYGWIN__
|
||||||
|
// Windows doesn't have sigset_t.
|
||||||
|
typedef int sigset_t;
|
||||||
|
#define sigemptyset(set)
|
||||||
|
#define sigprocmask(how,set,oldset)
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
enum CompilerErrorsE {
|
||||||
|
COMPILER_ERROR_NONE = 0,
|
||||||
|
COMPILER_ERROR_ALREADY_RUNNING,
|
||||||
|
COMPILER_ERROR_IN_CODE,
|
||||||
|
COMPILER_ERROR_CANNOT_RELOCATE,
|
||||||
|
COMPILER_ERROR_NO_ENTRYPOINT,
|
||||||
|
COMPILER_ERROR_SIGHANDLER_FAILED,
|
||||||
|
COMPILER_ERROR_SEGFAULT,
|
||||||
|
COMPILER_ERROR_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct CompilerContextS;
|
||||||
|
|
||||||
|
|
||||||
|
typedef void (*CompilerCallback)(struct CompilerContextS **context);
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct CompilerContextS {
|
||||||
|
TCCState *s;
|
||||||
|
int compilerResult;
|
||||||
|
int programResult;
|
||||||
|
CompilerCallback callback;
|
||||||
|
void *userData;
|
||||||
|
#if HAVE_SIGSEGV_RECOVERY
|
||||||
|
volatile int runPass;
|
||||||
|
jmp_buf runJump;
|
||||||
|
sigset_t runSigSet;
|
||||||
|
#endif
|
||||||
|
} CompilerContextT;
|
||||||
|
|
||||||
|
|
||||||
|
void compilerDeleteContext(CompilerContextT **context);
|
||||||
|
gboolean compilerHadError(CompilerContextT **context);
|
||||||
|
CompilerContextT *compilerNewContext(CompilerCallback callback, void *userData);
|
||||||
|
void compilerRunRecipe(CompilerContextT *context, char *recipe, char *input, char *outputPath);
|
||||||
|
|
||||||
|
|
||||||
#endif // COMPILER_H
|
#endif // COMPILER_H
|
||||||
|
|
|
@ -35,6 +35,8 @@ typedef enum MessageTypesE MessageTypesT;
|
||||||
|
|
||||||
|
|
||||||
void message(MessageTypesT level, char *format, ...);
|
void message(MessageTypesT level, char *format, ...);
|
||||||
|
void messageShutdown(void);
|
||||||
|
void messageStartup(void);
|
||||||
|
|
||||||
|
|
||||||
#endif //MESSAGES_H
|
#endif // MESSAGES_H
|
||||||
|
|
215
src/compiler.c
215
src/compiler.c
|
@ -20,10 +20,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "compiler.h"
|
#include "compiler.h"
|
||||||
#include "libtcc.h"
|
|
||||||
#include "sigsegv.h"
|
|
||||||
#include "project.h"
|
#include "project.h"
|
||||||
#include "messages.h"
|
#include "messages.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
@ -31,156 +31,214 @@
|
||||||
|
|
||||||
|
|
||||||
#if HAVE_SIGSEGV_RECOVERY
|
#if HAVE_SIGSEGV_RECOVERY
|
||||||
#include <setjmp.h>
|
|
||||||
#include <signal.h>
|
|
||||||
|
|
||||||
#if defined _WIN32 && !defined __CYGWIN__
|
|
||||||
// Windows doesn't have sigset_t.
|
|
||||||
typedef int sigset_t;
|
|
||||||
#define sigemptyset(set)
|
|
||||||
#define sigprocmask(how,set,oldset)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
volatile int runPass = 0;
|
|
||||||
jmp_buf runRecipe;
|
|
||||||
sigset_t runSigSet;
|
|
||||||
|
|
||||||
static void sigHandlerContinuation(void *arg1, void *arg2, void *arg3);
|
static void sigHandlerContinuation(void *arg1, void *arg2, void *arg3);
|
||||||
int sigHandler(void *faultAddress, int serious);
|
int sigHandler(void *faultAddress, int serious);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
static CompilerContextT *_currentRunningContext = NULL; // Right now we can only have one compile running at a time.
|
||||||
|
|
||||||
char **___recipeTargets = NULL;
|
char **___recipeTargets = NULL;
|
||||||
|
|
||||||
|
|
||||||
static void compilerErrorHandler(void *opaque, const char *msg);
|
static void compilerErrorHandler(void *opaque, const char *msg);
|
||||||
void recipeAddTarget(char *target);
|
|
||||||
void recipeMessage(char *format, ...);
|
// Exposed to dynamic code. Not static.
|
||||||
|
void recipeAddTarget(char *target);
|
||||||
|
void recipeMessage(char *format, ...);
|
||||||
|
|
||||||
|
|
||||||
static void compilerErrorHandler(void *opaque, const char *msg) {
|
void compilerDeleteContext(CompilerContextT **context) {
|
||||||
char *isWarning = strstr(msg, " warning: ");
|
CompilerContextT *c = *context;
|
||||||
|
tcc_delete(c->s);
|
||||||
(void)opaque;
|
DEL(c);
|
||||||
|
|
||||||
message(isWarning == NULL ? MSG_ERROR : MSG_WARN, "%s", msg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// All the paths passed in here are expected to be complete and absolute.
|
static void compilerErrorHandler(void *opaque, const char *msg) {
|
||||||
int compilerRunRecipe(char *recipe, char *input, char *outputPath, void *context) {
|
(void)opaque;
|
||||||
TCCState *s;
|
|
||||||
char *oldLocation;
|
|
||||||
char c;
|
|
||||||
int x;
|
|
||||||
int result = -255;
|
|
||||||
int (*entry)(char *, char *);
|
|
||||||
|
|
||||||
___recipeTargets = NULL;
|
message(strstr(msg, " warning: ") == NULL ? MSG_ERROR : MSG_WARN, "%s", msg);
|
||||||
|
}
|
||||||
|
|
||||||
s = tcc_new();
|
|
||||||
if (!s) {
|
gboolean compilerHadError(CompilerContextT **context) {
|
||||||
|
CompilerContextT *c = *context;
|
||||||
|
|
||||||
|
if (c->compilerResult != COMPILER_ERROR_NONE) {
|
||||||
|
// Handle errors not handled elsewhere.
|
||||||
|
switch (c->compilerResult) {
|
||||||
|
case COMPILER_ERROR_ALREADY_RUNNING:
|
||||||
|
message(MSG_ERROR, "Compiled code is already running");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case COMPILER_ERROR_CANNOT_RELOCATE:
|
||||||
|
message(MSG_ERROR, "Unable to link compiled code");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case COMPILER_ERROR_NO_ENTRYPOINT:
|
||||||
|
message(MSG_ERROR, "Compiled code is missing entrypoint");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case COMPILER_ERROR_SIGHANDLER_FAILED:
|
||||||
|
message(MSG_ERROR, "Unable to set up sigsegv handler");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CompilerContextT *compilerNewContext(CompilerCallback callback, void *userData) {
|
||||||
|
CompilerContextT *c;
|
||||||
|
int x;
|
||||||
|
char ch;
|
||||||
|
|
||||||
|
c = NEW(CompilerContextT);
|
||||||
|
|
||||||
|
c->compilerResult = COMPILER_ERROR_NONE;
|
||||||
|
c->programResult = 0;
|
||||||
|
c->callback = callback;
|
||||||
|
c->userData = userData;
|
||||||
|
|
||||||
|
c->s = tcc_new();
|
||||||
|
if (!c->s) {
|
||||||
|
DEL(c);
|
||||||
// Something bad happened.
|
// Something bad happened.
|
||||||
return -1;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// __resourcePath comes in with a trailing slash. Temporarily remove it.
|
// __resourcePath comes in with a trailing slash. Temporarily remove it.
|
||||||
x = (int)strlen(__resourcePath) - 1;
|
x = (int)strlen(__resourcePath) - 1;
|
||||||
c = __resourcePath[x];
|
ch = __resourcePath[x];
|
||||||
__resourcePath[x] = 0;
|
__resourcePath[x] = 0;
|
||||||
tcc_set_lib_path(s, __resourcePath);
|
tcc_set_lib_path(c->s, __resourcePath);
|
||||||
tcc_add_sysinclude_path(s, __resourcePath);
|
tcc_add_sysinclude_path(c->s, __resourcePath);
|
||||||
__resourcePath[x] = c;
|
__resourcePath[x] = ch;
|
||||||
|
|
||||||
tcc_set_options(s, "-Wall -Wno-write-strings");
|
tcc_set_options(c->s, "-Wall -Wno-write-strings"); // Debug options not working right: -g -bt 5
|
||||||
tcc_set_error_func(s, stdout, compilerErrorHandler);
|
tcc_set_error_func(c->s, NULL, compilerErrorHandler);
|
||||||
tcc_set_output_type(s, TCC_OUTPUT_MEMORY);
|
tcc_set_output_type(c->s, TCC_OUTPUT_MEMORY);
|
||||||
|
|
||||||
if (tcc_add_file(s, recipe) < 0) {
|
tcc_add_symbol(c->s, "fputc", fputc);
|
||||||
|
tcc_add_symbol(c->s, "qsort", qsort);
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void compilerRunRecipe(CompilerContextT *context, char *recipe, char *input, char *outputPath) {
|
||||||
|
char *oldLocation;
|
||||||
|
int (*entry)(char *, char *);
|
||||||
|
void *self;
|
||||||
|
|
||||||
|
// All the paths passed in here are expected to be complete and absolute.
|
||||||
|
|
||||||
|
//***TODO*** Oh my god this is so bad. The next line depends on 'userData' being set to 'self' when called from project.c.
|
||||||
|
self = context->userData;
|
||||||
|
|
||||||
|
if (_currentRunningContext != NULL) {
|
||||||
|
context->compilerResult = COMPILER_ERROR_ALREADY_RUNNING;
|
||||||
|
context->callback(&context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
___recipeTargets = NULL;
|
||||||
|
|
||||||
|
if (tcc_add_file(context->s, recipe) < 0) {
|
||||||
// Errors in code.
|
// Errors in code.
|
||||||
tcc_delete(s);
|
context->compilerResult = COMPILER_ERROR_IN_CODE;
|
||||||
return -4;
|
context->callback(&context);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tcc_add_symbol(s, "fputc", fputc);
|
tcc_add_symbol(context->s, "___recipeTargets", ___recipeTargets);
|
||||||
tcc_add_symbol(s, "qsort", qsort);
|
tcc_add_symbol(context->s, "recipeAddTarget", recipeAddTarget);
|
||||||
|
tcc_add_symbol(context->s, "recipeMessage", recipeMessage);
|
||||||
|
tcc_add_symbol(context->s, "utilCreateString", utilCreateString);
|
||||||
|
|
||||||
tcc_add_symbol(s, "___recipeTargets", ___recipeTargets);
|
if (tcc_relocate(context->s, TCC_RELOCATE_AUTO) < 0) {
|
||||||
tcc_add_symbol(s, "recipeAddTarget", recipeAddTarget);
|
|
||||||
tcc_add_symbol(s, "recipeMessage", recipeMessage);
|
|
||||||
tcc_add_symbol(s, "utilCreateString", utilCreateString);
|
|
||||||
|
|
||||||
if (tcc_relocate(s, TCC_RELOCATE_AUTO) < 0) {
|
|
||||||
// Something bad happened.
|
// Something bad happened.
|
||||||
tcc_delete(s);
|
context->compilerResult = COMPILER_ERROR_CANNOT_RELOCATE;
|
||||||
return -2;
|
context->callback(&context);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
entry = tcc_get_symbol(s, "recipe");
|
entry = tcc_get_symbol(context->s, "recipe");
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
// Something bad happened.
|
// Something bad happened.
|
||||||
tcc_delete(s);
|
context->compilerResult = COMPILER_ERROR_NO_ENTRYPOINT;
|
||||||
return -3;
|
context->callback(&context);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
getcwd(__utilFilenameBuffer, FILENAME_MAX);
|
getcwd(__utilFilenameBuffer, FILENAME_MAX);
|
||||||
oldLocation = strdup(__utilFilenameBuffer);
|
oldLocation = strdup(__utilFilenameBuffer);
|
||||||
chdir(outputPath);
|
chdir(outputPath);
|
||||||
|
|
||||||
|
_currentRunningContext = context;
|
||||||
|
|
||||||
#if HAVE_SIGSEGV_RECOVERY
|
#if HAVE_SIGSEGV_RECOVERY
|
||||||
sigset_t emptySet;
|
sigset_t emptySet;
|
||||||
|
|
||||||
runPass = 0;
|
context->runPass = 0;
|
||||||
|
|
||||||
if (sigsegv_install_handler(&sigHandler) < 0)
|
if (sigsegv_install_handler(&sigHandler) < 0) {
|
||||||
return -5;
|
context->compilerResult = COMPILER_ERROR_SIGHANDLER_FAILED;
|
||||||
|
context->callback(&context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
sigemptyset(&emptySet);
|
sigemptyset(&emptySet);
|
||||||
sigprocmask(SIG_BLOCK, &emptySet, &runSigSet);
|
sigprocmask(SIG_BLOCK, &emptySet, &context->runSigSet);
|
||||||
|
|
||||||
switch (setjmp(runRecipe)) {
|
switch (setjmp(context->runJump)) {
|
||||||
case 0:
|
case 0:
|
||||||
result = entry(input, outputPath);
|
context->programResult = entry(input, outputPath);
|
||||||
case 1:
|
case 1:
|
||||||
sigprocmask(SIG_SETMASK, &runSigSet, NULL);
|
sigprocmask(SIG_SETMASK, &context->runSigSet, NULL);
|
||||||
sigsegv_install_handler(NULL);
|
sigsegv_install_handler(NULL);
|
||||||
if (runPass != 0) {
|
if (context->runPass != 0) {
|
||||||
message(MSG_SEVERE, "%s caused a segmentation fault!", recipe);
|
message(MSG_SEVERE, "%s caused a segmentation fault!", recipe);
|
||||||
|
context->compilerResult = COMPILER_ERROR_SEGFAULT;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
context->callback(&context);
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
result = entry(input, outputPath);
|
context->programResult = entry(input, outputPath);
|
||||||
|
context->callback(&context);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
_currentRunningContext = NULL;
|
||||||
|
|
||||||
chdir(oldLocation);
|
chdir(oldLocation);
|
||||||
DEL(oldLocation);
|
DEL(oldLocation);
|
||||||
|
|
||||||
while (arrlen(___recipeTargets) > 0) {
|
while (arrlen(___recipeTargets) > 0) {
|
||||||
if (projectAddToTree(context, ___recipeTargets[0])) {
|
if (projectAddToTree(self, ___recipeTargets[0])) {
|
||||||
utilSetDirty((WindowDataT *)context, TRUE);
|
utilSetDirty((WindowDataT *)context, TRUE);
|
||||||
}
|
}
|
||||||
DEL(___recipeTargets[0]);
|
DEL(___recipeTargets[0]);
|
||||||
arrdel(___recipeTargets, 0);
|
arrdel(___recipeTargets, 0);
|
||||||
}
|
}
|
||||||
ARRFREE(___recipeTargets);
|
ARRFREE(___recipeTargets);
|
||||||
|
|
||||||
tcc_delete(s);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#if HAVE_SIGSEGV_RECOVERY
|
#if HAVE_SIGSEGV_RECOVERY
|
||||||
static void sigHandlerContinuation(void *arg1, void *arg2, void *arg3) {
|
static void sigHandlerContinuation(void *arg1, void *arg2, void *arg3) {
|
||||||
(void)arg1;
|
CompilerContextT *c = (CompilerContextT *)arg1;
|
||||||
|
|
||||||
(void)arg2;
|
(void)arg2;
|
||||||
(void)arg3;
|
(void)arg3;
|
||||||
longjmp(runRecipe, runPass);
|
|
||||||
|
longjmp(c->runJump, c->runPass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -188,11 +246,12 @@ int sigHandler(void *faultAddress, int serious) {
|
||||||
(void)faultAddress;
|
(void)faultAddress;
|
||||||
(void)serious;
|
(void)serious;
|
||||||
|
|
||||||
runPass++;
|
_currentRunningContext->runPass++;
|
||||||
return sigsegv_leave_handler(sigHandlerContinuation, NULL, NULL, NULL);
|
return sigsegv_leave_handler(sigHandlerContinuation, _currentRunningContext, NULL, NULL);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
void recipeAddTarget(char *target) {
|
void recipeAddTarget(char *target) {
|
||||||
arrput(___recipeTargets, strdup(target));
|
arrput(___recipeTargets, strdup(target));
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
#include "ssh.h"
|
#include "ssh.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "cwalk.h"
|
#include "cwalk.h"
|
||||||
|
#include "messages.h"
|
||||||
|
|
||||||
|
|
||||||
char *__resourcePath = NULL;
|
char *__resourcePath = NULL;
|
||||||
|
@ -47,6 +48,7 @@ int main(int argc, char **argv) {
|
||||||
|
|
||||||
gtk_init(&argc, &argv);
|
gtk_init(&argc, &argv);
|
||||||
utilStartup();
|
utilStartup();
|
||||||
|
messageStartup();
|
||||||
|
|
||||||
// Make sure we have a config & resources folder.
|
// Make sure we have a config & resources folder.
|
||||||
__resourcePath = utilCreateString("%s%cjoeydev%cresources%c", g_get_user_config_dir(), UTIL_PATH_CHAR, UTIL_PATH_CHAR, UTIL_PATH_CHAR);
|
__resourcePath = utilCreateString("%s%cjoeydev%cresources%c", g_get_user_config_dir(), UTIL_PATH_CHAR, UTIL_PATH_CHAR, UTIL_PATH_CHAR);
|
||||||
|
@ -79,6 +81,7 @@ int main(int argc, char **argv) {
|
||||||
scintilla_release_resources();
|
scintilla_release_resources();
|
||||||
sshShutdown();
|
sshShutdown();
|
||||||
httpShutdown();
|
httpShutdown();
|
||||||
|
messageShutdown();
|
||||||
utilShutdown();
|
utilShutdown();
|
||||||
|
|
||||||
DEL(__resourcePath);
|
DEL(__resourcePath);
|
||||||
|
|
159
src/messages.c
159
src/messages.c
|
@ -20,21 +20,74 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "messages.h"
|
#include "messages.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
|
|
||||||
static GtkWidget *_lstMessages = NULL;
|
static GtkWidget *_lstMessages = NULL;
|
||||||
static int _autoScroll = 0;
|
static char **_pendingMessages = NULL;
|
||||||
|
static pthread_mutex_t _mtxMessage;
|
||||||
|
|
||||||
|
|
||||||
static gboolean messagesScroll(gpointer userData);
|
static gboolean messagesUpdate(gpointer userData);
|
||||||
EVENT gboolean winMessagesClose(GtkWidget *object, gpointer userData);
|
EVENT gboolean winMessagesClose(GtkWidget *object, gpointer userData);
|
||||||
static void winMessagesDelete(gpointer userData);
|
static void winMessagesDelete(gpointer userData);
|
||||||
|
|
||||||
|
|
||||||
void message(MessageTypesT level, char *format, ...) {
|
void message(MessageTypesT level, char *format, ...) {
|
||||||
|
va_list args;
|
||||||
|
char *string;
|
||||||
|
char *msg;
|
||||||
|
char *tok;
|
||||||
|
char *labels[MSG_COUNT] = {
|
||||||
|
"<span foreground=\"gray\"> Info:</span>",
|
||||||
|
"<span foreground=\"yellow\">Warning:</span>",
|
||||||
|
"<span foreground=\"red\"> Error:</span>",
|
||||||
|
"<span foreground=\"red\"><b> Severe:</b></span>"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Construct message.
|
||||||
|
va_start(args, format);
|
||||||
|
msg = utilCreateStringVArgs(format, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
// Break multiline messages down into individual lines.
|
||||||
|
tok = strtok(msg, "\n");
|
||||||
|
while (tok != NULL) {
|
||||||
|
//***TODO*** Filter out things that could be mistaken as markup tags.
|
||||||
|
string = utilCreateString("<tt>%s %s</tt>", labels[level], tok);
|
||||||
|
|
||||||
|
// Save for UI thread. message() is not always called from the UI thread!
|
||||||
|
pthread_mutex_lock(&_mtxMessage);
|
||||||
|
arrput(_pendingMessages, string); // Do not free string.
|
||||||
|
pthread_mutex_unlock(&_mtxMessage);
|
||||||
|
|
||||||
|
tok = strtok(NULL, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void messageShutdown(void) {
|
||||||
|
g_idle_remove_by_data(messagesUpdate);
|
||||||
|
pthread_mutex_destroy(&_mtxMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void messageStartup(void) {
|
||||||
|
if (pthread_mutex_init(&_mtxMessage, NULL) != 0) {
|
||||||
|
debug("Message mutex init failed!\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up updates on the UI thread.
|
||||||
|
g_idle_add(messagesUpdate, messagesUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static gboolean messagesUpdate(gpointer userData) {
|
||||||
WindowDataT *self = NULL;
|
WindowDataT *self = NULL;
|
||||||
char *widgetNames[] = {
|
char *widgetNames[] = {
|
||||||
"winMessages",
|
"winMessages",
|
||||||
|
@ -48,83 +101,59 @@ void message(MessageTypesT level, char *format, ...) {
|
||||||
GtkWidget *row;
|
GtkWidget *row;
|
||||||
GtkWidget *box;
|
GtkWidget *box;
|
||||||
GtkWidget *label;
|
GtkWidget *label;
|
||||||
va_list args;
|
GtkAdjustment *adjustment;
|
||||||
char *string;
|
char *string = NULL;
|
||||||
char *msg;
|
|
||||||
char *tok;
|
|
||||||
char *labels[MSG_COUNT] = {
|
|
||||||
"<span foreground=\"gray\"> Info:</span>",
|
|
||||||
"<span foreground=\"yellow\">Warning:</span>",
|
|
||||||
"<span foreground=\"red\"> Error:</span>",
|
|
||||||
"<span foreground=\"red\"><b> Severe:</b></span>"
|
|
||||||
};
|
|
||||||
|
|
||||||
// Do we need to open this window?
|
(void)userData;
|
||||||
if (!_lstMessages) {
|
|
||||||
// Set up instance data. We only need WindowDataT since this is a "singleton" window.
|
|
||||||
self = NEW(WindowDataT);
|
|
||||||
self->closeWindow = winMessagesClose;
|
|
||||||
|
|
||||||
widgets[0] = &self->window;
|
// Lock and access the pending message list.
|
||||||
utilGetWidgetsFromMemory("/com/kangaroopunch/joeydev/Messages.glade", widgetNames, widgets, self);
|
pthread_mutex_lock(&_mtxMessage);
|
||||||
|
if (arrlen(_pendingMessages) > 0) {
|
||||||
// Register window.
|
string = _pendingMessages[0]; // We just need the pointer.
|
||||||
utilWindowRegister(self);
|
arrdel(_pendingMessages, 0);
|
||||||
|
|
||||||
// Show window.
|
|
||||||
gtk_widget_show_all(self->window);
|
|
||||||
|
|
||||||
// Set up automatic scrolling.
|
|
||||||
g_idle_add(messagesScroll, messagesScroll);
|
|
||||||
}
|
}
|
||||||
|
pthread_mutex_unlock(&_mtxMessage);
|
||||||
|
|
||||||
// Display message.
|
// Are there pending messages?
|
||||||
va_start(args, format);
|
if (string != NULL) {
|
||||||
msg = utilCreateStringVArgs(format, args);
|
|
||||||
va_end(args);
|
|
||||||
|
|
||||||
// Break multiline messages down into individual lines.
|
// Do we need to open the message window?
|
||||||
tok = strtok(msg, "\n");
|
if (!_lstMessages) {
|
||||||
while (tok != NULL) {
|
// Set up instance data. We only need WindowDataT since this is a "singleton" window.
|
||||||
//***TODO*** Filter out things that could be mistaken as markup tags.
|
self = NEW(WindowDataT);
|
||||||
string = utilCreateString("<tt>%s %s</tt>", labels[level], tok);
|
self->closeWindow = winMessagesClose;
|
||||||
|
|
||||||
|
widgets[0] = &self->window;
|
||||||
|
utilGetWidgetsFromMemory("/com/kangaroopunch/joeydev/Messages.glade", widgetNames, widgets, self);
|
||||||
|
|
||||||
|
// Register window.
|
||||||
|
utilWindowRegister(self);
|
||||||
|
|
||||||
|
// Show window.
|
||||||
|
gtk_widget_show_all(self->window);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new row with the message string.
|
||||||
row = gtk_list_box_row_new();
|
row = gtk_list_box_row_new();
|
||||||
box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
|
box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
|
||||||
gtk_widget_set_hexpand(box, TRUE);
|
gtk_widget_set_hexpand(box, TRUE);
|
||||||
label = gtk_label_new(string);
|
label = gtk_label_new(string);
|
||||||
gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
|
gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
|
||||||
|
|
||||||
DEL(string);
|
// Add new row to the message box.
|
||||||
|
|
||||||
gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 0);
|
gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 0);
|
||||||
gtk_container_add(GTK_CONTAINER(row), box);
|
gtk_container_add(GTK_CONTAINER(row), box);
|
||||||
gtk_list_box_insert(GTK_LIST_BOX(_lstMessages), row, -1);
|
gtk_list_box_insert(GTK_LIST_BOX(_lstMessages), row, -1);
|
||||||
|
|
||||||
// Force paint.
|
// Scroll to show new row.
|
||||||
gtk_widget_show_all(row);
|
//***TODO*** This doesn't always scroll to the very end.
|
||||||
|
adjustment = gtk_list_box_get_adjustment(GTK_LIST_BOX(_lstMessages));
|
||||||
|
gtk_adjustment_set_value(adjustment, gtk_adjustment_get_upper(adjustment));
|
||||||
|
gtk_widget_show_all(_lstMessages);
|
||||||
utilForceUpdate();
|
utilForceUpdate();
|
||||||
_autoScroll = 15; // Try several times to show the new row.
|
|
||||||
|
|
||||||
tok = strtok(NULL, "\n");
|
DEL(string); // Finally free the string allocated in message().
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static gboolean messagesScroll(gpointer userData) {
|
|
||||||
GtkAdjustment *adjustment;
|
|
||||||
|
|
||||||
(void)userData;
|
|
||||||
|
|
||||||
if (_autoScroll <= 0) return G_SOURCE_CONTINUE;
|
|
||||||
|
|
||||||
// Scroll to show new line.
|
|
||||||
adjustment = gtk_list_box_get_adjustment(GTK_LIST_BOX(_lstMessages));
|
|
||||||
gtk_adjustment_set_value(adjustment, gtk_adjustment_get_upper(adjustment));
|
|
||||||
gtk_widget_show_all(_lstMessages);
|
|
||||||
utilForceUpdate();
|
|
||||||
|
|
||||||
_autoScroll--;
|
|
||||||
|
|
||||||
return G_SOURCE_CONTINUE;
|
return G_SOURCE_CONTINUE;
|
||||||
}
|
}
|
||||||
|
@ -142,7 +171,13 @@ EVENT gboolean winMessagesClose(GtkWidget *object, gpointer userData) {
|
||||||
|
|
||||||
|
|
||||||
static void winMessagesDelete(gpointer userData) {
|
static void winMessagesDelete(gpointer userData) {
|
||||||
g_idle_remove_by_data(messagesScroll);
|
// Delete any pending messages.
|
||||||
|
while (arrlen(_pendingMessages) > 0) {
|
||||||
|
DEL(_pendingMessages[0]);
|
||||||
|
arrdel(_pendingMessages, 0);
|
||||||
|
}
|
||||||
|
ARRFREE(_pendingMessages);
|
||||||
|
|
||||||
utilWindowUnRegister(userData);
|
utilWindowUnRegister(userData);
|
||||||
_lstMessages = NULL;
|
_lstMessages = NULL;
|
||||||
DEL(userData);
|
DEL(userData);
|
||||||
|
|
|
@ -84,7 +84,8 @@ typedef struct ProjectDataS {
|
||||||
char *buildUser;
|
char *buildUser;
|
||||||
char *buildPassword;
|
char *buildPassword;
|
||||||
TargetT **targets;
|
TargetT **targets;
|
||||||
GtkWidget *tempWidget; // Used to pass data around dialogs.
|
GtkWidget *tempWidget; // Used to pass data around dialogs.
|
||||||
|
int tempInteger; // Used during recipe building.
|
||||||
} ProjectDataT;
|
} ProjectDataT;
|
||||||
|
|
||||||
typedef struct SectionDataS {
|
typedef struct SectionDataS {
|
||||||
|
@ -107,7 +108,6 @@ static SectionDataT _sectionData[] = {
|
||||||
{ NULL, NULL }
|
{ NULL, NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
static ProjectDataT *_cookingProjectData = NULL;
|
|
||||||
static char **_sendList = NULL;
|
static char **_sendList = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
@ -120,6 +120,7 @@ EVENT void btnEditRawClicked(GtkButton *widget, gpointer userData);
|
||||||
EVENT void btnEditRecipeClicked(GtkButton *widget, gpointer userData);
|
EVENT void btnEditRecipeClicked(GtkButton *widget, gpointer userData);
|
||||||
EVENT void btnNewRecipeClicked(GtkButton *widget, gpointer userData);
|
EVENT void btnNewRecipeClicked(GtkButton *widget, gpointer userData);
|
||||||
static void clearRecipeData(ProjectDataT *self);
|
static void clearRecipeData(ProjectDataT *self);
|
||||||
|
static void cookFinished(CompilerContextT **context);
|
||||||
static void decompressBuild(ArchiveT *archive);
|
static void decompressBuild(ArchiveT *archive);
|
||||||
static void dialogCookOptions(char *filename, ProjectDataT *self);
|
static void dialogCookOptions(char *filename, ProjectDataT *self);
|
||||||
static int findRecipeData(ProjectDataT *self, char *key);
|
static int findRecipeData(ProjectDataT *self, char *key);
|
||||||
|
@ -244,6 +245,50 @@ static void clearRecipeData(ProjectDataT *self) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void cookFinished(CompilerContextT **context) {
|
||||||
|
CompilerContextT *ctx = *context;
|
||||||
|
ProjectDataT *self = (ProjectDataT *)ctx->userData;
|
||||||
|
char *raw;
|
||||||
|
char *recipe;
|
||||||
|
|
||||||
|
// Was there a compiler error?
|
||||||
|
if (compilerHadError(context)) {
|
||||||
|
compilerDeleteContext(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is not first call, process context.
|
||||||
|
if (self->tempInteger != -1) {
|
||||||
|
if (ctx->programResult != 0) {
|
||||||
|
//***TODO*** Not all negative returns are severe.
|
||||||
|
message(ctx->programResult > 0 ? MSG_ERROR : MSG_SEVERE, "Recipe %s returned %d", self->recipes[self->tempInteger]->value, ctx->programResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compilerDeleteContext(context);
|
||||||
|
|
||||||
|
// Is there something to cook?
|
||||||
|
self->tempInteger++;
|
||||||
|
if (self->tempInteger < arrlen(self->recipes)) {
|
||||||
|
// Build path names.
|
||||||
|
cwk_path_change_basename(self->windowData.filename, self->recipes[self->tempInteger]->key, __utilFilenameBuffer, sizeof(__utilFilenameBuffer));
|
||||||
|
raw = strdup(__utilFilenameBuffer);
|
||||||
|
cwk_path_change_basename(self->windowData.filename, self->recipes[self->tempInteger]->value, __utilFilenameBuffer, sizeof(__utilFilenameBuffer));
|
||||||
|
recipe = strdup(__utilFilenameBuffer);
|
||||||
|
|
||||||
|
// Run it!
|
||||||
|
message(MSG_INFO, "Cooking %s", self->recipes[self->tempInteger]->key);
|
||||||
|
ctx = compilerNewContext(cookFinished, self);
|
||||||
|
compilerRunRecipe(ctx, recipe, raw, self->windowData.path);
|
||||||
|
DEL(recipe);
|
||||||
|
DEL(raw);
|
||||||
|
} else {
|
||||||
|
// Finished with recipe list.
|
||||||
|
message(MSG_INFO, "Finished Cooking");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void decompressBuild(ArchiveT *archive) {
|
static void decompressBuild(ArchiveT *archive) {
|
||||||
ProjectDataT *self = (ProjectDataT *)archive->userData;
|
ProjectDataT *self = (ProjectDataT *)archive->userData;
|
||||||
char *temp;
|
char *temp;
|
||||||
|
@ -254,6 +299,7 @@ static void decompressBuild(ArchiveT *archive) {
|
||||||
DEL(temp);
|
DEL(temp);
|
||||||
|
|
||||||
//***TODO*** Process build results.
|
//***TODO*** Process build results.
|
||||||
|
message(MSG_INFO, "Processing build results");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -922,39 +968,15 @@ EVENT void menuProjectBuildTargets(GtkWidget *object, gpointer userData) {
|
||||||
|
|
||||||
|
|
||||||
EVENT void menuProjectBuildCookRecipes(GtkWidget *object, gpointer userData) {
|
EVENT void menuProjectBuildCookRecipes(GtkWidget *object, gpointer userData) {
|
||||||
ProjectDataT *self = (ProjectDataT *)userData;
|
ProjectDataT *self = (ProjectDataT *)userData;
|
||||||
int i;
|
CompilerContextT *ctx;
|
||||||
char *raw;
|
|
||||||
char *recipe;
|
|
||||||
int result;
|
|
||||||
|
|
||||||
// Only one cook at a time. Should not be able to happen.
|
// Are there recipes to cook?
|
||||||
if (_cookingProjectData) {
|
if (arrlen(self->recipes) > 0) {
|
||||||
message(MSG_ERROR, "Cook currently running");
|
self->tempInteger = -1; // Tell cookFinished() we're just starting.
|
||||||
return;
|
ctx = compilerNewContext(cookFinished, self);
|
||||||
|
cookFinished(&ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remember who started the cook.
|
|
||||||
_cookingProjectData = self;
|
|
||||||
|
|
||||||
for (i=0; i<arrlen(self->recipes); i++) {
|
|
||||||
// Build pathnames.
|
|
||||||
cwk_path_change_basename(self->windowData.filename, self->recipes[i]->key, __utilFilenameBuffer, sizeof(__utilFilenameBuffer));
|
|
||||||
raw = strdup(__utilFilenameBuffer);
|
|
||||||
cwk_path_change_basename(self->windowData.filename, self->recipes[i]->value, __utilFilenameBuffer, sizeof(__utilFilenameBuffer));
|
|
||||||
recipe = strdup(__utilFilenameBuffer);
|
|
||||||
|
|
||||||
// Run it!
|
|
||||||
message(MSG_INFO, "Cooking %s", self->recipes[i]->key);
|
|
||||||
result = compilerRunRecipe(recipe, raw, self->windowData.path, self);
|
|
||||||
if (result != 0) {
|
|
||||||
//***TODO*** Not all negative returns are severe.
|
|
||||||
message(result > 0 ? MSG_ERROR : MSG_SEVERE, "Recipe %s returned %d", self->recipes[i]->value, result);
|
|
||||||
}
|
|
||||||
message(MSG_INFO, "Finished Cooking");
|
|
||||||
}
|
|
||||||
|
|
||||||
_cookingProjectData = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -975,8 +997,6 @@ EVENT void menuProjectBuildBuild(GtkWidget *object, gpointer userData) {
|
||||||
gboolean archPrinted;
|
gboolean archPrinted;
|
||||||
FILE *out;
|
FILE *out;
|
||||||
|
|
||||||
menuProjectBuildCookRecipes(object, userData);
|
|
||||||
|
|
||||||
ssh = NEW(SSHT);
|
ssh = NEW(SSHT);
|
||||||
ssh = sshConnect(self->buildHost, self->buildSSHPort, self->buildUser, self->buildPassword);
|
ssh = sshConnect(self->buildHost, self->buildSSHPort, self->buildUser, self->buildPassword);
|
||||||
|
|
||||||
|
|
|
@ -118,6 +118,8 @@ SSHT *sshConnect(char *hostname, uint16_t port, char *user, char *password) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//***TODO*** Move SFTP session setup here so we don't keep repeating it.
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,6 +127,8 @@ SSHT *sshConnect(char *hostname, uint16_t port, char *user, char *password) {
|
||||||
void sshDisconnect(SSHT **sshData) {
|
void sshDisconnect(SSHT **sshData) {
|
||||||
SSHT *data = *sshData;
|
SSHT *data = *sshData;
|
||||||
|
|
||||||
|
//***TODO*** Move SFTP session teardown here so we don't keep repeating it.
|
||||||
|
|
||||||
libssh2_session_disconnect(data->session, "Normal Shutdown.");
|
libssh2_session_disconnect(data->session, "Normal Shutdown.");
|
||||||
libssh2_session_free(data->session);
|
libssh2_session_free(data->session);
|
||||||
socketclose(data->sock);
|
socketclose(data->sock);
|
||||||
|
|
|
@ -233,6 +233,7 @@ gboolean utilDecompressUpdate(gpointer userData) {
|
||||||
|
|
||||||
if (toClose >= 0) {
|
if (toClose >= 0) {
|
||||||
a = _activeArchives[toClose];
|
a = _activeArchives[toClose];
|
||||||
|
arrdel(_activeArchives, toClose);
|
||||||
if (a->callback) {
|
if (a->callback) {
|
||||||
a->callback(a);
|
a->callback(a);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue