Writing Emacs modules with Go
Table of Contents
This page is generated using (org-html-export-to-html)
if you prefer using
org-mode
you can get the org file from GitHub.
1 Introduction
This page is currently complete, but should still be considered a draft. If you find any grammar, spelling or technical errors, please create an issue on GitHub
As of Emacs version 25.1
Emacs has support for loading dynamic modules. And as
of version 1.5
Go supports building packages as dynamic shared libraries.
Allowing us to write Emacs functions in Go. This guide will walk you through
creating first a simple module in C and then a simple module in Go. There is not
a huge difference between the two. But understanding the C module well help when
we write our Go module.
2 C Emacs module
First create a top level directory to house our project . I like to create my
projects in $HOME/src
pick whichever location suits you. Once we
create the directory we'll cd
into it. And any commands we run from now on.
Will be from that directory.
mkdir -pv ~/src/emacs-module
cd ~/src/emacs-module
To compile a Emacs modules we need the emacs-module.h
header file. The header
file is found in the src
directory of the Emacs source tarball. It's possible your
OS provides this already. However this guide is OS agnostic and we'll assume
that the header file has not been installed. Since we just need the header and
not all of the emacs tarball we'll download it from the git repository.
Create the src directory
mkdir src
Download the emacs header file
wget "http://git.savannah.gnu.org/cgit/emacs.git/plain/src/emacs-module.h?h=emacs-25.2" -O src/emacs-module.h
2.1 src/cmodule.c
If you are using org-mode
you can generate this file automatically with C-c C-v
t
or M-x org-babel-tangle
.
Here is the complete C file. In the next section we'll do a line by line breakdown of what it does.
#include <emacs-module.h> #include <stdio.h> #define CMOD_VERSION "0.1" int plugin_is_GPL_compatible; static emacs_value Fcmodule_version (emacs_env * env, ptrdiff_t nargs, emacs_value args[], void *data) { return env->make_string (env, CMOD_VERSION, 3); } extern int emacs_module_init (struct emacs_runtime *ert) { emacs_env *env = ert->get_environment (ert); emacs_value Qfeat = env->intern (env, "cmodule"); emacs_value Qprovide = env->intern (env, "provide"); emacs_value pargs[] = { Qfeat }; env->funcall (env, Qprovide, 1, pargs); emacs_value fn = env->make_function (env, 0, 0, Fcmodule_version, "Returns cmodule version", NULL); emacs_value Qfset = env->intern (env, "fset"); emacs_value Qsym = env->intern (env, "cmodule-version"); emacs_value fargs[] = { Qsym, fn }; env->funcall(env, Qfset, 2, fargs); return 0; }
2.2 C Breakdown
First we include emacs-module.h
header file. We'll need this for the Emacs
declaration types.
#include <emacs-module.h> #include <stdio.h> #define CMOD_VERSION "0.1"
Emacs also requires a plugin_is_GPL_compatible
symbol, if not declared then
Emacs will not load the module.
int plugin_is_GPL_compatible;
Next is the C function that we plan to call from Emacs. Our function is
simple, all it does is take a C string literal called CMOD_VERSION
. Creates an
Emacs string and then returns it.
static emacs_value Fcmodule_version (emacs_env * env, ptrdiff_t nargs, emacs_value args[], void *data) { return env->make_string (env, CMOD_VERSION, 3); }
Next is the entry point of our module and is run when the module is first loaded by Emacs.
extern int emacs_module_init (struct emacs_runtime *ert)
First we get the Emacs environment from the Emacs run-time.
{ emacs_env *env = ert->get_environment (ert);
We need to provision the module. We'll call the elisp (provide)
function
through the C interface. If not Emacs will error with feature not provided.
We convert our feature string into a qouted lisp symbol. The nameing is important
our module will be compiled to cmodule.so
so the feature symbol must be named cmodule
,
anything else will not work. Then we get a quoted symbol for the elisp provide function.
emacs_value Qfeat = env->intern (env, "cmodule"); emacs_value Qprovide = env->intern (env, "provide"); emacs_value pargs[] = { Qfeat }; env->funcall (env, Qprovide, 1, pargs);
Next we declare our function and then use Emacs fset to define it. In short we
are telling Emacs that whenever we call (cmodule-verion)
to execute
the Fcmodule_version
C function.
emacs_value fn = env->make_function (env, 0, 0, Fcmodule_version, "Returns cmodule version", NULL); emacs_value Qfset = env->intern (env, "fset"); emacs_value Qsym = env->intern (env, "cmodule-version"); emacs_value fargs[] = { Qsym, fn }; env->funcall(env, Qfset, 2, fargs); return 0; }
2.3 Compiling the module
Create a lib directory to hold our shared libraries.
mkdir -p lib
Now we compile src/cmodule.c
as a shared C library.
gcc -I src -fPIC -shared src/cmodule.c -o lib/cmodule.so
2.4 Testing C module with Emacs.
To test our module we'll start Emacs in batch mode then call our custom function. We'll use Emacs message function to print the value to stdout.
emacs -Q -L ./lib -batch -l cmodule --eval "(message (cmodule-version))"
0.1
2.5 C summary
Writing a Emacs module in C is straight forward. These are the basic things you need to create a Emacs module.
- emacs-module.h
- pluginisGPLcompatibe symbol
- emacsmoduleinit entry point
- Provision module feature within Emacs
- fset function within Emacs
This C example was put together from diobla.info blog . He does a good job of breaking things down, and has some helper functions for binding and provisioning.
Here is some additional links if your looking to create some more advanced C module.
3 Go Emacs Module
We'll now make a similar module, but this time we'll access the Go runtime and get the version of go we are using. We'll have to do some boiler plating and some C type conversions but we can for the most part stick to go.
3.1 src/main.go
Create and edit file src/main.go
. We'll do a breakdown of this file in the
next section.
package main /* #include <emacs-module.h> #include <stdlib.h> int plugin_is_GPL_compatible; static void provide (struct emacs_runtime *ert, const char *feature) { emacs_env *env = ert->get_environment (ert); emacs_value Qfeat = env->intern (env, feature); emacs_value Qprovide = env->intern (env, "provide"); emacs_value args[] = { Qfeat }; env->funcall (env, Qprovide, 1, args); } extern emacs_value Fgo_version(emacs_env* p0); static void fset (struct emacs_runtime *ert, const char *name, void *fn) { emacs_env *env = ert->get_environment (ert); emacs_value Qfn = env->make_function (env, 0, 0, fn, "Return string describing the go runtime version.", NULL); emacs_value Qfset = env->intern (env, "fset"); emacs_value Qsym = env->intern (env, name); emacs_value fargs[] = { Qsym, Qfn }; env->funcall (env, Qfset, 2, fargs); } static emacs_value make_string (emacs_env * env, const char *s) { return env->make_string (env, s, strlen(s)); } */ import "C" import ( "runtime" "unsafe" ) func freeString(cstring *C.char) { C.free(unsafe.Pointer(cstring)) } //export Fgo_version func Fgo_version(env *C.emacs_env) C.emacs_value { version := runtime.Version() cversion := C.CString(version) defer freeString(cversion) return C.make_string(env, cversion) } //export emacs_module_init func emacs_module_init(ert *C.struct_emacs_runtime) C.int { cfeat := C.CString("gmodule") fname := C.CString("go-version") defer freeString(fname) defer freeString(cfeat) C.provide(ert, cfeat) C.fset(ert, fname, C.Fgo_version); return 0 } func main() {}
3.2 Go Breakdown
We'll name our package main, even though we are not creating a command module it's important the package name is main, otherwise when we build a c-shared library it will build an ar archive, and not a dynamic library.
package main
And in the comments section we include emacs-module.h
. We also
declare int plugin_is_GPL_compatible
.
/* #include <emacs-module.h> #include <stdlib.h> int plugin_is_GPL_compatible;
We need to provision our feature within emacs. To do this we need to call the
Emacs (provide)
function just like we did with the C module. Unfortunately we
can not call C pointer functions directly from Go. But we can call them from C.
So we'll create a helper C function that can access the struct function pointer we
need. We'll do this in the special comment section just before import "C"
.
static void provide (struct emacs_runtime *ert, const char *feature) { emacs_env *env = ert->get_environment (ert); emacs_value Qfeat = env->intern (env, feature); emacs_value Qprovide = env->intern (env, "provide"); emacs_value args[] = { Qfeat }; env->funcall (env, Qprovide, 1, args); }
We declare our function in C even though cgo will generate a gmodule.h
file. If
we don't declare it now we won't be able to reference it from this file.
extern emacs_value Fgo_version(emacs_env* p0);
We create a helper function to fset our function within emacs, this function assumes our Emacs function will not be taking any arguements it's not ideal but it will work for the function we want to create. We also hardcode our doc
static void fset (struct emacs_runtime *ert, const char *name, void *fn) { emacs_env *env = ert->get_environment (ert); emacs_value Qfn = env->make_function (env, 0, 0, fn, "Return string describing the go runtime version.", NULL); emacs_value Qfset = env->intern (env, "fset"); emacs_value Qsym = env->intern (env, name); emacs_value fargs[] = { Qsym, Qfn }; env->funcall (env, Qfset, 2, fargs); }
Later on we'll need to convert the runtime version Go string into a Emacs string value. Since we can't call function pointers directly we'll create a helper function to handle this.
static emacs_value make_string (emacs_env * env, const char *s) { return env->make_string (env, s, strlen(s)); }
We need to import the C package. The C package is special since it tells go to
build with cgo. It's important that this package import comes first and it on
it's own line. We need to close our C comment section just before import "C"
there should be no new lines after closing the comment.
*/ import "C" import ( "runtime" "unsafe" )
We'll be using some C strings. Go will not be able to memory manage our C
types. so we'll have to use some unsafe functions and free the strings
ourselves. We'll create a freeString
function to make this easier. C.free
calls the
free function we included in stdlib.h
. We also need to use the unsafe package
to get the pointer.
func freeString(cstring *C.char) { C.free(unsafe.Pointer(cstring)) }
We declare the Go function. This is the Go code that will be exectuted when we
call our Emacs function using (go-version)
. Our function will return a type
that emacs can evaluate. In this case the go runtime version as a string.
//export Fgo_version func Fgo_version(env *C.emacs_env) C.emacs_value { version := runtime.Version() cversion := C.CString(version) defer freeString(cversion) return C.make_string(env, cversion) }
Next we create our Emacs entry point. and we'll use //export
directive to tell
cgo we want to export this as a dynamic function. This is the same as if we
wrote extern int emacs_module_init(struct emacs_runtime* p0);
in C, in fact Go
will produce this declaration when it creates a C header file for our dynamic library.
If we were to return the Go type int
instead of C.int
it would create a
compiler error since the signature would no longer match the Emacs one.
//export emacs_module_init func emacs_module_init(ert *C.struct_emacs_runtime) C.int { cfeat := C.CString("gmodule") fname := C.CString("go-version") defer freeString(fname) defer freeString(cfeat) C.provide(ert, cfeat) C.fset(ert, fname, C.Fgo_version); return 0 }
Even though this is not a command package. We need to include a main function.
Due to the fact we are exporting go functions using //export
. We can simply
stub one out like so.
func main() {}
3.3 Building the Go module
We export our include flags and then manually build our so file.
go build -buildmode=c-shared -o lib/gmodule.so src/main.go
3.4 Test the Go module
emacs -Q -L ./lib -batch -l "gmodule" --eval '(message (go-version))'
go1.9
4 Automating this guide.
This whole guide can be run without any manual input using.
(org-babel-tangle) (org-babel-execute-buffer)
5 Todo's
5.1 DONE emacs-module.h
The emacs tarball is pretty large. figure out how we can download the header file and still be GPL compliant.
5.2 TODO GPL compliance
Find out what GPL implications might arise from writing packages in Go. Go uses a claused BSD license not sure of hosting the go runtime within emacs breaks GPL or not.