Skip to content

Commit

Permalink
Add a --linker-flags argument (#221)
Browse files Browse the repository at this point in the history
Co-authored-by: XiaoBaiYun <[email protected]>
  • Loading branch information
Akuli and littlewhitecloud authored Feb 17, 2023
1 parent 405cb8c commit 097dee7
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 11 deletions.
58 changes: 58 additions & 0 deletions examples/x11_window.jou
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# To run this program, you need linux:
#
# $ ./jou --linker-flags "-lX11" examples/x11_window.jou
#

declare usleep(x: int) -> int

struct Display:
_dummy: int
struct XGCValues:
_dummy: int
struct GC:
_dummy: int

declare XOpenDisplay(name: byte*) -> Display*
declare XCreateSimpleWindow(
display: Display*,
parent: long,
x: int,
y: int,
width: int,
height: int,
border_width: int,
border: long,
background: long,
) -> long
declare XCreateGC(display: Display*, drawable: long, valuemask: long, values: XGCValues*) -> GC*
declare XSetForeground(display: Display*, gc: GC*, foreground: long) -> int
declare XSelectInput(display: Display*, window: long, event_mask: long) -> int
declare XMapRaised(display: Display*, window: long) -> int
declare XDrawImageString(
display: Display*,
drawable: long,
gc: GC*,
x: int,
y: int,
string: byte*,
length: int,
) -> int
declare XDefaultRootWindow(display: Display*) -> long
declare XStoreName(display: Display*, window: long, name: byte*) -> int
declare XFlush(display: Display*) -> int

def main() -> int:
display = XOpenDisplay("")
window = XCreateSimpleWindow(display, XDefaultRootWindow(display), 200, 200, 200, 200, 5, 0, 0xff00ffL)
XStoreName(display, window, "Hello pink world")

gc = XCreateGC(display, window, 0, NULL)
XSetForeground(display, gc, 0xffffffL)

XSelectInput(display, window, 32768)
XMapRaised(display, window)

while True:
usleep(100)
XDrawImageString(display, window, gc, 50, 50, "hello", 5)
XFlush(display) # This makes the program die when closed. Don't know why :)
4 changes: 3 additions & 1 deletion runtests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,10 @@ function run_test()
# Skip tests when:
# * the test is supposed to crash, but optimizations are enabled (unpredictable by design)
# * the test is supposed to fail (crash or otherwise) and we use valgrind (see README)
# * the "test" is actually a GUI program in examples/
if ( [[ "$command_template" =~ -O[1-3] ]] && [[ $joufile =~ ^tests/crash/ ]] ) \
|| ( [[ "$command_template" =~ valgrind ]] && [ $correct_exit_code != 0 ] )
|| ( [[ "$command_template" =~ valgrind ]] && [ $correct_exit_code != 0 ] ) \
|| [ $joufile = examples/x11_window.jou ]
then
show_skip $joufile
mv $diffpath $diffpath.skip
Expand Down
1 change: 1 addition & 0 deletions src/jou_compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ struct CommandLineFlags {
bool verbose; // Whether to print a LOT of debug info
int optlevel; // Optimization level (0 don't optimize, 3 optimize a lot)
const char *outfile; // If not NULL, where to output executable
const char *linker_flags; // String that is appended to linking command
};

struct Location {
Expand Down
14 changes: 13 additions & 1 deletion src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,15 @@ static void optimize(LLVMModuleRef module, int level)

static const char help_fmt[] =
"Usage:\n"
" <argv0> [-o OUTFILE] [-O0|-O1|-O2|-O3] [--verbose] FILENAME\n"
" <argv0> [-o OUTFILE] [-O0|-O1|-O2|-O3] [--verbose] [--linker-flags \"...\"] FILENAME\n"
" <argv0> --help # This message\n"
" <argv0> --update # Download and install the latest Jou\n"
"\n"
"Options:\n"
" -o OUTFILE output an executable file, don't run the code\n"
" -O0/-O1/-O2/-O3 set optimization level (0 = default, 3 = runs fastest)\n"
" --verbose display a lot of information about all compilation steps\n"
" --linker-flags appended to the linker command, so you can use external libraries\n"
;

static void parse_arguments(int argc, char **argv, CommandLineFlags *flags, const char **filename)
Expand Down Expand Up @@ -75,6 +76,17 @@ static void parse_arguments(int argc, char **argv, CommandLineFlags *flags, cons
} else if (!strcmp(argv[i], "--verbose")) {
flags->verbose = true;
i++;
} else if (!strcmp(argv[i], "--linker-flags")) {
if (flags->linker_flags) {
fprintf(stderr, "%s: --linker-flags cannot be given multiple times", argv[0]);
goto wrong_usage;
}
if (argc-i < 2) {
fprintf(stderr, "%s: there must be a string of flags after --linker-flags", argv[0]);
goto wrong_usage;
}
flags->linker_flags = argv[i+1];
i += 2;
} else if (strlen(argv[i]) == 3
&& !strncmp(argv[i], "-O", 2)
&& argv[i][2] >= '0'
Expand Down
41 changes: 33 additions & 8 deletions src/run.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "jou_compiler.h"
#include <libgen.h>
#include <stdarg.h>
#include <errno.h>
#include <sys/stat.h>
#include <llvm-c/TargetMachine.h>
Expand Down Expand Up @@ -67,30 +68,53 @@ static void compile_to_object_file(LLVMModuleRef module, const char *path, const
LLVMDisposeTargetMachine(machine);
}

static char *malloc_sprintf(const char *fmt, ...)
{
int size;

va_list ap;
va_start(ap, fmt);
size = vsnprintf(NULL, 0, fmt, ap);
va_end(ap);

assert(size >= 0);
char *str = malloc(size+1);
assert(str);

va_start(ap, fmt);
vsprintf(str, fmt, ap);
va_end(ap);

return str;
}

static void run_linker(const char *objpath, const char *exepath, const CommandLineFlags *flags)
{
char *jou_exe = find_current_executable();
const char *instdir = dirname(jou_exe);

#ifdef _WIN32
char *command = malloc(strlen(instdir) + strlen(objpath) + strlen(exepath) + 500);
char *linker_flags;
if (flags->linker_flags)
linker_flags = malloc_sprintf("-lm %s", flags->linker_flags);
else
linker_flags = strdup("-lm");

char *gcc = malloc(strlen(instdir) + 50);
sprintf(gcc, "%s\\mingw64\\bin\\gcc.exe", instdir);
char *command;
#ifdef _WIN32
char *gcc = malloc_sprintf("%s\\mingw64\\bin\\gcc.exe", instdir);
if (stat(gcc, &(struct stat){0}) != -1) {
// The Windows builds come with the GNU linker.
// Windows quoting is weird, in this command it strips the outermost quotes.
sprintf(command, "\"\"%s\" \"%s\" -o \"%s\" -lm\"", gcc, objpath, exepath);
command = malloc_sprintf("\"\"%s\" \"%s\" -o \"%s\" %s\"", gcc, objpath, exepath, linker_flags);
} else {
// Use clang from PATH. Convenient when developing Jou locally.
sprintf(command, "clang \"%s\" -o \"%s\" -lm", objpath, exepath);
command = malloc_sprintf("clang \"%s\" -o \"%s\" %s", objpath, exepath, linker_flags);
}
free(gcc);
#else
// Assume clang is installed and use it to link. Could use lld, but clang is needed anyway.
(void)instdir;
char *command = malloc(strlen(JOU_CLANG_PATH) + strlen(objpath) + strlen(exepath) + 500);
sprintf(command, "'%s' '%s' -o '%s' -lm", JOU_CLANG_PATH, objpath, exepath);
command = malloc_sprintf("'%s' '%s' -o '%s' %s", JOU_CLANG_PATH, objpath, exepath, linker_flags);
#endif

if (flags->verbose)
Expand All @@ -100,6 +124,7 @@ static void run_linker(const char *objpath, const char *exepath, const CommandLi

free(jou_exe);
free(command);
free(linker_flags);
}

static char *get_filename_without_suffix(const LLVMModuleRef module)
Expand Down
12 changes: 11 additions & 1 deletion tests/should_succeed/compiler_cli.jou
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from "stdlib/str.jou" import sprintf, strstr
from "stdlib/mem.jou" import malloc, free
from "stdlib/process.jou" import system, getenv
from "stdlib/io.jou" import printf

def is_windows() -> bool:
return getenv("OS") != NULL and strstr(getenv("OS"), "Windows") != NULL
Expand All @@ -27,16 +28,19 @@ def main() -> int:
run_jou("-O8 examples/hello.jou") # Output: <jouexe>: unknown argument "-O8" (try "<jouexe> --help")
run_jou("--lolwat") # Output: <jouexe>: unknown argument "--lolwat" (try "<jouexe> --help")
run_jou("lolwat.jou") # Output: compiler error in file "lolwat.jou": cannot open file: No such file or directory
run_jou("--linker-flags") # Output: <jouexe>: there must be a string of flags after --linker-flags (try "<jouexe> --help")
run_jou("--linker-flags x --linker-flags y") # Output: <jouexe>: --linker-flags cannot be given multiple times (try "<jouexe> --help")

# Output: Usage:
# Output: <jouexe> [-o OUTFILE] [-O0|-O1|-O2|-O3] [--verbose] FILENAME
# Output: <jouexe> [-o OUTFILE] [-O0|-O1|-O2|-O3] [--verbose] [--linker-flags "..."] FILENAME
# Output: <jouexe> --help # This message
# Output: <jouexe> --update # Download and install the latest Jou
# Output:
# Output: Options:
# Output: -o OUTFILE output an executable file, don't run the code
# Output: -O0/-O1/-O2/-O3 set optimization level (0 = default, 3 = runs fastest)
# Output: --verbose display a lot of information about all compilation steps
# Output: --linker-flags appended to the linker command, so you can use external libraries
run_jou("--help")

# Test that --verbose kinda works, without asserting the output in too much detail.
Expand Down Expand Up @@ -80,4 +84,10 @@ def main() -> int:
system("cp jou tmp/tests/jou_executable")
system("tmp/tests/jou_executable")

# Compile a GUI program
if not is_windows():
ret = system("./jou -o /dev/null --linker-flags \"-lX11\" examples/x11_window.jou")
if ret != 0:
printf("Compiling failed???\n")

return 0

0 comments on commit 097dee7

Please sign in to comment.