Extensions

Contents

Tutorial

Ground Rules and Preparation

What follows depends on autoconf, so we're assuming you're working off of a git clone of the server sources rather than a tarball (since you wouldn't be here unless you were interested in developing new features and for that you need git; autoconf you do not necessarily have to learn too much about; we just need you to be running a recent version).

We will also assume you have successfully cloned the MOO server source directory and done autoconf && ./configure -C && make to build a vanilla server. (If you hadn't actually done -C, that's okay, just rerun the configure script as ./configure -C; it should be a fast no-op. Also make again, too, if you want, but that should also be a fast no-op.)

(... also hoping you have an editor that does syntax highlighting so that # and dnl comments look right? Emacs has anautoconf mode that is different from its m4 mode and you should use the former when editing both .ac and .m4 files in this source tree. See below for further Emacs tips)

In what follows, $ at the beginning of the line is intended to be a shell prompt -- yes, real world people will display current directories and other things that are all irrelevant for what we're doing. You'll be staying in the root MOO source directory throughout.

Here is a quick convenience command you can set up:

$ mooval() {
  printf "%s\n" ";$1" 'abort' |
   ./moo -e -l /dev/null Minimal.db /dev/null |
   sed -e '0,/^[*]/d;/^MOO.*Bye.*NOT saving/,$d;/^$/d'
 }

(Cutting&pasting everything after the $ is probably your best bet here. Or just save it to a file and source it from there (i.e., using the . command). And yes, we're assuming you have a modern Unix shell that understands shell functions; any of bash, ksh, zsh, or dash will do. Otherwise, you will need to write an equivalent shell script yourself and store it on your PATH somewhere (sorry)).

As you might guess, mooval runs a server, loads Minimal.db, redirects both log and checkpoints to nowhere, fires up Emergency Wizard Mode, evals the first argument, shows you the result, and then exits without trying to save anything:

Here are, respectively, what successful evaluations and errors look like in this world:

$ mooval 'server_version("features")'
MOO (#3): => {"regexp"}

$ mooval 'hello()'
MOO (#3): ** 1 errors during parsing:
  Line 2:  Unknown built-in function: hello

and just always put single quotes around that first argument; it means everything in between will be passed by the shell verbatim and MOO almost never uses single quotes for anything, so this is about as ideal as it gets.

(And yes, this version of mooval is quite simplistic. Doubtless, you can already see multiple ways to improve or confuse it, all entirely beyond the scope of this document.)

Baby's Very First Extension

  1. Copy the file extensions_tutorial.ac to extensions2.ac (which, whenever it exists, is automatically hooked into the build process the way extensions.ac is, but there's no penalty for it not existing.)

    Important

    When you are done with this tutorial, be sure to delete or rename extensions2.ac and then do one last autoconf -f to flush out whatever effects it's had. Otherwise it lingers.

  2. Visit extensions2.ac; it's nothing but comments and is where we'll be doing most of our edits. You should probably take a moment to read through the comments.

You may notice that the "TOP" section is entirely within square brackets [ ], which, in this edition of m4, are the quote characters , which means m4 strips off the outer pair of brackets (yes they have to nest properly) and sends everything between verbatim as an argument to whatever the context is, in this case, we're in the middle of a (gigantic, taking up the entire file) invocation of MOO_XT_DECLARE_EXTENSIONS which interprets its first argument as being in MOO_XT this weird, idiosyncratic, domain-specific language I made up.

Here, indentation (of noncommented lines) matters. Exact amounts don't matter so much as "indented less" vs. "indented the same" vs. "indented more".

And by "same", I mean "same sequence of tab and space characters".

Also, tabs have to come first, trying to indent in any other way will give you a "spaces before tabs" error).

(Actually, life will be generally easier if you never use tabs in this file, but, for the tutorial's sake, I wanted the commented and uncommented lines to match up with each other to make it more readable so I'm doing that just this once (promise))

(And yes, the "MIDDLE" and "BOTTOM" sections work differently. Really, they're just straight m4 code).

Okay, enough talk. Let's do stuff:

  1. In the "TOP" section, uncomment the %%extension hello line, (it tells you how to do this).

    Also, save the file. (Since this will get old quickly, I'll assume from now on I don't have to keep telling you save the file after every change.)

  2. Rerun autoconf -f && ./configure -C && make (You may want to alias this to something short since you'll be typing it a lot. From now on, I'm just going to say "Rebuild")

    (as for why the -f:  This makes autoconf re-read things it would normally skip if it has reason to believe they don't matter, and since ./configure already has weird dependency rules due to Makefile itself needing to be rebuilt whenever ./configure or anything else in Autoconf Land changes, and the GNU folks having come up with extra-clever ways for Makefile to behave in situations when it's rebuilding itself, and trying to come up with a way to not include extensions2.ac in the distribution, but have ./configure both depend on extensions2.ac, but quietly skip over it not being there so as not to confuse all of the People Who Are Not Writing Extensions (which is at least 7 billion more than the People Who Are Writing Extensions), without causing random, extra unnecessary runs of ./configure was all making my head explode, all to cover situations that are normally quite rare, so I punted. Yes, that was all one sentence.

    The rule is specific to extensions2.ac and it's simple: If you edit this file, you need to do autoconf -f. Done.)

You should now be able to do this:

$ mooval 'server_version("features")'
MOO (#3): => {"regexp", "hello"}

In other words, there is now a hello extension. Feel free to look at how configure and version_src.h changed. The extension does absolutely nothing, but it is, at least, listed now.

It is also a mandatory extension, i.e., always active, because there is as yet no way to disable it.

  1. Uncomment the %disabled line. Rebuild.
$ mooval 'server_version("features")'
MOO (#3): => {"regexp"}

So you made the extension go away again. Congratulations.

Actually, this is important: Whatever you do, make sure you're cleaning up after yourself, so that when your extension is disabled, your impact on the existing code is zero. Yes, I know I may have violated that rule a bit for Unicode, but ... Unicode, so... Point is, the less you're doing when your extension is disabled, the more The People Who Hate Your Extension will thank you.

  1. Recomment the %disabled line. Rebuild.
$ mooval 'server_version("features")'
MOO (#3): => {"regexp", "hello"}

Surely, there's some better way to turn it off and on. Perhaps a command line argument?

$ ./configure -C --disable-hello
configure: WARNING: unrecognized options: --disable-hello
...
$ ./configure -C --enable-hello
configure: WARNING: unrecognized options: --enable-hello
...

Evidently not, but maybe we can fix this?

  1. Uncomment the first --enable-hello line in extensions2.ac and rebuild.

Oh, look:

$ mooval 'server_version("features")'
MOO (#3): => {"regexp", "hello"}

$ ./configure -C --disable-hello && make
...
configure: (%%extension hello:) disabled
...
$ mooval 'server_version("features")'
MOO (#3): => {"regexp"}

$ ./configure -C --enable-hello && make
...
$ mooval 'server_version("features")'
MOO (#3): => {"regexp", "hello"}

No message the second time because enabled is the default. And, yay, our extension is back.

And, in case you were wondering, the name of the extension and the name for the --enable- argument do not have to be the same.

  1. Recomment --enable-hello, uncomment --enable-hi, and rebuild with ./configure -C --disable-hi

Still kind of annoying that ./configure -hs says nothing about it. But then, we haven't specifed what it should say, so how could it know?

  1. Uncomment the two lines (%? and %?-) that are immediately after --enable-hi. These are indented more, meaning they are subdirectives of --enable-hi.

    Rebuild.

And,... oh look, now we have argument descriptions:

$ ./configure -hs
...
  --disable-hi         no hello() function, sorry
  --enable-hi          provide a hello() function

If you are annoyed that these two lines are mushed in with other options (it sort of looks like they're part of the Waifs package), we can fix that by adding a section header:

  1. Uncomment the topmost %?, the one that has the same indentation as --enable-; it is a sibling directive and actually applies to the entire extension, saying in effect, "This extension has so much crap under it, it needs its own section header". Rebuild.

And now we get to see:

$ ./configure -hs
...
 Hello Facilities:
  --disable-hi         no hello() function, sorry
  --enable-hi          provide a hello() function

At this point, it's natural to think there should be some way to actually provide, say, a hello() function. So let's do that.

  1. Recomment everything except the %%extension line, so that we're back to the mandatory invisible extension we had as of step 4.

    Then, create a file hello.c with this in it:

    /* #include "hello.h" */
    #include "bf_register.h"

    #include "config.h"
    #include "options.h"

    #include "functions.h"
    #include "storage.h"

    static package
    bf_hello(Var arglist UNUSED_, Byte  next  UNUSED_,
             void *vdata UNUSED_, Objid progr UNUSED_)
    {
        const char *msg =
    #if HELLO_ESMTP
        "ehlo world"
    #else
        "hello world"
    #endif
        ;
        return make_string_pack(str_dup(msg));
    }

    void
    register_hello(void)
    {
        register_function("hello", 0, 0, bf_hello);
    }

For now, assume this code works (it should!). I could go into how builtin functions work, what a package is, why you need that str_dup() call, and so on, but this is described elsewhere.

The question here is how to get make and friends to do the right things and get this compiled in:

  1. Uncomment the XT_CSRCS = hello.c line and rebuild.

For this one, you should look at what happens to Makefile and bf_register.{h,c}.

Extension commands of the form "VAR=value" refer to Makefile variables. In most cases where the VAR is something random, this line will just get copied in when the extension is active (when you need your own special makevars to make life easier in other parts of your extensions, not that you'll need to do this that often).

But certain VARs are special to the extension framework: XT_CSRCS is the list of C sources that each extension potentially adds to. In other words, XT_CSRCS is going to list all of the extra C source files needed by all active extensions, not just hello. (And if we were to need to have a hello.h -- we do not in this version of the world -- that's what XT_HDRS is for).

And then all C sources, both the common ones and the "XT" ones, (always) get scanned for registration functions -- read bf_register.h if you care about how that works -- which then show up in bf_register.{h,c}. Which is then how the server knows to invoke register_hello() on startup.

Which means this should all now Just Work:

$ mooval 'server_version("features")'
MOO (#3): => {"regexp", "hello"}

$ mooval 'hello()'
MOO (#3): => "hello world"

Now, as it happens, hello.c has some Optional Behavior, i.e., there appear to be multiple conflicting paradigms for what hello() should actually do. And so, we have an Option, HELLO_ESMTP, which presumably gets #defined somewhere.

In times of yore, Pavel would just add a suitable default #undef HELLO_ESMTP to options.h, provide a description, and if you wanted to change it, you'd edit that file manually.

Here in The Future, we are More Advanced:

  1. Put the following lines in options.h.in somewhere before the #include "options_epilog.h" that ends everything there (and you can have the comment say anything you want because we don't care).
/************************************************************
 *  The HELLO_ESMTP option is an important component of the
 *  Hello extension.  If #defined, it makes the new builtin
 *  hello() behave in a more ESMTP-ly fashion
 */

#undef HELLO_ESMTP

and it's #undef not because that's the default but because everything in options.h.in needs to be #undef.

Normally, the actual default would have to be set in options.ac, however since this option is essentially useless without the extension, we'd just as soon keep this information in the same file with the extension description.

You'll notice that options.ac consists entirely of a single call to MOO_DECLARE_OPTIONS, which is an m4 macro that can actually be invoked multiple times.

So that is what the "MIDDLE" section of extensions2.ac (also extensions.ac, for that matter) is.

  1. Uncomment the first line after MOO_DECLARE_OPTIONS by deleting the characters dnl from the beginning of the line.
$ autoconf -f && ./configure -hs
...
  --enable-def-HELLO_ESMTP          make hello() do The ESMTP Thing

$ ./configure -C --enable-def-HELLO_ESMTP && make
...
$ mooval 'hello()'
MOO (#3): => "ehlo world"

But really this is all MOO_DECLARE_OPTIONS at work. This is how you make a regular option and the only thing we're doing differently is that the MOO_DECLARE_OPTIONS invocation is not the one that's in options.ac. Thus far, MOO_XT_DECLARE_EXTENSIONS is not seeing any of this.

We haven't even put the --enable-hi argument back.

But maybe this is enough.

Or maybe, once we get back to the release situation where this extension is disabled by default, and people start getting annoyed that they have to do both --enable-hi and --enable-def-HELLO_ESMTP in order to get what they want. We can do something to make their lives easier?

Time for some option keywords.

  1. Uncomment --enable- hi = KWD and the next two lines (%?-, %?). This brings back --enable-hi and adds a bit more to tell people they can add keywords if they want (they don't have to).

    Then uncomment %option esmtp..., and the next two lines (%alt and %?).

    Finally, to make it all look truly professional, we will put back the %? Hello Facilities: section header as well.

And voilà!

$ autoconf -f && ./configure -hs
...
 Hello Facilities:
  --disable-hi            no hello() function, sorry
  --enable-hi=KWD,...     provide a hello() function
            ehlo|esmtp: . . do the esmtp thing
...
$ ./configure -C --enable-hi=ehlo && make && mooval 'hello()'
...
MOO (#3): => "ehlo world"

$ ./configure -C --enable-hi=esmtp && make && mooval 'hello()'
...
MOO (#3): => "ehlo world"

$ ./configure -C --enable-hi && make && mooval 'hello()'
...
MOO (#3): => "hello world"

$ ./configure -C --disable-hi && make && mooval 'hello()'
...
MOO (#3): ** 1 errors during parsing:
  Line 2:  Unknown built-in function: hello

Congratulations, you've completed the tutorial and earned your Extensions White Belt. Go forth, and remember: With great power comes great responsibility.

Bonus round for Emacs users

If you want to learn more from this (meaning the following is all optional, but really good if you can manage it), you can visit configure, Makefile, config.status,config.h,options.h,bf_register.c, and version_src.h in Emacs buffers, and then repeat as much of the tutorial as you can manage, but, this time:

HOW-TOs

(for now, you have examples in extensions.ac)

How to require a system shared library

TODO

How to incorporate a build directory

TODO


XT Reference

Cmd reference

Here are all of the cmds.

The source that actually does the work is in ac_include/ax_xt.m4.

--enable- ew_name [= text ]

Declares an --enable- argument for ./configure for the parent %%extension. The expected format is --enable-ew_name=keywords, where keywords is a comma--separated list indicating which subfeatures are being chosen, the individual possible keywords being declared via %option or %option_set (to have a single keyword that selects multiple subfeatures).

--with- ew_name [= text ]

Declares a --with- argument for ./configure.

If the parent declaration is %require, the expected format is --with-ew_name=lib_keywords, where lib_keywords is a comma--separated list of library keywords in preference order. The default (yes keyword) is to search for all of the libraries in order declared.

If the parent declaration is %build, the expected format is --with-ew_name=directory_path, where ew_name, by convention, is expected to end in path (we don't enforce this yet, but maybe we should), and directory_path indicates where the build directory is to be found relative to the MOO source directory. This directory is then assigned to the make variable specified by %dirvar and also substituted for %DIR% in any %ac code provided.

%cdefine name [ value ]

For %%extension this determines the extension's cppname and/or its various option cppnames. %cdefine must precede all %option declarations.

It is is intended that an AC_DEFINE be issued for an extension cppname exactly when the extension is active. Option cppnames are intended to be AC_DEFINEd if an option keyword is chosen either directly or indirectly (via %option_set) by the --enable-ew_name argument. In either case, if a given cppname is also defined in a MOO_DECLARE_OPTIONS then the corresponding --enable-def- argument can override this

(... and if there's some reason that being able to override would be a bad idea, that's then also a reason to not declare it in MOO_DECLARE_OPTIONS and instead put that cppname in config.h.in instead of options.h.in).

For %require, this determines a requirement cppname and/or the library cppnames. %cdefine must precede all %lib declarations.

An AC_DEFINE for the requirement cppname is issued if and only if the requirement has been satisfied, which must and can only happen if the extension is active (if, say you want to do without having a separate extension cppname). An AC_DEFINE for a Library cppnames is issued if the library is selected.

Important

There is currently no notion of being able to override library selections with --enable-def-. cppnames for libraries and requirements must go in config.h.in.

There are actually 3 forms of this declaration:

For either of the templated cases, the option or library cppname to be substituted, if it is not specifed in the %option or %lib declaration, defaults to the option or library keyword in all-caps.

In all cases you need to ensure that each possible cppname has an #undef line in one of config.h.in or options.h.in, otherwise the C code won't see it.

%%extension extension_name

Declares an extension. Allowed subcmds are %?, %disabled, --enable-, %cdefine, %option, %option_set, =, and %require.

%? text

In general, this adds helptext to configure -hs.

%?- text

For --enable- or --with-, describes what --disable-ew_name or --without-ew_name does.

%?* text

Add a line of description to a --with-.

%ac m4_code

Allowed in %lib or %build, this inserts the given m4_code, which is expected to expand to shell code

For %lib, the expansion executes in the library search loop. Typical usage looks like

   %ac  AC_SEARCH_LIBS([[ble_func]], [[best_lib_evar]], [%USE%])

i.e., attempt to compile a small program calling ble_func() using -lbest_lib_evar, and, if that succeeds, then whatever %USE% expands to is invoked to indicate that this library is to be selected (which then breaks out of the search loop).

The other available placeholder is %FAIL% which is expected to appear in an lvalue position (left side of an =) getting assigned an error message in a failure block. A list of the %FAIL% messages for all libraries is displayed if none of the libraries can be selected.

(If you invoke both %USE and %FAIL%, the library will be selected and all %FAIL% messages will be ignored.)

For %build, the expansion shell code is executed if the build is selected. In this case, %DIR% is available as a placeholder that expands to a shell code expression that evaluates to the build directory, for situations where you need to investigate something about it for ./configure purposes).

Quoting within m4_code should be such that placeholders will be m4-expanded. E.g., do not do %ac AC_SEARCH_LIBS([[f]], [[lib]], [[%USE%]]).

%ac_yes m4_code

Allowed in %require, this inserts the given m4_code, which is expected to expand to shell code to determine the default library search list for when there is no --with-ew_name argument or the keyword given is an implicit or explicit yes, for situations where this needs to be determined dynamically depending on whether, e.g., other extensions are active or not.

The expansion is expected to either be a shell expression that expands to a comma-separated list of library keywords (as would be provided in --with-ew_name=) or a shell command that assigns to the placeholder %LIB.

Quoting within m4_code should be such that placeholders will be m4-expanded. E.g., to protect uvw,xyz from expansion do %ac_yes AS_IF([...],[%LIB%[=uvw,xyz]],[%LIB%[=xyz]]), not %ac_yes AS_IF([...],[[%LIB%=uvw,xyz]],[[%LIB%=xyz]]),

%alt keyword

Specfies an alternate keyword for an %option or %lib.

%build

Specfies a static build for a %lib. Allowed subcmds are --with-, and %ac, %make, %dirvar, %path, and =.

%dirvar varname

Required in %build to specify the name of the make variable to be assigned the (absolute) build directory path (whether this comes from --with-*path or %path).

%disabled

Indicates that an extension is disabled by default. Otherwise it is enabled by default.

%implies other_keyword

For %option, indicates that other_keyword, which may be a single option or a set thereof is implied by selecting this option's keyword.

%lib keyword [cpp_keyword]

Declares a library choice for %require with keyword and a corresponding cppname as determined by cpp_keyword, which defaults to keyword upcased, and the setting of %cdefine.

Allowed subcmds are %ac, %alt, =, and %build

%make makefile_insert

For a %build, declare the additional dependencies and recipes to be included in Makefile if this build is selected.

%option_set os_keyword = keyword [keyword ...]

For an %%extension, this declares a option set os_keyword that, if selected, implies all of the right-hand-side keywords.

An %option_set does not get a cppname.

%option keyword [cpp_keyword]

For an %%extension, this declares an option keyword and a corresponding cppname as determined by cpp_keyword, which defaults to keyword upcased, and the setting of %cdefine.

Tip

Declaring an option keyword for an --enable- argument and declaring an option cppname for options.h are not the same thing.

Allowed subcmds are %implies, %?, and %alt.

%path directory_path

Declares a fixed directory for %build or a default directory for the situation where --with-*path has not been supplied by the user.

(Unless you're actually planning to this build directory as part of the MOO distribution (please don't; I worked hard on getting rid of these), this is really only recommended for development in order to spare you from having to retype--with-whateverpath=path/to/your/build over and over while you're working through the issues.)

%require requirement_name

Declares a requirement for an extension. Currently, the only supported notion of "requirement" is that of a required library that needs to be linked in order for the extension to function.

Allowed subcmds are --with-, %cdefine, %ac_yes, and %lib.

The order for the library search loop is specified by the --with-ew_name argument given by the user, which defaults to yes, which means whatever %ac_yes says, unless there is no %ac_yes declaration, in which case it's order in which the %lib declarations occur.

= [VAR = value]

Allowed in %%extension, %lib or %build. The specified variable setting is included in Makefile if the extension is active, the library is selected, or the build is selected, respectively.

Either this sets VAR to value or, in the case of variables special to the extension framework, appends value to whatever may already have already been added by other active extensions or selected libaries/builds, with either a space or a newline separator as appropriate for that VAR.

Makefile reference

The following are the "standard" makevars that the extension framework currently knows about and makes substitutions in:

There are also two additional Makefile substitutions which also cannot be set directly, start out empty, and get lines appended to them depending on which extensions are active

The rule for building the server is then

moo: $(OBJS) $(XT_LOBJS)
    $(CC) $(LDFLAGS) $(OBJS) $(XT_LOBJS) $(LIBS) -o $@

while individual modules depend on CPPFLAGS and CFLAGS in the usual way.


Philosophical Issues

When is an Option not an Option?

So, a cppname can either appear in config.h or options.h.

As far as ./configure is concerned, there is no distinction between these files. A cppname for which an AC_DEFINE has been issued (due to ./configure deciding it needs a value) will have its #undef line either substituted with a #define to put in a value or commented out to indicate it should stay #undef, wherever it appears; whether that's config.h.in or options.h.in doesn't matter. Both files are declared with AC_CONFIG_HEADERS in configure.ac; autoconf would allow us to have ten more such files if we wanted.

There are multiple ways to draw a distinction and they won't always completely agree:

The actual distinction goes something like this:

There is a set of high-level choices one needs to make up front. E.g., are we doing Unicode or not?

If we are, then ./configure has work to do; in this case, there's an extra source module that needs to be compiled in, we need to find a Unicode character data (uclib) library, and so on.

Later, there are some low-level choices that can be made, e.g., what are we doing about identifiers in verbcode? are we allowing non-ASCII there? This low level choice is just an #ifdef; the code is all there for either version of the world.

Therefore we can allow UNICODE_IDENTIFIERS to be an option, i.e., a genuine options.h option, where the user can use --enable-def-UNICODE_IDENTIFIERS to set it or not, and it'll work either way.

On the other hand, we have UNICODE_STRINGS, which answers the question of whether we have arbitrary UTF-8 in our string constants.

Which is a pervasive thing.

That is, once we decide we are not going to have Unicode, no amount of #ifdef-ing is going to make the code and libraries that ./configure should have installed magically appear. Thus, allowing the user to change UNICODE_STRINGS after the fact is going to fail horribly and shouldn't be allowed.

Therefore, UNICODE_STRINGS belongs in config.h. It is not an option, it's a setting that ./configure does automatically, even if it happens to be based on our high-level feature choice, we don't get a further choice to change it back.

... even though it is an %option keyword according to extensions.ac, being part of the original high-level choice whether to do Unicode.

Shorter version

The extensions framework creates a number of cppnames. All of the ones at the %require level and below have to go in config.h.

The rest are created by %option declarations, in which case you (the extension developer) have a choice whether they can/should be set separately later by --enable-def.

If you decide not to allow this for a particular cppname, you put it in config.h.in and you're done.

If you're okay with people messing with it independently, then you