From 097dee7e16924957777044621d769cf9a697b5e1 Mon Sep 17 00:00:00 2001 From: Akuli Date: Fri, 17 Feb 2023 20:49:26 +0200 Subject: [PATCH] Add a --linker-flags argument (#221) Co-authored-by: XiaoBaiYun <71159641+littlewhitecloud@users.noreply.github.com> --- examples/x11_window.jou | 58 +++++++++++++++++++++++++++ runtests.sh | 4 +- src/jou_compiler.h | 1 + src/main.c | 14 ++++++- src/run.c | 41 +++++++++++++++---- tests/should_succeed/compiler_cli.jou | 12 +++++- 6 files changed, 119 insertions(+), 11 deletions(-) create mode 100644 examples/x11_window.jou diff --git a/examples/x11_window.jou b/examples/x11_window.jou new file mode 100644 index 00000000..1f42f6d2 --- /dev/null +++ b/examples/x11_window.jou @@ -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 :) diff --git a/runtests.sh b/runtests.sh index da0c7800..e208a6a5 100755 --- a/runtests.sh +++ b/runtests.sh @@ -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 diff --git a/src/jou_compiler.h b/src/jou_compiler.h index 199d3cf5..a0a6d3e7 100644 --- a/src/jou_compiler.h +++ b/src/jou_compiler.h @@ -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 { diff --git a/src/main.c b/src/main.c index fb4d3f47..5c2a6372 100644 --- a/src/main.c +++ b/src/main.c @@ -34,7 +34,7 @@ static void optimize(LLVMModuleRef module, int level) static const char help_fmt[] = "Usage:\n" - " [-o OUTFILE] [-O0|-O1|-O2|-O3] [--verbose] FILENAME\n" + " [-o OUTFILE] [-O0|-O1|-O2|-O3] [--verbose] [--linker-flags \"...\"] FILENAME\n" " --help # This message\n" " --update # Download and install the latest Jou\n" "\n" @@ -42,6 +42,7 @@ static const char help_fmt[] = " -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) @@ -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' diff --git a/src/run.c b/src/run.c index f8b3f0a8..694a5327 100644 --- a/src/run.c +++ b/src/run.c @@ -8,6 +8,7 @@ #include "jou_compiler.h" #include +#include #include #include #include @@ -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) @@ -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) diff --git a/tests/should_succeed/compiler_cli.jou b/tests/should_succeed/compiler_cli.jou index 3f40323d..1dfe419b 100644 --- a/tests/should_succeed/compiler_cli.jou +++ b/tests/should_succeed/compiler_cli.jou @@ -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 @@ -27,9 +28,11 @@ def main() -> int: run_jou("-O8 examples/hello.jou") # Output: : unknown argument "-O8" (try " --help") run_jou("--lolwat") # Output: : unknown argument "--lolwat" (try " --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: : there must be a string of flags after --linker-flags (try " --help") + run_jou("--linker-flags x --linker-flags y") # Output: : --linker-flags cannot be given multiple times (try " --help") # Output: Usage: - # Output: [-o OUTFILE] [-O0|-O1|-O2|-O3] [--verbose] FILENAME + # Output: [-o OUTFILE] [-O0|-O1|-O2|-O3] [--verbose] [--linker-flags "..."] FILENAME # Output: --help # This message # Output: --update # Download and install the latest Jou # Output: @@ -37,6 +40,7 @@ def main() -> int: # 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. @@ -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