From 25156eb83ed4ccf6601548407d25c973db132f74 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 4 Sep 2020 17:05:28 -0500 Subject: [PATCH] For #469 - Add support for C++ and mixed C/C++ WIP and for native modules. Required a few changes to headers and some changes to JPM. --- jpm | 118 +++++++++++++++++++++++++++++++----- jpm.1 | 6 +- src/include/janet.h | 17 ++++-- test/install/project.janet | 4 ++ test/install/testexec.janet | 3 +- test/install/testmod3.cpp | 42 +++++++++++++ 6 files changed, 166 insertions(+), 24 deletions(-) create mode 100644 test/install/testmod3.cpp diff --git a/jpm b/jpm index 4d363831..84593b58 100755 --- a/jpm +++ b/jpm @@ -323,7 +323,9 @@ # (def default-compiler (or (os/getenv "CC") (if is-win "cl.exe" "cc"))) +(def default-cpp-compiler (or (os/getenv "CXX") (if is-win "cl.exe" "c++"))) (def default-linker (or (os/getenv "CC") (if is-win "link.exe" "cc"))) +(def default-cpp-linker (or (os/getenv "CXX") (if is-win "link.exe" "c++"))) (def default-archiver (or (os/getenv "AR") (if is-win "lib.exe" "ar"))) # Detect threads @@ -352,6 +354,10 @@ (if is-win ["/nologo" "/MD"] ["-std=c99" "-Wall" "-Wextra"])) +(def default-cppflags + (if is-win + ["/nologo" "/MD"] + ["-std=c++11" "-Wall" "-Wextra"])) (def default-ldflags []) # Required flags for dynamic libraries. These @@ -424,6 +430,13 @@ (string "-I" (dyn :headerpath JANET_HEADERPATH)) (string "-O" (opt opts :optimize 2))]) +(defn- getcppflags + "Generate the cpp flags from the input options." + [opts] + @[;(opt opts :cppflags default-cppflags) + (string "-I" (dyn :headerpath JANET_HEADERPATH)) + (string "-O" (opt opts :optimize 2))]) + (defn- entry-name "Name of symbol that enters static compilation of a module." [name] @@ -441,12 +454,30 @@ (def headers (or (opts :headers) [])) (rule dest [src ;headers] (check-cc) - (print "compiling " dest "...") + (print "compiling " src " to " dest "...") (create-dirs dest) (if is-win (shell cc ;defines "/c" ;cflags (string "/Fo" dest) src) (shell cc "-c" src ;defines ;cflags "-o" dest)))) +(defn- compile-cpp + "Compile a C++ file into an object file." + [opts src dest &opt static?] + (def cpp (opt opts :cpp-compiler default-cpp-compiler)) + (def cflags [;(getcppflags opts) ;(if static? [] dynamic-cflags)]) + (def entry-defines (if-let [n (opts :entry-name)] + [(make-define "JANET_ENTRY_NAME" n)] + [])) + (def defines [;(make-defines (opt opts :defines {})) ;entry-defines]) + (def headers (or (opts :headers) [])) + (rule dest [src ;headers] + (check-cc) + (print "compiling " src " to " dest "...") + (create-dirs dest) + (if is-win + (shell cpp ;defines "/c" ;cflags (string "/Fo" dest) src) + (shell cpp "-c" src ;defines ;cflags "-o" dest)))) + (defn- libjanet "Find libjanet.a (or libjanet.lib on windows) at compile time" [] @@ -466,7 +497,7 @@ (string hpath `\\janet.lib`)) (defn- link-c - "Link object files together to make a native module." + "Link C object files together to make a native module." [opts target & objects] (def linker (opt opts (if is-win :linker :compiler) default-linker)) (def cflags (getcflags opts)) @@ -481,6 +512,22 @@ (shell linker ;ldflags (string "/OUT:" target) ;objects (win-import-library) ;lflags) (shell linker ;cflags ;ldflags `-o` target ;objects ;lflags)))) +(defn- link-cpp + "Link C++ object files together to make a native module." + [opts target & objects] + (def linker (opt opts (if is-win :cpp-linker :cpp-compiler) default-cpp-linker)) + (def cflags (getcppflags opts)) + (def lflags [;(opt opts :lflags default-lflags) + ;(if (opts :static) [] dynamic-lflags)]) + (def ldflags [;(opt opts :ldflags [])]) + (rule target objects + (check-cc) + (print "linking " target "...") + (create-dirs target) + (if is-win + (shell linker ;ldflags (string "/OUT:" target) ;objects (win-import-library) ;lflags) + (shell linker ;cflags ;ldflags `-o` target ;objects ;lflags)))) + (defn- archive-c "Link object files together to make a static library." [opts target & objects] @@ -655,10 +702,12 @@ int main(int argc, const char **argv) { (table/setproto m oldproto)) # Find static modules + (var has-cpp false) (def declarations @"") (def lookup-into-invocations @"") (loop [[prefix name] :pairs prefixes] (def meta (eval-string (slurp (modpath-to-meta name)))) + (if (meta :cpp) (set has-cpp true)) (buffer/push-string lookup-into-invocations " temptab = janet_table(0);\n" " temptab->proto = env;\n" @@ -683,15 +732,24 @@ int main(int argc, const char **argv) { (spit cimage_dest (make-bin-source declarations lookup-into-invocations) :ab) # Compile and link final exectable (unless no-compile - (def cc (opt opts :compiler default-compiler)) (def ldflags [;dep-ldflags ;(opt opts :ldflags []) ;janet-ldflags]) (def lflags [;static-libs (libjanet) ;dep-lflags ;(opt opts :lflags default-lflags) ;janet-lflags]) - (def cflags [;(getcflags opts) ;janet-cflags]) (def defines (make-defines (opt opts :defines {}))) - (print "compiling and linking " dest "...") - (if is-win - (shell cc ;cflags ;ldflags cimage_dest ;lflags `/link` (string "/OUT:" dest)) - (shell cc ;cflags ;ldflags `-o` dest cimage_dest ;lflags))))) + (if has-cpp + (do + (def cc (opt opts :cpp-compiler default-cpp-compiler)) + (def cflags [;(getcppflags opts) ;janet-cflags]) + (print "compiling and linking " dest "...") + (if is-win + (shell cc ;cflags ;ldflags cimage_dest ;lflags `/link` (string "/OUT:" dest)) + (shell cc ;cflags ;ldflags `-o` dest cimage_dest ;lflags))) + (do + (def cc (opt opts :compiler default-compiler)) + (def cflags [;(getcflags opts) ;janet-cflags]) + (print "compiling and linking " dest "...") + (if is-win + (shell cc ;cflags ;ldflags cimage_dest ;lflags `/link` (string "/OUT:" dest)) + (shell cc ;cflags ;ldflags `-o` dest cimage_dest ;lflags))))))) # # Installation and Dependencies @@ -853,9 +911,23 @@ int main(int argc, const char **argv) { # Make dynamic module (def lname (string "build" sep name modext)) - (loop [src :in sources] - (compile-c opts src (out-path src ".c" objext))) - (def objects (map (fn [path] (out-path path ".c" objext)) sources)) + + # Get objects to build with + (var has-cpp false) + (def objects + (seq [src :in sources] + (cond + (string/has-suffix? ".cpp" src) + (let [op (out-path src ".cpp" objext)] + (compile-cpp opts src op) + (set has-cpp true) + op) + (string/has-suffix? ".c" src) + (let [op (out-path src ".c" objext)] + (compile-c opts src op) + op) + (errorf "unknown source file type: %s, expected .c or .cpp")))) + (when-let [embedded (opts :embedded)] (loop [src :in embedded] (def c-src (out-path src ".janet" ".janet.c")) @@ -863,7 +935,7 @@ int main(int argc, const char **argv) { (array/push objects o-src) (create-buffer-c src c-src (embed-name src)) (compile-c opts c-src o-src))) - (link-c opts lname ;objects) + ((if has-cpp link-cpp link-c) opts lname ;objects) (add-dep "build" lname) (install-rule lname path) @@ -876,6 +948,7 @@ int main(int argc, const char **argv) { "# Metadata for static library %s\n\n%.20p" (string name statext) {:static-entry ename + :cpp has-cpp :ldflags ~',(opts :ldflags) :lflags ~',(opts :lflags)}))) (add-dep "build" metaname) @@ -887,9 +960,21 @@ int main(int argc, const char **argv) { (def opts (merge @{:entry-name ename} opts)) (def sobjext (string ".static" objext)) (def sjobjext (string ".janet" sobjext)) - (loop [src :in sources] - (compile-c opts src (out-path src ".c" sobjext) true)) - (def sobjects (map (fn [path] (out-path path ".c" sobjext)) sources)) + + # Get static objects + (def sobjects + (seq [src :in sources] + (cond + (string/has-suffix? ".cpp" src) + (let [op (out-path src ".cpp" sobjext)] + (compile-cpp opts src op true) + op) + (string/has-suffix? ".c" src) + (let [op (out-path src ".c" sobjext)] + (compile-c opts src op true) + op) + (errorf "unknown source file type: %s, expected .c or .cpp")))) + (when-let [embedded (opts :embedded)] (loop [src :in embedded] (def c-src (out-path src ".janet" ".janet.c")) @@ -1139,7 +1224,8 @@ Keys are: --binpath : The directory to install binaries and scripts. Defaults to $JANET_BINPATH. --libpath : The directory containing janet C libraries (libjanet.*). Defaults to $JANET_LIBPATH. --compiler : C compiler to use for natives. Defaults to $CC or cc (cl.exe on windows). - --archiver : C compiler to use for static libraries. Defaults to $AR ar (lib.exe on windows). + --cpp-compiler : C++ compiler to use for natives. Defaults to $CXX or c++ (cl.exe on windows). + --archiver : C archiver to use for static libraries. Defaults to $AR ar (lib.exe on windows). --linker : C linker to use for linking natives. Defaults to link.exe on windows, not used on other platforms. --pkglist : URL of git repository for package listing. Defaults to $JANET_PKGLIST or https://github.com/janet-lang/pkgs.git diff --git a/jpm.1 b/jpm.1 index a20bd168..53204195 100644 --- a/jpm.1 +++ b/jpm.1 @@ -71,9 +71,13 @@ $JANET_LIBPATH, or a reasonable default. See JANET_LIBPATH for more. .TP .BR \-\-compiler=$CC -Sets the compiler used for compiling native modules and standalone executables. Defaults +Sets the C compiler used for compiling native modules and standalone executables. Defaults to cc. +.BR \-\-cpp\-compiler=$CXX +Sets the C++ compiler used for compiling native modules and standalone executables. Defaults +to c++.. + .TP .BR \-\-linker Sets the linker used to create native modules and executables. Only used on windows, where diff --git a/src/include/janet.h b/src/include/janet.h index e90cc423..ec764851 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -201,7 +201,7 @@ extern "C" { #ifdef JANET_WINDOWS #define JANET_NO_RETURN __declspec(noreturn) #else -#define JANET_NO_RETURN __attribute__ ((noreturn)) +#define JANET_NO_RETURN __attribute__((noreturn)) #endif #endif @@ -561,7 +561,7 @@ JANET_API Janet janet_wrap_integer(int32_t x); #define janet_nanbox_tag(type) (janet_nanbox_lowtag(type) << 47) #define janet_type(x) \ (isnan((x).number) \ - ? (((x).u64 >> 47) & 0xF) \ + ? (JanetType) (((x).u64 >> 47) & 0xF) \ : JANET_NUMBER) #define janet_nanbox_checkauxtype(x, type) \ @@ -640,7 +640,7 @@ JANET_API Janet janet_nanbox_from_bits(uint64_t bits); #define JANET_DOUBLE_OFFSET 0xFFFF #define janet_u64(x) ((x).u64) -#define janet_type(x) (((x).tagged.type < JANET_DOUBLE_OFFSET) ? (x).tagged.type : JANET_NUMBER) +#define janet_type(x) (((x).tagged.type < JANET_DOUBLE_OFFSET) ? (JanetType)((x).tagged.type) : JANET_NUMBER) #define janet_checktype(x, t) ((t) == JANET_NUMBER \ ? (x).tagged.type >= JANET_DOUBLE_OFFSET \ : (x).tagged.type == (t)) @@ -1449,14 +1449,19 @@ JANET_API Janet janet_resolve_core(const char *name); /* New C API */ /* Allow setting entry name for static libraries */ +#ifdef __cplusplus +#define JANET_MODULE_PREFIX extern "C" +#else +#define JANET_MODULE_PREFIX +#endif #ifndef JANET_ENTRY_NAME #define JANET_MODULE_ENTRY \ - JANET_API JanetBuildConfig _janet_mod_config(void) { \ + JANET_MODULE_PREFIX JANET_API JanetBuildConfig _janet_mod_config(void) { \ return janet_config_current(); \ } \ - JANET_API void _janet_init + JANET_MODULE_PREFIX JANET_API void _janet_init #else -#define JANET_MODULE_ENTRY JANET_API void JANET_ENTRY_NAME +#define JANET_MODULE_ENTRY JANET_MODULE_PREFIX JANET_API void JANET_ENTRY_NAME #endif JANET_NO_RETURN JANET_API void janet_signalv(JanetSignal signal, Janet message); diff --git a/test/install/project.janet b/test/install/project.janet index d116847c..916abfda 100644 --- a/test/install/project.janet +++ b/test/install/project.janet @@ -9,6 +9,10 @@ :name "testmod2" :source @["testmod2.c"]) +(declare-native + :name "testmod3" + :source @["testmod3.cpp"]) + (declare-executable :name "testexec" :entry "testexec.janet") diff --git a/test/install/testexec.janet b/test/install/testexec.janet index d57e596e..045a6e4e 100644 --- a/test/install/testexec.janet +++ b/test/install/testexec.janet @@ -1,6 +1,7 @@ (use build/testmod) (use build/testmod2) +(use build/testmod3) (defn main [&] (print "Hello from executable!") - (print (+ (get5) (get6)))) + (print (+ (get5) (get6) (get7)))) diff --git a/test/install/testmod3.cpp b/test/install/testmod3.cpp new file mode 100644 index 00000000..dfaf94f0 --- /dev/null +++ b/test/install/testmod3.cpp @@ -0,0 +1,42 @@ +/* +* Copyright (c) 2020 Calvin Rose and contributors +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to +* deal in the Software without restriction, including without limitation the +* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +* sell copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +* IN THE SOFTWARE. +*/ + +/* A very simple native module */ + +#include +#include + +static Janet cfun_get_seven(int32_t argc, Janet *argv) { + (void) argv; + janet_fixarity(argc, 0); + std::cout << "Hello!" << std::endl; + return janet_wrap_number(7.0); +} + +static const JanetReg array_cfuns[] = { + {"get7", cfun_get_seven, NULL}, + {NULL, NULL, NULL} +}; + +JANET_MODULE_ENTRY(JanetTable *env) { + janet_cfuns(env, NULL, array_cfuns); +}