emacs/modules/modhelp.py

208 lines
5.6 KiB
Python
Executable File

#!/usr/bin/env python
# Module helper script.
# Copyright 2015-2024 Free Software Foundation, Inc.
# This file is part of GNU Emacs.
# GNU Emacs is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# GNU Emacs is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
import os
import string
import subprocess as sp
import argparse
import re
EMACS = os.path.join('..', 'src', 'emacs')
def find_modules():
modpaths = []
for (dirname, dirs, files) in os.walk('.'):
if 'Makefile' in files:
modpaths.append(dirname)
return modpaths
def cmd_test(args):
mods = args.module
if not mods:
mods = find_modules()
make_cmd = ['make']
if args.force:
make_cmd.append('-B')
failed = []
for m in mods:
print('[*] %s: ------- start -------' % m)
print('[*] %s: running make' % m)
r = sp.call(make_cmd, cwd=m)
if r != 0:
print('[E] %s: make failed' % m)
failed += [m]
continue
print('[*] %s: running test' % m)
testpath = os.path.join(m, 'test.el')
if os.path.isfile(testpath):
emacs_cmd = [EMACS, '-batch', '-L', '.', '-l', 'ert',
'-l', testpath, '-f', 'ert-run-tests-batch-and-exit']
print(' '.join(emacs_cmd))
r = sp.call(emacs_cmd)
if r != 0:
print('[E] %s: test failed' % m)
failed += [m]
continue
else:
print('[W] %s: no test to run' % m)
print('\n[*] %d/%d MODULES OK' % (len(mods)-len(failed), len(mods)))
for m in failed:
print('\tfailed: %s' % m)
def to_lisp_sym(sym):
sym = re.sub('[_ ]', '-', sym)
return sym
def to_c_sym(sym):
sym = re.sub('[- ]', '_', sym)
return sym
def cmd_init(args):
if os.path.exists(args.module):
print("%s: file/dir '%s' already exists" % (__file__, args.module))
return
os.mkdir(args.module)
template_vars = {
'module': args.module,
'func': args.fun,
'c_file': '%s.c' % args.module,
'c_func': 'F%s_%s' % (to_c_sym(args.module), to_c_sym(args.fun)),
'lisp_func': '%s-%s' % (args.module, to_lisp_sym(args.fun)),
}
for path, t in TEMPLATES.items():
if isinstance(path, string.Template):
path = path.substitute(template_vars)
path = os.path.join(args.module, path)
print("writing %s..." % path)
with open(path, "w+") as f:
f.write(t.substitute(template_vars))
print("done! you can run %s test %s" % (__file__, args.module))
def main():
# path always written relative to this file
os.chdir(os.path.dirname(os.path.realpath(__file__)))
mainp = argparse.ArgumentParser()
subp = mainp.add_subparsers()
testp = subp.add_parser('test', help='run tests')
testp.add_argument('-f', '--force', action='store_true',
help='force regeneration (make -B)')
testp.add_argument('module', nargs='*',
help='path to module to test (default all)')
testp.set_defaults(func=cmd_test)
initp = subp.add_parser('init', help='create a test module from a template')
initp.add_argument('module', help='name of the new module')
initp.add_argument('-f', '--fun', default='fun',
help='override name of the default function')
initp.set_defaults(func=cmd_init)
args = mainp.parse_args()
args.func(args)
# double the $ to escape python template syntax
TEMPLATES = {
'Makefile': string.Template('''
ROOT = ../..
CC = gcc
LD = gcc
CFLAGS = -ggdb3 -Wall
LDFLAGS =
all: ${module}.so ${module}.doc
%.so: %.o
$$(LD) -shared $$(LDFLAGS) -o $$@ $$<
%.o: %.c
$$(CC) $$(CFLAGS) -I$$(ROOT)/src -fPIC -c $$<
'''),
string.Template('${c_file}'): string.Template('''
#include <emacs-module.h>
int plugin_is_GPL_compatible;
static emacs_value
${c_func} (emacs_env *env, ptrdiff_t nargs, emacs_value args[], void *data)
{
return env->intern (env, "t");
}
/* Bind NAME to FUN. */
static void
bind_function (emacs_env *env, const char *name, emacs_value Sfun)
{
emacs_value Qfset = env->intern (env, "fset");
emacs_value Qsym = env->intern (env, name);
emacs_value args[] = { Qsym, Sfun };
env->funcall (env, Qfset, 2, args);
}
/* Provide FEATURE to Emacs. */
static void
provide (emacs_env *env, const char *feature)
{
emacs_value Qfeat = env->intern (env, feature);
emacs_value Qprovide = env->intern (env, "provide");
emacs_value args[] = { Qfeat };
env->funcall (env, Qprovide, 1, args);
}
int
emacs_module_init (struct emacs_runtime *ert)
{
emacs_env *env = ert->get_environment (ert);
bind_function (env, "${lisp_func}",
env->make_function (env, 1, 1, ${c_func}, "doc", NULL));
provide (env, "${module}");
return 0;
}
'''),
'test.el': string.Template('''
(require 'ert)
(require 'module-test-common)
;; #$$ works when loading, buffer-file-name when evaluating from emacs
(module-load (module-path (or #$$ (expand-file-name (buffer-file-name)))))
(ert-deftest ${lisp_func}-test ()
(should (eq (${lisp_func} 42) t)))
''')
}
if __name__ == '__main__':
main()