buildlib

buildlib is a tool that semi-automatically constructs a Cyclone interface to C code. It scans C header files and builds Cyclone header files and stub code so that Cyclone programs can call the C code. We use it to build the Cyclone interface to the C standard library (in much the same way that gcc uses the fixincludes program).

To use buildlib, you must construct a spec file that tells it what C headers to scan, and what functions and constants to extract from the headers. By convention, the names of spec files end in .cys. If spec.cys is a spec file, then buildlib is invoked by

  buildlib spec.cys

The output of buildlib is placed in a directory, BUILDLIB.OUT. The output consists of Cyclone header files and the stub files cstubs.c and cycstubs.cyc.

Spec files

The form of a spec file is given by the following grammar.

spec-file: (empty)
| spec spec-file

spec: header-name : directives ;

directives: (empty)
| directive directives

directive: cpp { balanced-braces }
| include { ids }
| hstub [id] { balanced-braces }
| cycstub [id] { balanced-braces }
| cstub [id] { balanced-braces }

ids: (empty)
| id balanced-braces* ids

The non-terminal id refers to C identifiers, and header-name ranges over C header names (e.g., stdio.h, sys/types.h). We use balanced-braces to refer to any sequence of C tokens with balanced braces, ignoring braces inside of comments, strings, and character constants.

Directives

include The include directive is used to extract constants and type definitions from C header files and put them into the equivalent Cyclone header file. For example, here is part of the spec that we use to interface to C’s errno.h:

  errno.h:
  include { E2BIG EACCES EADDRINUSE ... }

The spec says that the Cyclone version of errno.h should use the C definitions of error constants like E2BIG. These are typically macro-defined as integers, but the integers can differ from system to system. We ensure that Cyclone uses the right constants by running buildlib on each system.

For another example, our spec for sys/types.h reads, in part:

  sys/types.h:
  include { id_t mode_t off_t pid_t ... }

Here the symbols are typedef names, and the result will be that the Cyclone header file contains the typedefs that define id_t, etc. Again, these can differ from system to system.

You can use include to obtain not just constants (macros) and typedefs, but struct and union definitions as well. Furthermore, if a definition you include requires any other definitions that you do not explicitly include, those other definitions will be placed into the Cyclone header too. Moreover, for all such definitions, you can include an optional, expected Cyclonedefinition that is “equivalent” to the C definition on your system. By “equivalent,” we mean that your definition defines all of the same elements as the system definition (but possibly fewer), and each of these elements is “representation-compatible” in the sense that they use the same amount of storage when compiled. As example, here is our spec for grp.h:

include {
  gid_t
  group {
    struct group {
      char @gr_name;
      char @gr_passwd;
      gid_t gr_gid;
      char ** @zeroterm gr_mem;
    };  
  }
}

This provides richer information than the compatible definition on most systems. Here is the Linux definition:

struct group {
  char *gr_name;
  char *gr_passwd;
  gid_t gr_gid;
  char **gr_mem;
};

The user definition refines the system definition by indicating that for group strings grname and grpasswd must be non-NULL, and indicates that the array of strings gr_mem, is null-terminated. But note that the two definitions are representation-compatible in that they have the same run-time storage requirements. The Cyclone version provides more precise type information. You can provide user definitions for enumerated types and typedef’s as well.

Some refinements (such as polymorphism), are not yet supported for user definitions. Also, include does not work for variable or function declarations. You have to use the hstub directive to add variable and function declarations to your Cyclone header.

cstub The cstub directive adds code (the [balanced-braces][12]) to the C stub file. If an optional [id][14] is used, then the code will be added to the stub file only if the [id][14] is declared by the C header. This is useful because every system defines a different subset of the C standard library.

cycstub The cycstub directive is like the cstub directive, except that the code is added to the Cyclone stub file.

hstub The hstub directive is like the cstub directive, except that the code is added to the Cyclone header file.

cpp The cpp directive is used to tell buildlib to scan some extra header files before scanning the header file of the spec. This is useful when a header file can’t be parsed in isolation. For example, the standard C header sys/resource.h is supposed to define struct timeval, but on some systems, this is defined in sys/types.h, which must be included before sys/resource.h for that file to parse. This can be handled with a spec like the following:

  sys/resource.h:
  cpp {
    #include <sys/types.h>
  }
  ...

This will cause sys/types.h to be scanned by buildlib before sys/resource.h.

You can also use the cpp directive to directly specify anything that might appear in a C include file (e.g., macros).

Options

buildlib has the following options.

-d directory Use directory as the output directory instead of the default BUILDLIB.OUT.

-gather and -finish buildlib works in two phases. In the gather phase, buildlib grabs the C headers listed in the spec file from their normal locations in the C include tree, and stores them in a special format in the output directory. In the finish phase, buildlib uses the specially formatted C headers to build the Cyclone headers and stub files. The -gather flag tells buildlib to perform just the gather phase, and the -finish flag tells it to perform just the finish phase.

buildlib’s two-phase strategy is intended to support cross compilation. A Cyclone compiler on one architecture can compile to a second architecture provided it has the other architecture’s Cyclone header files. These headers can be generated on the first architecture from the output of the gather phase on the second architecture. This is more general than just having the second architecture’s Cyclone headers, because it permits works even in the face of some changes in the spec file or buildlib itself (which would change the other architecture’s Cyclone headers).

-gatherscript The -gatherscript flag tells buildlib to output a shell script that, when executed, performs buildlib’s gather phase. This is useful when porting Cyclone to an unsupported architecture, where buildlib itself does not yet work. The script can be executed on the unsupported architecture, and the result can be moved to a supported architecture, which can then cross-compile itself to the new architecture.