diff --git a/mu/mu-cmd.cc b/mu/mu-cmd.cc index ecb02ba4..522d7316 100644 --- a/mu/mu-cmd.cc +++ b/mu/mu-cmd.cc @@ -1,5 +1,5 @@ /* -** Copyright (C) 2010-2023 Dirk-Jan C. Binnema +** Copyright (C) 2010-2025 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the @@ -73,7 +73,7 @@ cmd_scm(const Store& store, const Options& opts) return Err(Error::Code::InvalidArgument, "scm/guile is not available in this build"); #else - return Mu::Scm::run(Mu::Scm::Config{store, opts}); + return Mu::Scm::run(store, opts, true/*blocking*/); #endif /*BUILD_SCM*/ } diff --git a/scm/mu-scm-repl.scm b/scm/mu-scm-repl.scm index 9664071b..6cd8efe7 100644 --- a/scm/mu-scm-repl.scm +++ b/scm/mu-scm-repl.scm @@ -21,7 +21,7 @@ ;; after printing UNIX-CONNECT:\n on stdout (let ((socket-path (getenv "MU_SCM_SOCKET_PATH"))) (when socket-path - (format #t "UNIX-CONNECT:~a\n" socket-path) + (format #t "~a\n" socket-path) (run-server (make-unix-domain-server-socket #:path socket-path)))) diff --git a/scm/mu-scm.cc b/scm/mu-scm.cc index 9d460de9..ac016045 100644 --- a/scm/mu-scm.cc +++ b/scm/mu-scm.cc @@ -16,23 +16,31 @@ ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ +#include "config.h" #include "mu-scm.hh" +#include #include #include +#include +#include +#include + #include "mu-utils.hh" -#include "config.h" #include "mu-scm-types.hh" +#ifdef HAVE_PTHREAD_SETNAME_NP +#include +#endif + using namespace Mu; using namespace Mu::Scm; namespace { -static const Mu::Scm::Config *config{}; -static SCM mu_mod; // The mu module +SCM mu_mod; // The mu module } /** @@ -56,15 +64,6 @@ init_options(const Options& opts) scm_c_define("%options", scm_opts); } -static void -init_module_mu(void* _data) -{ - init_options(config->options); - init_store(config->store); - init_message(); - init_mime(); -} - static const Result make_mu_scm_path(const std::string& fname) { @@ -84,23 +83,20 @@ make_mu_scm_path(const std::string& fname) { } namespace { -static std::string mu_scm_path; -static std::string mu_scm_repl_path; -static std::string mu_scm_socket_path; +std::string mu_scm_path; +std::string mu_scm_repl_path; +std::string mu_scm_socket_path; constexpr auto SOCKET_PATH_ENV = "MU_SCM_SOCKET_PATH"; +using StrVec = std::vector; +StrVec scm_args; +std::thread scm_worker; } static Result -prepare_run(const Mu::Scm::Config& conf) +prepare_run(const Mu::Options& opts) { - if (config) - return Err(Error{Error::Code::AccessDenied, - "already prepared"}); - config = &conf; - // do a checks _before_ entering guile, so we get a bit more civilized // error message. - if (const auto path = make_mu_scm_path("mu-scm.scm"); path) mu_scm_path = *path; else @@ -111,8 +107,8 @@ prepare_run(const Mu::Scm::Config& conf) else return Err(path.error()); - if (config->options.scm.script_path) { - const auto path{config->options.scm.script_path->c_str()}; + if (opts.scm.script_path) { + const auto path{opts.scm.script_path->c_str()}; if (const auto res = ::access(path, R_OK); res != 0) { return Err(Error::Code::InvalidArgument, "cannot read '{}': {}", path, ::strerror(errno)); @@ -122,71 +118,129 @@ prepare_run(const Mu::Scm::Config& conf) return Ok(); } -// make a unique unix-socket path -static std::string -maybe_set_uds_path(bool set) +static void +prepare_script(const Options& opts, StrVec& args) { - if (set) { - GRand* grand{g_rand_new()}; - auto path = join_paths(g_get_user_runtime_dir(), - mu_format("mu-scm-socket-{:08x}", - g_rand_int(grand))); - g_rand_free(grand); - g_setenv(SOCKET_PATH_ENV, path.c_str(), 1); - return path; - } else { - g_unsetenv(SOCKET_PATH_ENV); - return {}; + static std::string cmd; // keep alive + + // XXX: couldn't get another combination of -l/-s/-e/-c to work + // a) invokes `main' with arguments, and + // b) exits (rather than drop to a shell) + // but, what works is to manually specify (main ....) + cmd = "(main " + quote(*opts.scm.script_path); + for (const auto& scriptarg : opts.scm.params) + cmd += " " + quote(scriptarg); + cmd += ")"; + + args.emplace_back("-l"); + args.emplace_back(*opts.scm.script_path); + args.emplace_back("-c"); + args.emplace_back(cmd); +} + +static void +maybe_remove_socket_path() +{ + struct stat statbuf{}; + const auto sock{mu_scm_socket_path}; + + // opportunistic, so no real warnings, but be careful deleting! + + if (const int res = ::stat(sock.c_str(), &statbuf); res != 0) { + mu_debug("can't stat '{}'; err={}", sock, -res); + } else if ((statbuf.st_mode & S_IFMT) != S_IFSOCK) { + mu_debug("{} is not a socket", sock); + } else if (const int ulres = ::unlink(sock.c_str()); ulres != 0) { + mu_debug("failed to unlink '{}'; err={}", sock, -ulres); + } else { + mu_debug("unlinked {}", sock); } } -Result -Mu::Scm::run(const Mu::Scm::Config& conf) + +static void +prepare_shell(const Options& opts, StrVec& args) { - if (const auto res = prepare_run(conf); !res) - return Err(res.error()); + // drop us into an interactive shell/repl or start listening on a domain socket. + if (opts.scm.listen && opts.scm.socket_path) { + mu_scm_socket_path = *opts.scm.socket_path; + g_setenv(SOCKET_PATH_ENV, mu_scm_socket_path.c_str(), 1); + mu_info("setting up socket-path {}", mu_scm_socket_path); + ::atexit(maybe_remove_socket_path); //opportunistic cleanup + } + else + g_unsetenv(SOCKET_PATH_ENV); - scm_boot_guile(0, {}, [](void *data, int argc, char **argv) { - mu_mod = scm_c_define_module ("mu", init_module_mu, {}); - - std::vector args { - "mu", - "-l", mu_scm_path.c_str(), - }; - std::string cmd; - const auto opts{config->options.scm}; - // if a script-path was specified, run a script - if (opts.script_path) { - // XXX: couldn't get another combination of -l/-s/-e/-c to work - // a) invokes `main' with arguments, and - // b) exits (rather than drop to a shell) - // but, what works is to manually specify (main ....) - cmd = "(main " + quote(*opts.script_path); - for (const auto& scriptarg : opts.params) - cmd += " " + quote(scriptarg); - cmd += ")"; - for (const auto& arg: { - "-l", opts.script_path->c_str(), - "-c", cmd.c_str()}) - args.emplace_back(arg); - } else { - // otherwise, drop us into an interactive shell/repl - // or start listening on a domain socket. - mu_scm_socket_path = - maybe_set_uds_path(config->options.scm.listen); - args.emplace_back("--no-auto-compile"); - args.emplace_back("-l"); - args.emplace_back(mu_scm_repl_path.c_str()); - } - /* ahem...*/ - scm_shell(std::size(args), const_cast(args.data())); - }, {}); // never returns. - - return Ok(); + args.emplace_back("--no-auto-compile"); + args.emplace_back("-l"); + args.emplace_back(mu_scm_repl_path); } +struct ModMuData { const Mu::Store& store; const Mu::Options& opts; }; + +static void +init_module_mu(void* data) +{ + const ModMuData& conf{*reinterpret_cast(data)}; + + init_options(conf.opts); + init_store(conf.store); + init_message(); + init_mime(); +} + +static void +run_scm(const Mu::Store& store, const Mu::Options& opts) +{ + static ModMuData mu_data{store, opts}; + + scm_boot_guile(0, {}, + [](auto _data, auto _argc, auto _argv) { + mu_mod = scm_c_define_module ("mu", init_module_mu, &mu_data); + std::vector args; + std::transform(scm_args.begin(), + scm_args.end(), std::back_inserter(args), + [&](const std::string& strarg){ + /* ahem...*/ + return const_cast(strarg.c_str()); + }); + scm_shell(args.size(), args.data()); + + }, {}); // never returns. +} + +Result +Mu::Scm::run(const Mu::Store& store, const Mu::Options& opts, bool blocking) +{ + if (const auto res = prepare_run(opts); !res) + return Err(res.error()); + + scm_args = {"mu", "-l", mu_scm_path}; + + // do env stuff _before_ starting guile / threads. + if (opts.scm.script_path) + prepare_script(opts, scm_args); + else + prepare_shell(opts, scm_args); + + // in the non-blocking case, we start guile in a + // background thread; otherwise it will block. + if (!blocking) { + auto worker = std::thread([&](){ +#ifdef HAVE_PTHREAD_SETNAME_NP + pthread_setname_np(pthread_self(), "mu-scm"); +#endif /*HAVE_PTHREAD_SETNAME_NP*/ + run_scm(store, opts); + }); + worker.detach(); + } else + run_scm(store, opts); + + return Ok(); + +} #ifdef BUILD_TESTS @@ -199,7 +253,6 @@ Mu::Scm::run(const Mu::Scm::Config& conf) #include #include "utils/mu-test-utils.hh" - static void test_scm_script() { @@ -233,13 +286,8 @@ test_scm_script() Mu::Options opts{}; opts.scm.script_path = join_paths(MU_SCM_SRCDIR, "mu-scm-test.scm"); - Mu::Scm::Config scm_conf { - /*.store =*/ *store, - /*.options =*/ opts - }; - { - const auto res = Mu::Scm::run(scm_conf); + const auto res = Mu::Scm::run(*store, opts, false /*blocks*/); assert_valid_result(res); } } diff --git a/scm/mu-scm.hh b/scm/mu-scm.hh index 1d4776cd..7d017016 100644 --- a/scm/mu-scm.hh +++ b/scm/mu-scm.hh @@ -39,28 +39,24 @@ * */ namespace Mu::Scm { - /** - * Configuration object + * Start a guile REPL or program * - */ - struct Config { - const Mu::Store& store; - const Options& options; - }; - - /** - * Start a guile shell - * - * Initialize the Scm sub-system, then start a shell or run a script, + * Initialize the Scm sub-system, then start a REPL or run a script, * based on the configuration. * - * @param conf a Config object + * Unless 'blocking' is false or there is some pre-guile error, this + * method never returns. If blocking is false, it runs in the + * background. + * + * @param store a Store object + * @param opts options + * @param blocking whether to block (or run in the background) * * @return Ok() or some error */ - Result run(const Config& conf); - + Result run(const Store& store, const Options& opts, + bool blocking=true); /** * Helpers diff --git a/scm/mu-scm.texi b/scm/mu-scm.texi index 36c56972..14e4598e 100644 --- a/scm/mu-scm.texi +++ b/scm/mu-scm.texi @@ -107,6 +107,7 @@ Indices * Starting the REPL:: * Listening on a Unix Domain Socket:: * Hooking up with GNU/Emacs and Geiser:: +* Hooking up with Mu4e:: @end menu This chapter walks you through the installation and basic setup. @@ -183,19 +184,19 @@ REPL over a Unix domain socket, using the @t{--listen} flag. When you start @command{mu scm} with the @t{--listen} flag, it prints a (randomized) UNIX domain socket name and blocks after that; for instance: @example -$mu scm --listen -UNIX-CONNECT:/run/user/1000/mu-scm-socket-6ef6222e +$ mu scm --listen +/run/user/1000/mu-scm-15269.sock @end example -You can connect to this with exernal tools, for instance with @command{socat}: +You can connect to this with external tools, for instance with @command{socat}: @example -socat - UNIX-CONNECT:/run/user/1000/mu-scm-socket-6ef6222e +$ socat - UNIX-CONNECT:/run/user/1000/mu-scm-15269.sock GNU Guile 3.0.9 Copyright (C) 1995-2023 Free Software Foundation, Inc. -Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'. -This program is free software, and you are welcome to redistribute it -under certain conditions; type `,show c' for details. +Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'. This +program is free software, and you are welcome to redistribute it under certain +conditions; type `,show c' for details. Enter `,help' for help. scheme@@(guile-user)> @@ -216,7 +217,7 @@ domain socket, as discussed in @xref{Listening on a Unix Domain Socket}. Assuming you have installed the @t{guile-geiser} package, the following snippet makes that easy: @lisp -(require 'guiser-guile) +(require 'geiser-guile) (defvar mu-scm-listen-command "mu scm --listen" "mu command to start an scm repl listening on a socket.") @@ -229,13 +230,32 @@ Connect to mu's scm (guile) interface through Geiser." :name "*mu-scm-repl*" :command `("sh" "-c" ,mu-scm-listen-command) :filter (lambda (_proc chunk) - (when (string-match "^UNIX-CONNECT:\\(.*\\)$" chunk) + (when (stringng-match "^\\(mu-scm.*\\.sock\\)$" chunk) (geiser-connect-local 'guile (match-string 1 chunk)))))) @end lisp After evaluating this, you can use @command{M-x mu-scm-geiser-connect} to start the REPL, with all the Geiser bells & whistles. +@node Hooking up with Mu4e +@section Hooking up with Mu4e +@cindex Mu4e + +If you use @t{mu4e}, connecting to the SCM server is even easier than with +``plain'' Emacs. + +First tell @t{mu4e} to starts it server with with @t{--listen} parameter: +@lisp +(setq mu4e-mu-scm-server t) +@end lisp +After that, (re)start @t{mu4e}. + +Now should be able to connect to the REPL using @t{M-x mu4e-mu-scm-repl}. Like +@ref{Hooking up with GNU/Emacs and Geiser}, this depends on the @t{geiser-guile} +package. + +The SCM instance uses the same database/store instance that @t{mu4e} uses. + @node Shell @chapter Shell