#!/usr/bin/env python # Module helper script. # Copyright 2015-2023 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 . 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 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()