diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..7d8aa76 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,63 @@ +name: build + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + MKN_KUL_GIT_CO: --depth 1 + CURL_GET: curl -fL --retry 3 --retry-delay 2 + PATH_GET: https://github.com/mkn/mkn/releases/download/latest + +jobs: + ubuntu: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - env: + MKN_KUL_GIT_CO: --depth 1 + run: | + $CURL_GET -o mkn ${PATH_GET}/mkn_nix + chmod +x mkn + KLOG=2 ./mkn clean build run -dtOa "-std=c++20 -fPIC" + KLOG=2 ./mkn clean build run -dtOp test -a "-std=c++20 -fPIC" + + macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - env: + MKN_LIB_LINK_LIB: 1 + MKN_KUL_GIT_CO: --depth 1 + run: | + $CURL_GET -o mkn ${PATH_GET}/mkn_arm_osx + chmod +x mkn + KLOG=2 ./mkn clean build run -dtOa "-std=c++20 -fPIC" + KLOG=2 ./mkn clean build run -dtOp test -a "-std=c++20 -fPIC" + + windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + - uses: ilammy/msvc-dev-cmd@v1 + with: + arch: amd64 + + - env: + MKN_KUL_GIT_CO: --depth 1 + MKN_CL_PREFERRED: 1 + shell: cmd + run: | # /bin/link interferes with cl/link.exe + bash -c "rm /bin/link" + bash -c '$CURL_GET -o mkn.exe ${PATH_GET}/mkn.exe' + bash -c 'KLOG=2 ./mkn clean build run -dtOa "-EHsc -std:c++20"' + bash -c 'KLOG=2 ./mkn clean build run -dtOp test -a "-EHsc -std:c++20"' diff --git a/.github/workflows/build_nix.yml b/.github/workflows/build_nix.yml deleted file mode 100644 index c221c0b..0000000 --- a/.github/workflows/build_nix.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: ubuntu-latest - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - env: - MKN_KUL_GIT_CO: --depth 1 - run: | - curl -fL --retry 3 --retry-delay 2 -o mkn https://github.com/mkn/mkn/releases/download/latest/mkn_nix - chmod +x mkn - KLOG=2 ./mkn clean build run -dtOa "-std=c++17 -fPIC" - KLOG=2 ./mkn clean build run -dtOp test -a "-std=c++17 -fPIC" diff --git a/.github/workflows/build_osx.yml b/.github/workflows/build_osx.yml deleted file mode 100644 index ca68153..0000000 --- a/.github/workflows/build_osx.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: macos-latest - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - runs-on: macos-latest - steps: - - uses: actions/checkout@v4 - - - env: - MKN_LIB_LINK_LIB: 1 - MKN_KUL_GIT_CO: --depth 1 - run: | - curl -fL --retry 3 --retry-delay 2 -o mkn https://github.com/mkn/mkn/releases/download/latest/mkn_arm_osx - chmod +x mkn - KLOG=2 ./mkn clean build run -dtOa "-std=c++17 -fPIC" - KLOG=2 ./mkn clean build run -dtOp test -a "-std=c++17 -fPIC" diff --git a/.github/workflows/build_win.yml b/.github/workflows/build_win.yml deleted file mode 100644 index 84347fa..0000000 --- a/.github/workflows/build_win.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: windows-latest - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - runs-on: windows-latest - steps: - - uses: actions/checkout@v4 - - # setup MSVC compiler - - uses: ilammy/msvc-dev-cmd@v1 - with: - arch: amd64 - - - env: - MKN_KUL_GIT_CO: --depth 1 - MKN_CL_PREFERRED: 1 - shell: cmd - run: | # /bin/link interferes with cl/link.exe - bash -c "rm /bin/link" - bash -c 'curl -Lo mkn.exe https://github.com/mkn/mkn/releases/download/latest/mkn.exe' - bash -c 'KLOG=2 ./mkn clean build run -dtOa "-EHsc -std:c++17"' - bash -c 'KLOG=2 ./mkn clean build run -dtOp test -a "-EHsc -std:c++17"' diff --git a/.sublime-project b/.sublime-project new file mode 100644 index 0000000..aaf0487 --- /dev/null +++ b/.sublime-project @@ -0,0 +1,17 @@ +{ + "folders" : + [ + { + "path" : "." + } + ], + "settings" : + { + "ClangFormat" : + { + "binary" : "clang-format", + "format_on_save" : true, + "style" : "file" + } + } +} \ No newline at end of file diff --git a/.sublime-project.sublime-workspace b/.sublime-project.sublime-workspace new file mode 100644 index 0000000..99d4cf8 --- /dev/null +++ b/.sublime-project.sublime-workspace @@ -0,0 +1,3 @@ +{ + "project": ".sublime-project", +} diff --git a/mod.cpp b/mod.cpp index c8dab21..c534a43 100644 --- a/mod.cpp +++ b/mod.cpp @@ -28,36 +28,127 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include -#include - -#include "maiken/module/init.hpp" -#include "mkn/kul/string.hpp" +#include "maiken/module/init.hpp" // IWYU pragma: keep + +#include "maiken/app.hpp" // for Application +#include "maiken/compiler.hpp" // for CompilationInfo, Mode + +#include "mkn/kul/os.hpp" // for Dir, WHICH, PushDir +#include "mkn/kul/cli.hpp" // for EnvVar, EnvVarMode +#include "mkn/kul/env.hpp" // for GET, SET +#include "mkn/kul/log.hpp" // for KERR +#include "mkn/kul/defs.hpp" // for MKN_KUL_PUBLISH +#include "mkn/kul/proc.hpp" // for Process, ProcessCapture, AProcess +#include "mkn/kul/yaml.hpp" // for NodeValidator, Validator +#include "maiken/project.hpp" // for Project +#include "mkn/kul/except.hpp" // for Exception, KEXCEPT, KTHROW +#include "mkn/kul/string.hpp" // for String + +#include // for basic_string, string +#include // for vector +#include // for stringstream +#include // for exit +#include // for exception +#include // for exception namespace mkn { namespace lang { -class Python3Module : public maiken::Module { - private: +kul::File find_python3() KTHROW(std::exception) { + std::string const HOME = kul::env::GET("PYTHON3_HOME"); + + kul::Dir dir; + if (!HOME.empty()) { #if defined(_WIN32) - bool const config_expected = 0; + dir = kul::Dir(HOME); + if (!dir) KEXCEPT(kul::Exception, "$PYTHON3_HOME does not exist"); #else - bool const config_expected = 1; + dir = kul::Dir("bin", HOME); + if (!dir) KEXCEPT(kul::Exception, "$PYTHON3_HOME/bin does not exist"); +#endif + } + + std::vector bins{"python3", "python"}; + for (auto const& bin : bins) + if (kul::env::WHICH(bin.c_str())) + return dir ? kul::File{bin, dir} : kul::env::WHERE(bin.c_str()); + +#if defined(_WIN32) // or fallback + std::vector exes{"python3.exe", "python.exe"}; + for (auto const& bin : exes) + if (kul::env::WHICH(bin.c_str())) + return dir ? kul::File{bin, dir} : kul::env::WHERE(bin.c_str()); #endif - bool pyconfig_found = 0; - std::string HOME, PY = "python3", PYTHON, PY_CONFIG = "python-config", - PY3_CONFIG = "python3-config", PATH = kul::env::GET("PATH"); - kul::Dir bin; - std::shared_ptr path_var; - protected: + throw std::runtime_error("Could not find python!"); +} + +kul::cli::EnvVar python3_path_var(kul::File const& exe) { + return {"PATH", exe.dir().real(), kul::cli::EnvVarMode::PREP}; +} + +kul::File find_python_set_env() { + auto const python_exe = find_python3(); + // if (!python_exe) throw std::runtime_error("Could not find python!"); + // auto const path_var = python3_path_var(python_exe); + // kul::env::SET(path_var.name(), path_var.toString().c_str()); + return python_exe; +} + +static inline kul::File const python_exe = find_python_set_env(); + +std::string pyexec_for_string(std::string const& cmd) { + auto const path_var = python3_path_var(python_exe); + kul::Process p(python_exe.real()); + kul::ProcessCapture pc(p); + p << "-c" << ("\"" + cmd + "\""); + p.var(path_var.name(), path_var.toString()); + + try { + p.start(); + } catch (kul::proc::ExitException const& ex) { + KLOG(ERR) << pc.outs(); + KLOG(ERR) << pc.errs(); + KERR << ex; + throw; + } + return kul::String::LINES(pc.outs())[0]; // DROP EOL +} + +class Python3Module : public maiken::Module { + public: + void compile(maiken::Application& a, YAML::Node const& node) KTHROW(std::exception) override; + void link(maiken::Application& a, YAML::Node const& node) KTHROW(std::exception) override; + + private: + auto py_include() const { + return pyexec_for_string("import sysconfig; print(sysconfig.get_paths()['include'])"); + } + + std::string py_cflags() const { + return pyexec_for_string("import sysconfig; print(sysconfig.get_config_var('CFLAGS') or '')"); + } + + std::string py_libdir() const { + return pyexec_for_string("import sysconfig; print(sysconfig.get_config_var('LIBDIR'))"); + } + + std::string py_libname() const { + return pyexec_for_string( + "import sysconfig; lib=sysconfig.get_config_var('LDLIBRARY');" + "print(lib[3:-3] if lib.startswith('lib') and lib.endswith('.so') else lib)"); + } + std::string py_prefix() const { + return pyexec_for_string("import sysconfig; print(sysconfig.get_config_var('prefix'))"); + } + static std::vector MajMin(std::string const& PY) { std::vector version(2); for (auto const& idx : {0, 1}) { kul::Process p(PY); kul::ProcessCapture pc(p); - std::string print{"\"import sys; print(sys.version_info[" + std::to_string(idx) + "])\""}; + std::string print{"import sys; print(sys.version_info[" + std::to_string(idx) + "])"}; p << "-c" << print; p.start(); @@ -80,196 +171,67 @@ class Python3Module : public maiken::Module { }) .validate(node); } +}; - public: - void init(maiken::Application& a, YAML::Node const& node) KTHROW(std::exception) override { - bool finally = 0; - if (!kul::env::WHICH(PY.c_str())) PY = "python"; - PYTHON = kul::env::GET("PYTHON"); - if (!PYTHON.empty()) PY = PYTHON; -#if defined(_WIN32) - if (PY.rfind(".exe") == std::string::npos) PY += ".exe"; -#endif - kul::Process p(PY); - kul::ProcessCapture pc(p); - HOME = kul::env::GET("PYTHON3_HOME"); - if (!HOME.empty()) { -#if defined(_WIN32) - bin = kul::Dir(HOME); - if (!bin) KEXCEPT(kul::Exception, "$PYTHON3_HOME does not exist"); -#else - bin = kul::Dir("bin", HOME); - if (!bin) KEXCEPT(kul::Exception, "$PYTHON3_HOME/bin does not exist"); -#endif - path_var = std::make_shared("PATH", bin.real(), kul::cli::EnvVarMode::PREP); - kul::env::SET(path_var->name(), path_var->toString().c_str()); - p.var(path_var->name(), path_var->toString()); - }; - pyconfig_found = kul::env::WHICH(PY3_CONFIG.c_str()); - if (!pyconfig_found) { - pyconfig_found = kul::env::WHICH(PY_CONFIG.c_str()); - PY3_CONFIG = PY_CONFIG; - } - try { - if (!pyconfig_found && config_expected) { - finally = 1; - KEXCEPT(kul::Exception, "python-config does not exist on path"); - } - p << "-c" - << "\"import sys; print(sys.version_info[0])\""; - p.start(); - } catch (kul::Exception const& e) { - KERR << e.stack(); - } catch (std::exception const& e) { - KERR << e.what(); - } catch (...) { - KERR << "UNKNOWN ERROR CAUGHT"; - } - if (finally) exit(2); - } - - void compile(maiken::Application& a, YAML::Node const& node) KTHROW(std::exception) override { - VALIDATE_NODE(node); - kul::os::PushDir pushd(a.project().dir()); - std::vector incs; - try { - if (pyconfig_found) { - kul::Process p(PY3_CONFIG); - kul::ProcessCapture pc(p); - p << "--includes"; - if (path_var) p.var(path_var->name(), path_var->toString()); - p.start(); - auto outs(pc.outs()); - outs.pop_back(); - for (auto inc : kul::cli::asArgs(outs)) { - if (inc.find("-I") == 0) inc = inc.substr(2); - incs.push_back(inc); - } - } else { - kul::Dir dInc; - if (path_var) { - dInc = kul::Dir("include", bin.parent()); - if (!dInc) dInc = kul::Dir("include", kul::File(kul::env::WHERE(PY.c_str())).dir()); - } else { - dInc = kul::Dir("include", kul::File(kul::env::WHERE(PY.c_str())).dir()); - } - if (!dInc) - KEXCEPT(kul::Exception, "$PYTHON3_HOME/include does not exist") - << kul::os::EOL() << dInc.path(); - incs.push_back(dInc.real()); +void Python3Module::compile(maiken::Application& a, YAML::Node const& node) KTHROW(std::exception) { + VALIDATE_NODE(node); + + std::vector incs{py_include()}; + + try { + if (node["with"]) { + for (auto const& with : kul::cli::asArgs(node["with"].Scalar())) { + // std::stringstream import; + auto const import = std::string{"import "} + with + "; print(" + with + ".get_include())"; + // auto outs = pyexec_for_string(import); + // outs.pop_back(); + // #if defined(_WIN32) + // outs.pop_back(); + // #endif + incs.push_back(pyexec_for_string(import)); } - } catch (kul::Exception const& e) { - KERR << e.stack(); - } catch (std::exception const& e) { - KERR << e.what(); - } catch (...) { - KERR << "UNKNOWN ERROR CAUGHT"; } - - try { - if (node["with"]) { - for (auto const with : kul::cli::asArgs(node["with"].Scalar())) { - std::stringstream import; - import << "\"import " << with << "; print(" << with << ".get_include())\""; - kul::Process p(PY); - kul::ProcessCapture pc(p); - p << "-c" << import.str(); - if (path_var) p.var(path_var->name(), path_var->toString()); - p.start(); - auto outs(pc.outs()); - outs.pop_back(); -#if defined(_WIN32) - outs.pop_back(); -#endif - incs.push_back(outs); - } - } - for (auto const inc : incs) { - kul::Dir req_include(inc); - if (req_include) { - a.addInclude(req_include.real()); - for (auto* rep : a.revendencies()) rep->addInclude(req_include.real()); - } - } - } catch (kul::Exception const& e) { - KERR << e.stack(); - } catch (std::exception const& e) { - KERR << e.what(); - } catch (...) { - KERR << "UNKNOWN ERROR CAUGHT"; + for (auto const inc : incs) { + kul::Dir req_include(inc); + + if (req_include) { + a.addInclude(req_include.real()); + for (auto* rep : a.revendencies()) rep->addInclude(req_include.real()); + } else + throw std::runtime_error("include doesn't exist: " + req_include.real()); } + } catch (kul::Exception const& e) { + KERR << e.stack(); + } catch (std::exception const& e) { + KERR << e.what(); + } catch (...) { + KERR << "UNKNOWN ERROR CAUGHT"; } +} - std::string prefix() const { - kul::Process p(PY3_CONFIG); - kul::ProcessCapture pc(p); - p << "--prefix"; - p.start(); - auto ret = pc.outs(); - ret.pop_back(); // new line - return ret; - }; - - void link(maiken::Application& a, YAML::Node const& node) KTHROW(std::exception) override { - VALIDATE_NODE(node); - if (pyconfig_found) { - auto version = MajMin(PY); - - kul::os::PushDir pushd(a.project().dir()); - kul::Process p(PY3_CONFIG); - kul::ProcessCapture pc(p); - p << "--ldflags"; +void Python3Module::link(maiken::Application& a, YAML::Node const& node) KTHROW(std::exception) { + VALIDATE_NODE(node); - if (path_var) p.var(path_var->name(), path_var->toString()); + auto const embed = kul::String::BOOL(kul::env::GET("MKN_PYTHON_LIB_EMBED", "0")); - auto const embed = kul::String::BOOL(kul::env::GET("MKN_PYTHON_LIB_EMBED", "0")); - if (embed) p << "--embed"; + auto linker = py_cflags(); + auto const libpath = py_libdir(); + auto const prefx = py_prefix(); - p.start(); - std::string linker(pc.outs()); - linker.pop_back(); - auto const prefx = prefix(); - - if (prefx.size()) - if (auto const lib = kul::Dir(kul::Dir::JOIN(prefx, "lib"))) { - if (auto const needle = std::string{"-L" + lib.real()}; - linker.find(needle) != std::string::npos) { - kul::String::REPLACE_ALL(linker, needle + " ", ""); - a.addLibpath(lib.real()); - } - } - - if (embed) - for (auto bit : kul::String::SPLIT(linker, " ")) { - kul::String::REPLACE_ALL(bit, " ", ""); - if (bit.find("-l") == 0) { - auto const lib = bit.substr(2); - kul::String::REPLACE_ALL(linker, bit + " ", ""); - a.addLib(lib); - } - } - - if (node["delete"]) { - kul::String::REPLACE_ALL(linker, " ", " "); - for (auto const del : kul::String::SPLIT(node["delete"].Scalar(), " ")) - kul::String::REPLACE_ALL(linker, del, ""); - kul::String::REPLACE_ALL(linker, " ", " "); - } - if (a.mode() != maiken::compiler::Mode::STAT) a.prependLinkString(linker); - } else { - kul::Dir dPath; - if (path_var) { -#if defined(_WIN32) - dPath = kul::Dir("libs", bin); -#endif - } else { - dPath = kul::Dir("libs", kul::File(kul::env::WHERE(PY.c_str())).dir()); + if (prefx.size()) + if (auto const lib = kul::Dir(kul::Dir::JOIN(prefx, "lib"))) { + if (auto const needle = std::string{"-L" + lib.real()}; + linker.find(needle) != std::string::npos) { + kul::String::REPLACE_ALL(linker, needle + " ", ""); + a.addLibpath(lib.real()); } - if (!dPath) KEXCEPT(kul::Exception, "$PYTHON3_HOME/libs does not exist"); - a.addLibpath(dPath.real()); } - } -}; + + if (embed) a.addLib(py_libname()); + + if (a.mode() != maiken::compiler::Mode::STAT) a.prependLinkString(linker); +} + } // namespace lang } // namespace mkn