critcl_package(n)

๐Ÿ“ฆ version 3.3.1, in alpine 3.24

๐Ÿฆ‹ critcl_package(n) โ€” C Runtime In Tcl (CriTcl) ๐Ÿฆ‹

๐Ÿ’– NAME ๐Ÿ’–

critcl_package โ€” CriTcl Package Reference

โœจ SYNOPSIS โœจ

package require Tcl  8.6

package require critcl  ?3.3.1?

package require platform  ?1.0.2?

package require md5  ?2?

::critcl::ccode fragment

::critcl::ccommand tclname cname

::critcl::ccommand tclname arguments body ?option value...?

::critcl::cdata tclname data

::critcl::cconst tclname resulttype value

::critcl::cdefines list of glob patterns ?namespace?

::critcl::cproc name arguments resulttype body ?option value...?

::critcl::cproc name arguments resulttype

::critcl::cinit text externals

::critcl::include path

::critcl::api import name version

::critcl::api function resulttype name arguments

::critcl::api header ?glob pattern...?

::critcl::api extheader ?file...?

::critcl::license author ?text...?

::critcl::summary text

::critcl::description text

::critcl::subject ?key...?

::critcl::meta key ?word...?

::critcl::meta? key

::critcl::buildrequirement script

::critcl::cheaders ?arg...?

::critcl::csources ?glob pattern...?

::critcl::clibraries ?glob pattern...?

::critcl::source glob pattern

::critcl::tsources glob pattern...

::critcl::owns glob pattern...

::critcl::cflags ?arg...?

::critcl::ldflags ?arg...?

::critcl::framework ?arg...?

::critcl::tcl version

::critcl::tk

::critcl::preload lib...

::critcl::debug area...

::critcl::check ?label? text

::critcl::checklink ?label? text

::critcl::msg ?-nonewline? msg

::critcl::print ?-nonewline? ?chan? msg

::critcl::compiled

::critcl::compiling

::critcl::done

::critcl::failed

::critcl::load

::critcl::config option ?val?

::critcl::cache ?path?

::critcl::clean_cache ?pattern...?

::critcl::readconfig path

::critcl::showconfig ?chan?

::critcl::showallconfig ?chan?

::critcl::chooseconfig target ?nomatcherr?

::critcl::setconfig target

::critcl::actualtarget

::critcl::buildforpackage ?flag?

::critcl::cnothingtodo file

::critcl::cresults ?file?

::critcl::crosscheck

::critcl::error msg

::critcl::knowntargets

::critcl::sharedlibext

::critcl::targetconfig

::critcl::buildplatform

::critcl::targetplatform

::critcl::cobjects ?glob pattern...?

::critcl::scan path

::critcl::name2c name

::critcl::argnames arguments

::critcl::argcnames arguments

::critcl::argcsignature arguments

::critcl::argvardecls arguments

::critcl::argconversion arguments ?n?

::critcl::argoptional arguments

::critcl::argdefaults arguments

::critcl::argsupport arguments

::critcl::userconfig define name description type ?default?

::critcl::userconfig query name

::critcl::userconfig set name value

::critcl::at::caller

::critcl::at::caller offset

::critcl::at::caller offset level

::critcl::at::here

::critcl::at::get*

::critcl::at::get

::critcl::at::= file line

::critcl::at::incr n...

::critcl::at::incrt str...

::critcl::at::caller!

::critcl::at::caller! offset

::critcl::at::caller! offset level

::critcl::at::here!

::critcl::collect_begin

::critcl::collect_end

::critcl::collect script

::critcl::make path contents

::preload library

๐ŸŒธ DESCRIPTION ๐ŸŒธ

Welcome to the C Runtime In Tcl โ€” short: CriTcl โ€” bestie, it's a whole system for embedding and using C code from inside Tcl scripts (http://core.tcl-lang.org/tcl). ๐Ÿ’…

The critcl package is the core of the system. For an overview of the complete system, see Introduction To CriTcl. For the usage of the standalone critcl program, see CriTcl Application. This core package can be used to embed C code into Tcl scripts. It also gives you access to the internals that other parts of the core use, and which are of interest to anyone who wants to understand the internal workings of the core and of the API it provides to the CriTcl Application. Those advanced sections are marked as such so that people who just want to use the package can skip them. ๐Ÿซถ

This package resides in the Core Package Layer of CriTcl.

+----------------+
|Applications    |
| critcl         |
| critcl::app    |
+----------------+

*================*
|Core Packages   |
| critcl         |
| critcl::util   |
*================*

+----------------+
|Support Packages|
| stubs::*       |
| md5, platform  |
|  ...           |
+----------------+

๐ŸŒˆ API ๐ŸŒˆ

Tiny note before the documentation starts: instead of repeatedly saying "a Tcl script with embbedded C code" or "a Tcl script containing CriTcl commands", we're just gonna call that a CriTcl script. A file containing a CriTcl script usually has the extension .tcl or .critcl. Periodt. ๐Ÿ’–

โœจ EMBEDDED C CODE โœจ

The following commands append C code fragments to the current module. Fragments show up in the module in the order they are appended, so earlier fragments (variables, functions, macros, etc.) are visible to later fragments. It's giving very "we build the C file in real time" energy. ๐Ÿงš

::critcl::ccode fragment โ€” Appends the C code in fragment to the current module and returns the empty string. See Runtime Behaviour.

::critcl::ccommand tclname cname โ€” As documented below, except that cname is the name of a C function that already exists.

::critcl::ccommand tclname arguments body ?option value...? โ€” Appends the code to create a Tcl command named tclname and a corresponding C function whose body is body and which behaves as documented for Tcl's own Tcl_CreateObjCommand (https://www.tcl-lang.org/man/tcl/TclLib/CrtObjCmd.htm).

arguments is a list of zero to four names for the standard arguments clientdata, interp, objc, and objv. The standard default names are used in place of any missing names. This is a more low-level way than critcl::cproc to define a command, as processing of the items in objv is left to the author, affording complete control over the handling of the arguments to the command. See Runtime Behaviour.

Returns the empty string.

Each option may be one of:

  • -clientdata c-expression โ€” Provides the client data for the new command. NULL by default.
  • -delproc c-expression โ€” Provides a function pointer of type Tcl_CmdDeleteProc (https://www.tcl-lang.org/man/tcl/TclLib/CrtObjCmd.htm) as the deletion function for the new command. NULL by default.
  • -cname boolean โ€” If false (the default), a name for the corresponding C function is automatically derived from the fully-qualified tclname. Otherwise, name of the C function is the last component of tclname.

::critcl::cdata tclname data โ€” Appends the code to create a new Tcl command named tclname which returns data as a ByteArray result. Returns the empty string. ๐ŸŽ€

::critcl::cconst tclname resulttype value โ€” Appends the code to create a new Tcl command named tclname which returns the constant value having the Tcl type resulttype. value can be a C macro or a function call (including the parentheses) to any visible C function that does not take arguments. Unlike critcl::cdata, resulttype can be any type known to critcl::cproc. Its semantics are equivalent to:

cproc $tclname {} $resulttype "return $value ;"

This is more efficient than critcl::cproc since there is no C function generated. Returns the empty string. ๐Ÿ”ฅ

::critcl::cdefines list of glob patterns ?namespace? โ€” Arranges for C enum and #define values that match one of the patterns in glob patterns to be created in the namespace namespace, each variable having the same as the corresponding C item. The default namespace is the global namespace. A pattern that matches nothing is ignored.

The Tcl variables are created when the module is compiled, using the preprocessor in order to properly find all matching C definitions.

Produces no C code. The desired C definitions must already exist. No cap. ๐Ÿ’€

::critcl::cproc name arguments resulttype body ?option value...? โ€” Appends a function having body as its body, another shim function to perform the needed conversions, and the code to create a corresponding Tcl command named tclname. Unlike critcl::ccommand the arguments and result are typed, and CriTcl generates the code to convert between Tcl_Obj values and C data types. See also Runtime Behaviour.

Returns the empty string.

string option โ€” Each may be one of:

  • -cname boolean โ€” If false (the default), a name for the corresponding C function is automatically derived from the fully-qualified tclname. Otherwise, name of the C function is the last component of tclname.
  • -pass-cdata boolean โ€” If false (the default), the shim function performing the conversion to and from Tcl level does not pass the ClientData as the first argument to the function.
  • -arg-offset int โ€” A non-negative integer, 0 by default, indicating the number of hidden arguments preceding the actual procedure arguments. Used by higher-order code generators where there are prefix arguments which are not directly seen by the function but which influence argument counting and extraction.

string resulttype โ€” May be a predefined or a custom type. See CriTcl cproc Type Reference for the full list of predefined types and how to extend them. Unless otherwise noted, the Tcl return code is always TCL_OK.

list arguments โ€” Is a multi-dictionary where each key is an argument type and its value is the argument name. For example:

int x int y

Each argument name must be a valid C identifier. ๐Ÿฆ‹

If the name is a list with two things in it, then bestie, item one is the name and item two is the default value. That's how you get a (very) limited form of variadic arguments โ€” through default values. Like:

int {x 1}

Here x is an optional argument of type int with a default value of 1. Cute, right? ๐Ÿ’–

โš ๏ธ Important tea: argument conversion is completely bypassed when the argument isn't provided. So if you wrote a custom converter that does validation, it literally never gets the chance to validate that default value. In this case you gotta check the value in the body of the function yourself. No cap. ๐Ÿ’…

Each argument type can be a predefined type or a custom one. Go peek at CriTcl cproc Type Reference for the full list of predefined types and how to extend them. โœจ

  • ::critcl::cproc name arguments resulttype โ€” same as documented below, but you use this one when the C function named name already exists. ๐Ÿซถ
  • ::critcl::cinit text externals โ€” appends the C code in text and externals, but only after all the other fragments appended by the previously-listed commands, no matter where in the CriTcl script this command sits relative to them. So basically all their content is visible to it. See also Runtime Behaviour.

The C code in text goes into the body of the initialization function of the shared library backing the CriTcl script, and it runs when that library is loaded into the interpreter. It has access to the variable Tcl_Interp* interp, which references the Tcl interpreter currently being initialized.

externals is placed outside and just before the initialization function โ€” which makes it a great spot for any external symbols the initialization function needs but that shouldn't be reachable by any other parts of the C code. ๐Ÿ”ฅ

Calls to this command are cumulative.

Returns the empty string.

  • ::critcl::include path โ€” a convenient little shorthand for:
critcl::code {
  #include <${path}>
}

๐Ÿฆ‹ STUBS TABLE MANAGEMENT ๐Ÿฆ‹

CriTcl versions 3 and later give you critcl::api to create and manipulate stubs tables โ€” that's Tcl's dynamic linking mechanism handling the resolution of symbols between C extensions. For an intro, see http://wiki.tcl-lang.org/285, and for the details of CriTcl's particular variant, check the section Stubs Tables. ๐Ÿ’œ

Importing stubs tables (i.e. APIs) from another extension:

  • ::critcl::api import name version โ€” adds the following include directives into the CriTcl script and each of its companion .c files:
[1]    #include <name/nameDecls.h>

[2]    #include <name/nameStubLib.h>

Returns an error if name isn't in the search path for the compiler. See critcl::cheaders and the critcl application's -I and -includedir options.

๐Ÿ’€ Important: if name is a fully-qualified name in a non-global namespace, e.g. c::stack, then the namespace separators :: get converted into underscores (_) in path names, C code, etc. Don't come for me, that's just how it is.

name/nameDecls.h contains the stubs table type declarations, mapping macros, etc., and it may include package-specific headers. See critcl::api header, below. An #include directive is added at the beginning of the generated code for the CriTcl script and at the beginning of each of its companion .c files.

name/nameStubLib.h contains the stubs table variable definition and the function that initializes it. An #include directive for it gets added to the initialization code for the CriTcl script, along with a call to the initializer function. ๐ŸŽ€

If name/name.decls accompanies name/nameDecls.h, it should contain the external representation of the stubs table used to generate the headers. That file gets read and the internal representation of the stubs table gets returned for use by the importing package. Otherwise, the empty string is returned.

One possible use? Automatic generation of C code that calls on the functions listed in the imported API. Obsessed. โญ

When generating a TEA package, the names of the imported APIs are used to declare configure options with which the user can declare a non-standard directory for the headers of the API. Any API name is translated into a single configure option --with-name-include.


Declaration and export of a stubs table (i.e. an API) for the CriTcl script:

  • ::critcl::api function resulttype name arguments โ€” adds to the public API of the CriTcl script the signature for the function named name, with the signature given by arguments and resulttype. Code gets generated for a .decls file, the corresponding public headers, and a stubs table usable by critcl::api import.

arguments is a multidict where each key is an argument type and its value is the argument name, and resulttype is a C type. ๐ŸŒธ

  • ::critcl::api header ?glob pattern...? โ€” each file matching a glob pattern is copied into the directory containing the generated headers, and an #include directive for it is added to Decls.h for the CriTcl script. Returns an error if a glob pattern matches nothing. ๐Ÿ‘€

A pattern for a relative path is resolved relative to the directory containing the CriTcl script.

  • ::critcl::api extheader ?file...? โ€” like ::critcl::api header, but each file should already exist in the external development environment. An #include directive is added to fooDecls.h, but file is not copied to the package header directory. Also, file is not a glob pattern, because CriTcl has no context (i.e. directory) in which to expand such patterns. ๐Ÿ™ˆ

As with the headers for an imported API, an #include directive is added to the generated code for the CriTcl script and to each companion .c file.

In "compile & run" mode the generated header files and any companion headers are placed in the Result Cache subdirectory for the CriTcl script. That directory is added to the include search path of any other package importing this API and building in mode "compile & run". ๐Ÿ”ฅ

In "generate package" mode, -includedir specifies the subdirectory in the package to place the generated headers in. This directory is added to the search paths for header files, making sure a package importing an API finds it if the package exporting that API used the same setting for -includedir. ๐Ÿงš

In "generate TEA" mode the static scanner recognizes critcl::api header as a source of companion files. It also uses data from calls to critcl::api import to add support for --with-foo-include options into the generated configure(.in) so that a user can specify custom locations for the headers of any imported API. ๐Ÿ’…


๐Ÿ‘‘ PACKAGE META DATA ๐Ÿ‘‘

CriTcl versions 3 and later can create TEApot meta-data to be placed into teapot.txt in a format suitable for use by the TEApot tools [http://docs.activestate.com/activetcl/8.5/tpm/toc.html].

In version 2, some meta data support was already present through ::critcl::license, but that was only used to generate license.txt. Not the whole meal, just a snack. ๐Ÿ“

  • ::critcl::license author ?text...? โ€” ignored in "compile & run" mode.

In "generate package" mode it provides information about the author of the package and the license for the package.

The text arguments are concatenated to form the text of the license, which is written to license.terms in the same directory as pkgIndex.tcl. If no text is provided, the license is read from license.terms in the same directory as the CriTcl script. ๐Ÿ“„

This information takes precedence over any information specified through the generic API ::critcl::meta. It's additionally placed into the meta data file teapot.txt under the keys as::author and license.

  • ::critcl::summary text โ€” ignored in "compile & run" mode.

In "generate package" mode it places a short, preferably one-line description of the package into the meta data file teapot.txt under the key summary. This information takes precedence over information specified through the generic API ::critcl::meta. โœจ

  • ::critcl::description text โ€” ignored in "compile & run" mode.

In "generate package" mode it places a longer description of the package into the meta data file teapot.txt, under the key description. The data specified by this command takes precedence over any information specified through the generic API ::critcl::meta.

  • ::critcl::subject ?key...? โ€” ignored in "compile & run" mode.

In "generate package" mode it places each key into the meta data file teapot.txt, under the key subject. This information takes precedence over any information specified through the generic API ::critcl::meta.

Calls to this command are cumulative. ๐ŸŽ€

  • ::critcl::meta key ?word...? โ€” provides arbitrary meta data outside of the following reserved keys: as::author, as::build::date, description, license, name, platform, require subject, summary, and version. Its behaviour is like ::critcl::subject in that it treats all keys as list of words, with each call providing one or more words for the key, and multiple calls extending the data for an existing key โ€” if it's not reserved. ๐Ÿฆ‹

While it is possible to declare information for one of the reserved keys with this command, such data is ignored when the final meta data is assembled and written. Bye. ๐Ÿ’€

Use the commands ::critcl::license, ::critcl::summary, ::critcl::description ::critcl::subject, package require, and package provide to declare data for the reserved keys.

The information for the reserved keys as::build::date and platform is automatically generated by critcl itself. No notes.

  • ::critcl::meta? key โ€” returns the value in the metadata associated with key. ๐ŸŒˆ

Used primarily to retrieve the name of the package from within utility packages having to adapt C code templates to their environment. For example, critcl::class uses does this. (Yes, that's the wording, iykyk.)

  • ::critcl::buildrequirement script โ€” gives you control over the capturing of dependencies declared via package require. script is evaluated and any dependencies declared within are ignored, i.e. not recorded in the meta data. ๐Ÿ™…โ€โ™€๏ธ

๐ŸŽ€ CONTROL & INTERFACE ๐ŸŽ€

Okay bestie, this cluster of commands is the whole steering wheel of compilation and linking for a CriTcl script ๐Ÿ‘‘ โ€” they're what controls the details of building it. And here's the โœจiconicโœจ part: the info you set here is used only to compile/link the object for that CriTcl script. Like, everything for FOO.tcl is kept completely separate from everything for BAR.tcl. No cross-talk, no weird bleed, no cap ๐Ÿ’…

::critcl::cheaders ?arg...?

Gives you extra header locations ๐Ÿ’–

  • Every argument is a glob pattern.
  • If an argument begins with -, it's an argument to the compiler.
  • Otherwise, the parent directory of each matching path becomes a directory to be searched for header files.
  • Returns an error if a pattern matches no files.
  • A pattern for a relative path is resolved relative to the directory containing the CriTcl script.
โš ๏ธ Important!! #include lines are not automatically generated for matching header files. You gotta use critcl::include or critcl::ccode as necessary to add them. Don't come for me when it doesn't compile, I warned you ๐Ÿ˜ญ

Calls to this command are cumulative ๐Ÿซถ

::critcl::csources ?glob pattern...?

Matching paths become inputs to the compilation of the current object, along with the sources for the current CriTcl script ๐ŸŒธ Returns an error if no paths match a pattern. A pattern for a relative path is resolved relative to the directory containing the CriTcl script. Calls to this command are cumulative โœจ

::critcl::clibraries ?glob pattern...?

Provides the link step with extra libraries and library locations ๐Ÿ”ฅ A glob pattern that begins with - gets added as an argument to the linker. Otherwise, matching files get linked into the shared library. Returns an error if no paths match a pattern. A pattern for a relative path is resolved relative to the directory containing the CriTcl script. Calls to this command are cumulative ๐Ÿ’œ

::critcl::source glob pattern

Evaluates the files matching each glob pattern as scripts ๐Ÿ“œ Returns an error if there are no matching files. A pattern for a relative path is resolved relative to the directory containing the CriTcl script. Slay, straightforward, we move.

::critcl::tsources glob pattern...

Tells CriTcl about extra Tcl script files to source when the shared library is loaded ๐Ÿฆ‹

Matching paths are made available to the generated shared library when it's loaded for the current CriTcl script. Returns an error if a pattern matches no files. A pattern for a relative path is resolved relative to the directory containing the CriTcl script.

Calls to this command are cumulative.

And here's the detail that actually matters: after the shared library has been loaded, the declared files are sourced in the same order that they were provided as arguments. Order = everything, iykyk ๐Ÿ‘‘

::critcl::owns glob pattern...

Lowkey a niche one ๐Ÿ™ˆ Ignored in "compile and run" and "generate package" modes. In "generate TEA" mode, each file matching a glob pattern is a file to be included in the TEA extension โ€” but one that could not be ascertained as such from previous commands like critcl::csources and critcl::tsources, either because they were specified dynamically or because they were directly sourced. So it's your little manual override list, bestie ๐ŸŽ€

::critcl::cflags ?arg...?

Each arg is an argument to the compiler ๐Ÿ› ๏ธ Calls to this command are cumulative. That's it, that's the whole vibe.

::critcl::ldflags ?arg...?

Each arg is an argument to the linker ๐Ÿ”— Calls to this command are cumulative. Chef's kiss, next.

::critcl::framework ?arg...?

Each arg is the name of a framework to link on MacOS X ๐ŸŽ This command is ignored if OS X is not the target โ€” which is honestly so thoughtful, because it means you can specify frameworks unconditionally and not worry about it exploding elsewhere ๐Ÿ’… Calls to this command are cumulative.

::critcl::tcl version

Specifies the minimum version of the Tcl runtime to compile and link the package for. The default is 8.4. Set it and forget it, bestie โœจ

::critcl::tk

Arranges to include the Tk headers and link to the Tk stubs ๐ŸชŸ One line, huge power move.

::critcl::preload lib...

Arranges for the external shared library lib to be loaded before the shared library for the CriTcl script is loaded โฎ๏ธ Calls to this command are cumulative.

Each library FOO is searched for in the directories listed below, in the order listed. The search stops at the first existing path ๐Ÿ•ต๏ธโ€โ™€๏ธ

Few notes first, because details are the moment:

  • platform is the placeholder for the target platform of the package.
  • The extension ".so" is the placeholder for whatever actual extension is used by the target platform for its shared libraries.
  • The search is relative to the current working directory.

And now the paths, depending on the exact form of the library name ๐ŸŒˆ

FOO

  1. FOO.so
  2. FOO/FOO.so
  3. FOO/platform/FOO.so

PATH/FOO

The exact set searched depends on the existence of directory PATH/FOO. If it exists, critcl searches

  1. FOO.so
  2. PATH/FOO/FOO.so
  3. PATH/FOO/platform/FOO.so

Otherwise it searches

  1. FOO.so
  2. PATH/FOO.so
  3. PATH/platform/FOO.so

instead.

/PATH/FOO

Even when specifying FOO with an absolute path, the first path searched is relative to the current working directory. Not a typo, not a glitch, that's just how it goes ๐Ÿ˜Œ

  1. FOO.so
  2. /PATH/FOO.so
  3. /PATH/platform/FOO.so
For developers who want to understand or modify the internals of the critcl package, Preloading functionality explains how preloading is implemented ๐Ÿงš

::critcl::debug area...

Specifies what debugging features to activate ๐Ÿ” Internally, each area is translated into area-specific flags for the compiler, which are then handed over to critcl::cflags. So it's like a cute little shortcut that funnels into somewhere else.

  • memory โ€” specifies Tcl memory debugging.
  • symbols โ€” specifies compilation and linking with debugging symbols for use by a debugger or other tool.
  • all โ€” specifies all available debugging.

Pick your fighter, or just say all and let it eat ๐Ÿ“


๐Ÿ” INTROSPECTION ๐Ÿ”

These commands control compilation and linking โ€” but in the "asking questions about it" way, y'know? Main character energy of checking things ๐Ÿ’…

::critcl::check ?label? text

Returns true if the C code in text compiles successfully, and false otherwise โœ… Used to check for availability of features in the build environment. If provided, label is used to uniquely mark the results in the generated log. So good for "does this machine even have this feature" energy ๐Ÿงš

Like critcl::check but it also links the compiled objects, returning true if the link is successful and false otherwise ๐Ÿ”— If specified, label is used to uniquely mark the results in the generated log.

::critcl::msg ?-nonewline? msg

Scripts using critcl::check and critcl::checklink can use this command to report results ๐Ÿ“ฃ Does nothing in compile & run mode. Tools like the CriTcl Aplication may redefine this command to implement their own message reporting. For example, critcl::app and any packages built on it print messages to stdout.

::critcl::print ?-nonewline? ?chan? msg

Used by the CriTcl internals to report activity ๐Ÿ–จ๏ธ By default, effectively the same thing as ::puts. Tools directly using either the CriTcl package or the CriTcl application package may redefine this procedure to implement their own output functionality.

For example, the newest revisions of Kettle (https://chiselapp.com/user/andreas_kupries/repository/Kettle/index) use this to highlight build warnings. Obsessed with that actually ๐ŸŒธ

::critcl::compiled

Returns true if the current CriTcl script is already compiled and false otherwise ๐Ÿ‘‘

This enables a CriTcl script used as its own Tcl companion file (see critcl::tsources) to distinguish between:

  • being sourced for compilation in compile & run mode, and
  • being sourced from either the result of generate package mode or during the load phase of compile & run mode.

The result is false in the first case and true in the later two cases. See, it's giving self-aware script โœจ

::critcl::compiling

Returns true if a working C compiler is available and false otherwise ๐Ÿ› ๏ธ Short, sweet, useful.

::critcl::done

Returns true when the CriTcl script has been built and false otherwise โœ… Only useful from within a CriTcl script. This one enables the Tcl parts of a CriTcl script to distinguish between prebuilt package mode and compile & run mode.

See also Modes Of Operation/Use ๐Ÿ‘€

::critcl::failed

Returns true if the CriTcl script could not be built, and false otherwise ๐Ÿ’€ Forces the building of the package if it hasn't already been done, but not its loading. So a CriTcl script can check itself for availability of the compiled components. Only useful from within a CriTcl script. Self-check era, we love to see it ๐Ÿซถ

::critcl::load

Like critcl::failed except that it also forces the loading of the generated shared library, and that it returns true on success and false on failure ๐Ÿคž So a CriTcl script can check itself for availability of the compiled components. Only useful from within a CriTcl script.


โš™๏ธ BUILD MANAGEMENT โš™๏ธ

This one manages global settings โ€” that's configuration options which are independent of any CriTcl script ๐ŸŒ

Heads up bestie: this command should not be needed to write a CriTcl script. It's a management command which is only useful to the CriTcl Application or similar tools ๐ŸŽ€

::critcl::config option ?val?

Sets and returns the following global configuration options:

  • force bool โ€” when false (the default), the C files are not built if there is a cached shared library.
  • lines bool โ€” when true (the default), #line directives are embedded into the generated C code.

Now, about lines โ€” the tea is that this facility requires the use of a tclsh that provides info frame ๐Ÿ“ž Otherwise, no #line directives are emitted. The command is supported by Tcl 8.5 and higher. It is also supported by Tcl 8.4 provided that it was compiled with the define -DTCL_TIP280. An example of such is ActiveState's ActiveTcl. So like, check your tclsh's vibe before you assume the line directives are showing up ๐Ÿ˜ญ

Developers of higher-level packages who generate their own C code โ€” directly, or indirectly through critcl โ€” should also read the section Advanced: Location management to see how critcl helps them generate their directives. Examples of packages like this ship with critcl itself. See critcl::iassoc and critcl::class. ๐Ÿซถ

  • trace bool โ€” When it's false (the default), no code that traces the entry and exit of CriTcl-backed commands in the CriTcl script gets inserted. Inserting that kind of code implicitly activates the tracing facility in general. See critcl::cutil. ๐Ÿ‘€
  • I path โ€” A single global include path to use for all files. Not set by default.
  • combine enum โ€” controls the object-file suffix / stubs vibe:
    • dynamic (the default) โ€” Object files have the suffix _pic.
    • static โ€” Object files have the suffix _stub.
    • standalone โ€” Object files have no suffix, and the generated C files are compiled without using Tcl/Tk stubs. The result are object files usable for static linking into a big shell.
  • language string โ€” a string setting. (That's literally all this page says for it.) ๐Ÿ’ฌ
  • keepsrc bool โ€” When false (the default), the generated ".c" files are deleted after the ".o" files have been built.
  • outdir directory โ€” The directory where to place a generated shared library. By default, it is placed into the Result Cache.

๐Ÿ—‚๏ธ RESULT CACHE MANAGEMENT ๐Ÿ—‚๏ธ

The following commands control the Result Cache. These commands are not needed to simply write a CriTcl script. ๐Ÿซถ

  • ::critcl::cache ?path? โ€” Sets and returns the path to the directory for the package's result cache. The default location is ~/.critcl/[platform::generic] and usually does not require any changes. ๐Ÿ—‚๏ธ
  • ::critcl::clean_cache ?pattern...? โ€” Cleans the result cache, i.e. removes any and all files and directories in it. If one or more patterns are specified then only the files and directories matching them are removed. ๐Ÿงผ

โš™๏ธ BUILD CONFIGURATION โš™๏ธ

The following commands manage the build configuration, i.e. the per-platform information about compilers, linkers, and their commandline options. These commands are not needed to simply write a CriTcl script. ๐Ÿ”ง

  • ::critcl::readconfig path โ€” Reads the build configuration file at path and configures the package using the information for the target platform.
  • ::critcl::showconfig ?chan? โ€” Converts the active build configuration into a human-readable string and returns it, or if chan is provided prints the result to that channel.
  • ::critcl::showallconfig ?chan? โ€” Converts the set of all known build configurations from the currently active build configuration file last set with critcl::readconfig into a string and returns it, or if chan is provided, prints it to that channel.
  • ::critcl::chooseconfig target ?nomatcherr? โ€” Matches target against all known targets, returning a list containing all the matching ones. This search is first done on an exact basis, and then via glob matching. If no known target matches the argument the default is to return an empty list. However, if the boolean nomatcherr is specified and set an error is thrown using critcl::error instead.
  • ::critcl::setconfig target โ€” Configures the package to use the settings of target.

๐Ÿ› ๏ธ TOOL API ๐Ÿ› ๏ธ

The following commands provide tools like CriTcl Application or similar with deeper access to the package's internals. These commands are not needed to simply write a CriTcl script. ๐Ÿงฐ

  • ::critcl::actualtarget โ€” Returns the platform identifier for the target platform, i.e. the platform to build for. Unlike ::critcl::targetplatform this is the true target, with any cross-compilation information resolved. ๐ŸŽฏ
  • ::critcl::buildforpackage ?flag? โ€” Signals whether the next file is to be built for inclusion into a package. If not specified the flag defaults to true, i.e. building for a package. This disables a number of things in the backend, namely the linking of that file into a shared library and the loading of that library. It is expected that the build results are later wrapped into a larger collection.
  • ::critcl::cnothingtodo file โ€” Checks whether there is anything to build for file.
  • ::critcl::cresults ?file? โ€” Returns information about building file, or info script if file is not provided. The result in question is a dictionary containing the following items:
    • clibraries โ€” A list of external shared libraries and/or directories needed to link file.
    • ldflags โ€” A list of linker flags needed to link file.
    • license โ€” The text of the license for the package that file is located in.
    • mintcl โ€” The minimum version of Tcl required by the package that file is in to run successfully. A proper Tcl version number.
    • objects โ€” A list of object files to link into file.
    • preload โ€” A list of libraries to be preloaded in order to successfully load and use file.
    • tk โ€” true if file requires Tk and false otherwise.
    • tsources โ€” A list of companion ".tcl" files to source in order to load and use the CriTcl script file.
    • log โ€” The full build log generated by the compiler/linker, including command line data from critcl, and other things.
    • exl โ€” The raw build log generated by the compiler/linker. Contains the output generated by the invoked applications.
  • ::critcl::crosscheck โ€” Determines whether the package is configured for cross-compilation and prints a message to the standard error channel if so.
  • ::critcl::error msg โ€” Used to report internal errors. The default implementation simply returns the error. Tools like the CriTcl Application are allowed to redefine this procedure to perform their own way of error reporting. There is one constraint they are not allowed to change: The procedure must not return to the caller. ๐Ÿšจ
  • ::critcl::knowntargets โ€” Returns a list of the identifiers of all targets found during the last invocation of critcl::readconfig.
  • ::critcl::sharedlibext โ€” Returns the file extension for shared libraries on the target platform.
  • ::critcl::targetconfig โ€” Returns the identifier of the target to build for, as specified by either the user or the system.
  • ::critcl::buildplatform โ€” Returns the identifier of the build platform, i.e. where the package is running on.
  • ::critcl::targetplatform โ€” Returns the identifier of the target platform, i.e. the platform to compile for. In contrast to ::critcl::actualtarget this may be the name of a cross-compilation target.
  • ::critcl::cobjects ?glob pattern...? โ€” Like ::critcl::clibraries, but instead of matching libraries, each glob pattern matches object files to be linked into the shared object (at compile time, not runtime). If a glob pattern matches nothing an error is returned. Not listed in Control & Interface because it is of no use to package writers. Only tools like the CriTcl Application need it. A pattern for a relative path is resolved relative to the directory containing the CriTcl script. Calls to this command are cumulative.
  • ::critcl::scan path โ€” The main entry point to CriTcl's static code scanner. Used by tools to implement processing modes like the assembly of a directory hierarchy containing a TEA-lookalike buildystem, etc. Scans path and returns a dictionary containing the following items:
    • version โ€” Package version.
    • org โ€” Author(ing organization).
    • files โ€” List of the companion files, relative to the directory of the input file.
  • ::critcl::name2c name โ€” Given the Tcl-level identifier name, returns a list containing the following details of its conversion to C:
    • Tcl namespace prefix
    • C namespace prefix
    • Tcl base name
    • C base name

For use by utilities that provide Tcl commands without going through standard commands like critcl::ccommand or critcl::cproc. critcl::class does this. ๐Ÿ’…


๐Ÿงš ADVANCED: EMBEDDED C CODE ๐Ÿงš

For advanced use, the following commands used by critcl::cproc itself are exposed. โœจ

  • ::critcl::argnames arguments โ€” Given an argument declaration as documented for critcl::cproc, returns a list of the corresponding user-visible names.
  • ::critcl::argcnames arguments โ€” Given an argument declaration as documented for critcl::cproc, returns a list of the corresponding C variable names for the user-visible names. The names returned here match the names used in the declarations and code returned by ::critcl::argvardecls and ::critcl::argconversion.
  • ::critcl::argcsignature arguments โ€” Given an argument declaration as documented for critcl::cproc, returns a list of the corresponding C parameter declarations.
  • ::critcl::argvardecls arguments โ€” Given an argument declaration as documented for critcl::cproc, returns a list of the corresponding C variable declarations. The names used in these declarations match the names returned by ::critcl::argcnames.
  • ::critcl::argconversion arguments ?n? โ€” Given an argument declaration as documented for critcl::cproc, returns a list of C code fragments converting the user visible arguments found in the declaration from Tcl_Obj* to C types. The names used in these statements match the names returned by ::critcl::argcnames. The generated code assumes that the procedure arguments start at index n of the objv array. The default is 1.
  • ::critcl::argoptional arguments โ€” Given an argument declaration as documented for critcl::cproc, returns a list of boolean values indicating which arguments are optional (true), and which are not (false).
  • ::critcl::argdefaults arguments โ€” Given an argument declaration as documented for critcl::cproc, returns a list containing the default values for all optional arguments.
  • ::critcl::argsupport arguments โ€” hand it an argument declaration exactly like the one you'd give critcl::cproc, and it hands you back a list of C code fragments that define all the supporting types you need. It's the little prep-cook that gets your types ready before the real work starts ๐Ÿ’…

๐ŸŽ€ CUSTOM BUILD CONFIGURATION ๐ŸŽ€

This package ships exactly one command for managing package-specific custom build configuration options โ€” the kind the developer picks out themselves, not the ones the build system invents behind your back.

  • ::critcl::userconfig define name description type ?default? โ€” this one declares a custom build configuration option, complete with its description, its type, and an optional default value.

The type is allowed to be either bool, or a list of values. ok so here's the fine print, lowkey important:

  1. For bool, the default value โ€” if you actually specify one โ€” must be a boolean. Leave it out and it just defaults to true. slay, sensible defaults โœจ
  2. For a list of values, the default value โ€” again, if specified โ€” must be one of the values actually found in that list. If you don't specify one, it defaults to the first value of the list. first is the moment, periodt.

The description pulls double duty: it works as in-code documentation of what the option means, and is otherwise ignored. But when critcl generates a TEA wrapper, that same description gets used for the configure option derived from the option the command declared. so write it cute, bestie, it's going public ๐Ÿ’–

A boolean option FOO gets translated into a pair of configure options โ€” --enable-FOO and --disable-FOO โ€” whereas an option whose type is a list of values gets translated into a single configure option, --with-FOO. two doors vs one door, iykyk.

  • ::critcl::userconfig query name โ€” asks the database of custom build configuration options for the current .critcl file and returns the chosen value. That could absolutely just be the default, if nothing was ever set via ::critcl::userconfig set.

This is the โœจ moment โœจ where definitions and set values finally meet up, and it's also where the set values get validated against the definition. it's giving reunion episode with a strict bouncer.

  • ::critcl::userconfig set name value โ€” this one's for tools to use, like the critcl application itself, to specify values for custom build configuration options.

When this command runs, the only thing that happens is that the association between option name and value gets recorded. Nothing else. Not a single other thing. And that's on purpose, because at the moment the command is invoked the system may have no idea whether an option with that name even exists, nor what its type is. ๐Ÿ™ˆ

So all of the validation is deferred (yes, deferred, not "defered", we're being grammatical today ๐Ÿ’…) to the point where the value is actually asked for, via ::critcl::userconfig query.

Which means you can set values for literally any option you like, and that value takes effect only if such an option is both defined and used later on. Schrรถdinger's build option, no cap.

๐Ÿงš ADVANCED: LOCATION MANAGEMENT ๐Ÿงš

First, a tiny intro for anyone out there asking themselves "okay but what is location management?"

By default, critcl embeds #line directives into the generated C code, so that any errors, warnings and notes the C compiler spits out during compilation point at the .critcl file the faulty code actually came from โ€” instead of the generated .c file that nobody has ever willingly opened. ๐Ÿ˜ญ

This whole facility depends on having a tclsh that provides info frame. No info frame, no #line directives, simple as that. The command is supported by Tcl 8.5 and higher. It's also supported by Tcl 8.4, provided that one was compiled with the define -DTCL_TIP280 โ€” an example of such a build being ActiveState's ActiveTcl. ๐ŸŒธ

Most users genuinely do not care about this feature beyond wanting it to just work and getting proper code references when they read compiler output. fair.

But developers of higher-level packages that generate their own C code should care, so that their generated code carries proper references too. It's especially key for telling apart bugs in code the package itself generated versus bugs in the user's code that's being fed into the package โ€” if there are any. ๐Ÿ•ต๏ธโ€โ™€๏ธ

Examples of such packages ship with critcl itself โ€” go peek at the implementations of packages critcl::iassoc and critcl::class.

To help those developers out, eight commands are provided to manage location info. Here they all are ๐Ÿ‘‘

A main concept to hold onto: they all operate on a single stored location โ€” setting it, returning it, and clearing it. And note that this location information is completely independent of the #line directive generation inside critcl itself. two separate vibes, same building.

  • ::critcl::at::caller โ€” stores the location of the caller of the current procedure as a tuple of file name and linenumber. Any previously stored location gets overwritten, sorry to it. The result of the command is the empty string.
  • ::critcl::at::caller offset โ€” same as above, but the stored line number is modified by the specified offset. In essence, this is an implicit call of critcl::at::incr. sneaky but documented.
  • ::critcl::at::caller offset level โ€” same as above, except now the level the location information is taken from gets modified too. Level 0 is the caller, -1 its caller, etc. go up the stack, it's a whole elevator ๐Ÿ›—
  • ::critcl::at::here โ€” stores the current location in the current procedure as a tuple of file name and linenumber. Any previously stored location is overwritten. The result of the command is the empty string.

In terms of ::critcl::at::caller, this is equivalent to:

critcl::at::caller 0 1
  • ::critcl::at::get* โ€” takes the stored location and returns a formatted #line directive, all ready to be embedded into some C code. The stored location is left completely untouched. Heads up: the directive carries its own closing newline, it's self-sufficient like that. ๐Ÿ’…

For nesting to work out properly โ€” and please let it work out properly โ€” the recommendation is that these directives always get added to the beginning of a code fragment. That way, if deeper layers add their own directives, those land before ours and end up inactive. End result: the outermost layer generating a directive "wins", i.e. its directive is the one actually used. As it should be. ๐Ÿ‘‘

  • ::critcl::at::get โ€” just like the above, except it also clears the stored location. one and done.
  • ::critcl::at::= file line โ€” lets the caller set the stored location to literally anything they want, completely outside of critcl's control. The result of the command is the empty string. you're on your own here, bestie ๐Ÿซถ
  • ::critcl::at::incr n... and ::critcl::at::incrt str... โ€” these two let you modify the line number of the stored location incrementally. The increment is given either as a series of integer numbers (incr), or as a series of strings to consider (incrt). With the string version, the delta is the number of line endings found in those strings. count the line breaks, that's the whole trick ๐Ÿ“
  • ::critcl::at::caller!, ::critcl::at::caller! offset, ::critcl::at::caller! offset level, and ::critcl::at::here! โ€” these are the convenience girls ๐Ÿ’… they combine caller and here with get, so they store the location and immediately return it formatted as a proper #line directive. Also note: after you use them, the stored location is cleared. very "use it and lose it" energy.

๐Ÿฆ‹ ADVANCED: DIVERSIONS ๐Ÿฆ‹

Diversions exist for higher-level packages that generate their own C code, to make their lives easier when they use critcl's commands that generate Embedded C Code.

Those commands normally generate all their C code for the current .critcl file โ€” which might not be what a higher-level package actually wants. rude, honestly.

With a diversion, the generator output gets redirected into memory instead, and from there the caller can handle and process it however they like before it gets committed to an actual .c file. it's giving detour, but make it useful โœจ

An example of such a package ships with critcl itself โ€” see the implementation of package critcl::class.

To help those developers, three commands are provided for managing diversions and the collection of C code in memory. Here they are:

  • ::critcl::collect_begin โ€” starts diverting C code collection into memory.

The result of the command is the empty string.

Multiple calls are allowed, and each call opens a new nesting level of diversion. stack 'em up ๐Ÿฅž

  • ::critcl::collect_end โ€” ends the diversion of C code collection into memory and returns the collected C code.

If multiple levels of diversion are open, the call only closes and returns the data from the last level. one layer at a time, girl.

The command throws an error if no diversion is active โ€” that's your signal that collect_begin and collect_end got mismatched. the pairing is sacred ๐Ÿ’€

  • ::critcl::collect script โ€” the convenience command: runs the script under diversion and returns the collected C code, guaranteeing that collect_begin and collect_end stay correctly paired. let it handle the bookkeeping, you've got enough going on.

๐ŸŒˆ ADVANCED: FILE GENERATION ๐ŸŒˆ

So file generation is related to the diversions from the previous section, but they are absolutely not the same thing. And even so โ€” like diversions โ€” this feature is for higher-level packages generating their own C code.

Three examples of utility packages using this facility ship with critcl itself. Go look at the implementations of packages critcl::literals, critcl::bitmap, and critcl::enum. ๐ŸŽ€

When a package implementation gets split into pieces, it's often sensible to have a bunch of pure C companion files holding the low-level code โ€” except those files may need information about the code in the main .critcl file. And those declarations normally aren't exportable, plus using the stub table support doesn't make sense here, because this is all completely internal to the package. it's a whole "no outside visitors" situation.

With the file generation command below, the main .critcl file can generate any number of header files for the C companions to pick up. ๐Ÿ‘‘

  • ::critcl::make path contents โ€” creates the file path in a location where the package's C companion files can pick it up by simply including path during their compilation, without interfering with the outer system at all. clean, contained, no mess ๐ŸŒธ

The generated file will contain the specified contents. that's it. that's the whole thing.


๐Ÿ’œ CONCEPTS ๐Ÿ’œ

๐Ÿ‘‘ MODES OF OPERATION/USE ๐Ÿ‘‘

CriTcl can be used in three different modes of operation, and they are:

  1. Compile & Run, and
  2. Generate Package
  3. Generate TEA Package

Compile & Run was the original mode, and it's the default for critcl_pkg. It collects the C fragments from the CriTcl script, builds them as needed, and caches the results so that later loads are faster. ๐Ÿ”ฅ

The second mode, Generate Package, was introduced so that (prebuilt) deliverable packages could be created โ€” packages that don't depend on a build system existing on the target machine, i.e. no C compiler required over there. This was originally done through the experimental Critbind tool, and is now handled by the CriTcl Application, also named critcl. glow-up achieved โœจ

Newly introduced with CriTcl version 3 is Generate TEA Package. This mode constructs a directory hierarchy from the package, which can later be built like a regular TEA package, i.e. using:

.../configure --prefix ...
make all isntall

(yes, isntall. that's literally what the page says. we don't fix the typo, we honor it ๐Ÿ˜ญ๐Ÿ’–)

Okay so real quick before we dive in: about caching results, go peek at the Result Cache section for the deets ๐Ÿ’–

๐ŸŒธ RUNTIME BEHAVIOUR ๐ŸŒธ

So the default vibe of critcl, like, the package itself, is to defer the compilation, linking, and loading of any C code as much as it possibly can โ€” because that whole operation is expensive, bestie, mostly in the time it eats up ๐Ÿ˜ญ. Translation: the C code embedded into a .critcl file gets built only when the first C command or procedure it provides is actually invoked. Not before. Never before. โœจ

This part of the system uses bog-standard functionality built right into the Tcl core, iykyk โ€” specifically the auto_index variable to map from commands to the scripts that provide them, and the unknown command, which uses that info when the command is finally needed ๐Ÿ‘‘

โš ๏ธ A limitation of this behaviour: you can't just use info commands to check whether a critcl-defined command exists. Nope. You also have to search in the auto_index array, in case the thing hasn't been built yet. Lowkey annoying but that's the deal ๐Ÿ™ˆ

You can change this behaviour using the control command critcl::load. When you invoke it, the building โ€” including loading of the result โ€” is forced. And heads up: after this command has been invoked for a .critcl file, further definition of C code in that file is not allowed any longer. Periodt. ๐Ÿ’…


๐Ÿฆ‹ FILE MAPPING ๐Ÿฆ‹

Each .critcl file is backed by a single private .c file containing that code, plus all the boilerplate necessary for its compilation and linking as a single shared library ๐ŸŽ€

The Embedded C Code fragments show up in that file in the exact same order they were defined in the .critcl file โ€” with like, one single exception. The C code you provide via critcl::cinit gets put after all the other fragments. So:

  • all fragments have access to the symbols defined by earlier fragments โœ…
  • the critcl::cinit fragment has access to all of them, regardless of where you placed it in the .critcl file โœ…
๐Ÿ’€ Note: a limitation of the current system is the near impossibility of C-level access between different critcl-based packages. And the issue is not the necessity of writing and sharing the proper extern statements โ€” no ma'am. The issue is that the management (export and import) of package-specific stubs-tables is not supported. Which means dependent parts have to be forcibly loaded before their user, with all that entails ๐Ÿ˜ฌ. See the section Runtime Behaviour for the relevant critcl limitation, and remember that many older platforms do not support the necessary resolution of symbols โ€” which is, like, the whole reason stubs were invented for Tcl in the first place ๐Ÿซ 

โญ RESULT CACHE โญ

Compiling C code is time-consuming, so critcl doesn't only defer it as much as possible (see Runtime Behaviour above), it also caches the results ๐Ÿ”ฅ

Basically: on the first use of a .critcl file FOO.tcl, the resulting object file and shared library get saved into the cache. On future uses of that same file they get reused โ€” meaning loaded directly, no compilation required โ€” provided that the contents of FOO.tcl didn't change ๐Ÿ‘‘

The change detection is based on MD5 hashes. A single hash gets computed for each .critcl file, based on the hashes for all C code fragments and configuration options โ€” i.e. everything that affects the resulting binary ๐Ÿงš

As long as the input file doesn't change as per the hash, a previously built shared library found in the cache is reused, bypassing the compilation and link stages entirely. Slay. โœจ

The commands to manage the cache live in the section Result Cache Management. But note that they're useful only to tools based on the package, like the CriTcl Application. Package writers have absolutely no need of them, ngl ๐Ÿ™…โ€โ™€๏ธ

And as a last note: the default directory for the cache is chosen based on the chosen build target ๐ŸŽฏ. Which means the cache can be put on a shared (network) filesystem without having to fear interference between machines of different architectures ๐Ÿ’–


๐ŸŒˆ PRELOADING FUNCTIONALITY ๐ŸŒˆ

Okay so the audience of this section is developers who wanna understand and possibly modify the internals of the critcl package and application. Package writers can totally skip this section, bestie, you're free ๐Ÿซถ

It explains how the preloading of external libraries is realized โœจ

Whenever a package declares libraries for preloading, critcl will build a supporting shared library providing a Tcl package named preload ๐ŸŽ€. This package is not distributed separately โ€” it ships as part of the package that requires the preload functionality. This support package exports a single Tcl command:

  • ::preload library โ€” which is invoked once per libraries to preload, with the absolute path of that library. The command then loads the library. On windows the command will further use the Tcl command ::critcl::runtime::precopy to copy the library to the disk, should its path be in a virtual filesystem which doesn't directly support the loading of a shared library from it.

The command ::critcl::runtime::precopy is provided by the file critcl-rt.tcl in the generated package โ€” same as the command ::critcl::runtime::loadlib, which generates the ifneeded script expected by Tcl's package management. That generated ifneeded script contains the invocations of ::preload ๐Ÿ’…

The C code for the supporting library lives in the file critcl_c/preload.c, which is part of the critcl package ๐Ÿฆ‹

The Tcl code for the supporting runtime critcl-rt.tcl lives in the file runtime.tcl, which is part of the critcl::app package โญ


๐Ÿ‘‘ CONFIGURATION INTERNALS ๐Ÿ‘‘

Same audience warning as above, babes: this section is for developers who wanna understand and possibly modify the internals of the critcl package and application. Package writers can skip it ๐Ÿ™ˆ

It explains the syntax of configuration files and the configuration keys critcl uses to configure its build backend โ€” i.e. how this part of the system accesses the compiler, the linker, etc. ๐Ÿ› ๏ธ

๐Ÿ’ก Strongly recommended: open the file containing the standard configurations (path/to/critcl/Config) in the editor of your choice while reading this section of the documentation, and use it as an extended set of examples going beyond the simple defaults shown here ๐Ÿ‘‘

First up: the keys, the meaning of their values, plus examples drawn from the standard configurations distributed with the package โœจ Note that when you're writing a custom configuration it is not necessary to specify all the keys listed below โ€” only those whose default values are wrong or insufficient for the platform in question ๐Ÿ’–

  • version โ€” the command to print the compiler version number. Defaults to:
gcc -v
  • compile โ€” the command to compile a single C source file to an object file. Defaults to:
gcc -c -fPIC
  • debug_memory โ€” the list of flags for the compiler to enable memory debugging in Tcl. Defaults to:
-DTCL_MEM_DEBUG
  • debug_symbols โ€” the list of flags for the compiler to add symbols to the object files and the resulting library. Defaults to:
-g
  • include โ€” the compiler flag to add an include directory. Defaults to:
-I
  • tclstubs โ€” the compiler flag to set USE_TCL_STUBS. Defaults to:
-DUSE_TCL_STUBS
  • tkstubs โ€” the compiler flag to set USE_TK_STUBS. Defaults to:
-DUSE_TK_STUBS
  • threadflags โ€” the list of compiler flags to enable a threaded build. Defaults to:
-DUSE_THREAD_ALLOC=1 -D_REENTRANT=1 -D_THREAD_SAFE=1
-DHAVE_PTHREAD_ATTR_SETSTACKSIZE=1 -DHAVE_READDIR_R=1
-DTCL_THREADS=1
  • noassert โ€” the compiler flag to turn off assertions in Tcl code. Defaults to:
-DNDEBUG
  • optimize โ€” the compiler flag to specify the optimization level. Defaults to:
-O2
  • output โ€” the compiler flags to set the output file of a compilation. Defaults to:
-o [list $outfile]
โš ๏ธ NOTE the use of Tcl commands and variables here. At the time critcl uses the value of this key, the value of the referenced variable is substituted into it. The named variable is the only variable whose value is defined for this substitution ๐Ÿ’€
  • object โ€” the file extension for object files on the platform. Defaults to:
.o
  • preproc_define โ€” the command to preprocess a C source file without compiling it, but leaving #define's in the output. Defaults to:
gcc -E -dM
  • preproc_enum โ€” see preproc_define, except that #define's are not left in the output. Defaults to:
gcc -E
  • link โ€” the command to link one or more object files and create a shared library. Defaults to:
gcc -shared
  • link_preload โ€” the list of linker flags to use when dependent libraries are pre-loaded. Defaults to:
--unresolved-symbols=ignore-in-shared-libs
  • strip โ€” the flag to tell the linker to strip symbols from the shared library. Defaults to:
-Wl,-s
  • ldoutput โ€” like output, but for the linker. Defaults to the value of output ๐Ÿ’…
  • link_debug โ€” the list of linker flags needed to build a shared library with symbols. Defaults to the empty string. One platform requiring this is all variants of Windows, which uses:
-debug:full -debugtype:cv
  • link_release โ€” the list of linker flags needed to build a shared library without symbols, i.e. a regular build. Defaults to the empty string. One platform requiring this is all variants of Windows, which uses:
-release -opt:ref -opt:icf,3 -ws:aggressive
  • sharedlibext โ€” the file extension for shared library files on the platform. Defaults to:
[info sharedlibextension]
  • platform โ€” the identifier of the platform used in generated packages. Defaults to:
[platform::generic]
  • target โ€” the presence of this key marks the configuration as a cross-compilation target, and the value is the actual platform identifier of the target. No default. ๐ŸŽฏ

The syntax expected from configuration files is governed by the rules below ๐Ÿ“œ Again, it is recommended to open the file containing the standard configurations (path/to/critcl/Config) in the editor of your choice while reading this section of the documentation, and use it as an extended set of examples for the syntax ๐ŸŽ€

  1. Each logical line of the configuration file consists of one or more physical lines. In case of the latter the physical lines have to follow each other and all but the first must be marked by a trailing backslash. This is the same marker for continuation lines as used by Tcl itself โœจ
  2. A (logical) line starting with the character # (modulo whitespace) is a comment which runs until the end of the line, and is otherwise ignored ๐Ÿ’–
  3. the if line</> โ€” any (logical) line that starts with the word if (whitespace doesn't count, bestie) gets read as Tcl's if command and run exactly like that. Which means it has to follow Tcl's syntax for the command, and yes that can stretch across multiple logical lines, no biggie. It runs in a safe interpreter. ๐Ÿ’…
  4. the set line</> โ€” same energy: a (logical) line starting with the word set (again, modulo whitespace) is treated as Tcl's set command and executed as such. Follows Tcl's syntax for the command, may span multiple logical lines, and runs in a safe interpreter. ๐ŸŽ€
  5. platform variable value</> โ€” this is how you define a platform-specific configuration variable and its value. The variable has to be the name of one of the configuration keys listed earlier in this section (no making up new ones, ok?), and the platform string identifies which platform the setting is for. Every setting that shares the same identification string together forms the configuration block for that platform. โœจ
  6. platform when expression</> โ€” the special one ๐Ÿ‘‘. This marks the platform, and all the settings in its configuration block, as conditional on expression.

If the build platform is not a prefix of platform, nor vice versa, the whole block is just ignored, no drama. Otherwise the expression gets evaluated via expr, in that same safe interpreter used to run any set and if commands found in the configuration file (see above).

If the expression evaluates to true, this configuration block is considered to be the build platform for the host and gets chosen as the default configuration. A big, large example of this feature lives in the handling of OS X in the standard configuration file, where it selects the architectures to build based on the version of the operating system, the available SDK, etc. I.e. it chooses whether the output is universal or not, and whether it's old-style (ix86 + ppc) versus new-style (ix86 32+64) of universality. Literally so much going on. ๐Ÿฆ‹

  1. platform copy sourceplatform</> โ€” this copies the configuration variables and values currently defined in the configuration block for sourceplatform over to that of platform, overwriting existing values and creating missing ones. Variables of platform that sourceplatform doesn't define are not touched, they're safe, periodt.

The copied values can be overridden later in the configuration file, so order matters. Multiple copy lines may exist for a platform and can be intermixed with normal configuration definitions. Only the last definition of a variable is used. ๐Ÿ™ˆ

  1. variable value</> โ€” last but not least, this defines a default configuration variable and value. ๐Ÿ“

๐ŸŒˆ STUBS TABLES ๐ŸŒˆ

Okay so this section is for the devs out there building extensions that are not based on critcl, yet still wanna interface with stubs as they're understood and used by critcl โ€” either by exporting their own stubs table to a critcl-based extension, or by importing a stubs table of a critcl-based extension into their own. Both directions, we love to see it. ๐Ÿซถ

To this end we describe the stubs table information of a package foo.

  1. capitalization matters</> โ€” note that the differences in the capitalization of "foo", "Foo", "FOO", etc. below demonstrate how to capitalize the actual package name in each context. It's giving attention to detail. ๐Ÿ’…
  2. the directory</> โ€” all relevant files must be available in a sub-directory foo which can be found on the include search paths.
  3. foo.decls</> โ€” the above directory may contain a file foo.decls. If present, it's assumed to contain the external representation of the stubs table the headers mentioned in the following items are based on.

critcl is able to use such a file to give the importing package programmatic access to the imported API, for automatic code generation and the like. Obsessed. โœจ

  1. fooDecls.h</> โ€” the above directory must contain a header file fooDecls.h. This file declares the exported API. It's used by both exporting and importing packages. It's usually generated, and it must contain (in the order specified):
    1. the declarations of the exported, i.e. public, functions of foo,
    2. the declaration of structure FooStubs for the stub table,
    3. the C preprocessor macros which route the invocations of the public functions through the stubs table.

These macros must be defined if, and only if, the C preprocessor macro USE_FOO_STUBS is defined. Package foo does not define this macro, as it's allowed to use the exported functions directly. All importing packages however must define this macro, to ensure that they do not use any of the exported functions directly, but only through the stubs table. Big rule, no cap. ๐Ÿ”ฅ

  1. If the exported functions need additional types for their proper declaration, then these types should be put into a separate header file (of arbitrary name) and fooDecls.h should contain an #include directive to this header at the top.

A very reduced, yet also complete example, from a package for low-level random number generator functions, can be found at the end of this section. ๐Ÿงš

  1. fooStubLib.h</> โ€” the above directory must contain a header file fooStubLib.h. This file defines everything needed to use the API of foo. Consequently it's used only by importing packages. It's usually generated, and it must contain (in the order specified):
    1. An #include directive for tcl.h, with USE_TCL_STUBS surely defined.
    2. An #include directive for fooDecls.h, with USE_FOO_STUBS surely defined.
    3. A definition of the stubs table variable, i.e.
const FooStubs* fooStubsPtr;
  1. A definition of the stubs initializer function, like
char *
Foo_InitStubs(Tcl_Interp *interp, CONST char *version, int exact)
{
    /*
     * Boiler plate C code initalizing the stubs table variable,
     * i.e. "fooStubsPtr".
     */

    CONST char *actualVersion;

    actualVersion = Tcl_PkgRequireEx(interp, "foo", version,
               exact, (ClientData *) &fooStubsPtr);

    if (!actualVersion) {
  return NULL;
    }

    if (!fooStubsPtr) {
  Tcl_SetResult(interp,
           "This implementation of Foo does not support stubs",
           TCL_STATIC);
  return NULL;
    }

    return (char*) actualVersion;
}

This header file must be included by an importing package exactly once, so that it contains only one definition of both stubs table and stubs initializer function. One. Single. Definition. ๐Ÿ’€

The importing package's initialization function must further contain a statement like

if (!Foo_InitStubs (ip, "1", 0)) {
    return TCL_ERROR;
}

which invokes foo's stubs initializer function to set the local stub table up. ๐ŸŽ€

For a complete example of such a header file, see below, at the end of this section.

  1. the tea on why this differs โ˜•</> โ€” the last item above, about fooStubLib.h, differs from the regular stub stable system used by Tcl. The regular system assumes that a static library libfoostub.a was installed by package foo, and links it.

IMVHO critcl's approach is simpler, using only header files found in a single location, vs. header files and static library found in multiple, different locations.

A second simplification is that we avoid having to extend critcl's compiler backend with settings for the creation of static libraries. We love a shortcut that isn't a mess. ๐Ÿฆ‹


Below is a complete set of example header files, reduced, yet still complete, from a package for low-level random number generator functions:

rngDecls.h</>:

#ifndef rng_DECLS_H
#define rng_DECLS_H

#include <tcl.h>

/*
 * Exported function declarations:
 */

/* 0 */
EXTERN void rng_bernoulli(double p, int*v);

typedef struct RngStubs {
    int magic;
    const struct RngStubHooks *hooks;

    void (*rng_bernoulli) (double p, int*v); /* 0 */
} RngStubs;

#ifdef __cplusplus
extern "C" {
#endif
extern const RngStubs *rngStubsPtr;
#ifdef __cplusplus
}
#endif

#if defined(USE_RNG_STUBS)

/*
 * Inline function declarations:
 */

#define rng_bernoulli  (rngStubsPtr->rng_bernoulli) /* 0 */

#endif /* defined(USE_RNG_STUBS) */
#endif /* rng_DECLS_H */

rngStubLib.h</>:

/*
 * rngStubLib.c --
 *
 * Stub object that will be statically linked into extensions that wish
 * to access rng.
 */

#ifndef USE_TCL_STUBS
#define USE_TCL_STUBS
#endif
#undef  USE_TCL_STUB_PROCS

#include <tcl.h>

#ifndef USE_RNG_STUBS
#define USE_RNG_STUBS
#endif
#undef  USE_RNG_STUB_PROCS

#include "rngDecls.h"

/*
 * Ensure that Rng_InitStubs is built as an exported symbol.  The other stub
 * functions should be built as non-exported symbols.
 */

#undef  TCL_STORAGE_CLASS
#define TCL_STORAGE_CLASS DLLEXPORT

const RngStubs* rngStubsPtr;

/*
 *----------------------------------------------------------------------
 *
 * Rng_InitStubs --
 *
 * Checks that the correct version of Rng is loaded and that it
 * supports stubs. It then initialises the stub table pointers.
 *
 * Results:
 *  The actual version of Rng that satisfies the request, or
 *  NULL to indicate that an error occurred.
 *
 * Side effects:
 *  Sets the stub table pointers.
 *
 *----------------------------------------------------------------------
 */

#ifdef Rng_InitStubs
#undef Rng_InitStubs
#endif

char *
Rng_InitStubs(Tcl_Interp *interp, CONST char *version, int exact)
{
    CONST char *actualVersion;
                  actualVersion = Tcl_PkgRequireEx(interp, "rng", version,
                             exact, (ClientData *) &rngStubsPtr);
                  if (!actualVersion) {
                return NULL;
                  }

                  if (!rngStubsPtr) {
                Tcl_SetResult(interp,
                         "This implementation of Rng does not support stubs",
                         TCL_STATIC);
                return NULL;
                  }

                  return (char*) actualVersion;
              }

and that's the whole stubs handshake, bestie โ€” Tcl_PkgRequireEx grabs the version, rngStubsPtr gets filled in, and if the implementation is giving "no stubs for you" energy it bails with TCL_STATIC and a NULL. Clean. โœจ


๐ŸŒธ EXAMPLES ๐ŸŒธ

okay so for actual examples, go read the section "Embedding C" in Using CriTcl. that's where the real demo lives, iykyk ๐Ÿ’…


๐Ÿ‘‘ AUTHORS ๐Ÿ‘‘

the iconic trio behind all this:

  • Jean Claude Wippler
  • Steve Landers
  • Andreas Kupries

๐Ÿ’€ BUGS, IDEAS, FEEDBACK ๐Ÿ’€

ngl, this doc โ€” and the whole package it's describing โ€” will absolutely have bugs and other problems. like, guaranteed. ๐Ÿ˜ญ so if you find one, please don't suffer in silence:

Report bugs at https://github.com/andreas-kupries/critcl/issues

and bestie, they want your ideas too ๐Ÿซถ โ€” enhancements for the package, the application, and/or the documentation are all super welcome, and those should go to the exact same place:

https://github.com/andreas-kupries/critcl/issues

same link, both vibes. easy. ๐ŸŽ€


๐Ÿ”ฅ KEYWORDS ๐Ÿ”ฅ

for the search-engine girlies, here's the full tag cloud:

  • C code
  • Embedded C Code
  • calling C code from Tcl
  • code generator
  • compile & run
  • compiler
  • dynamic code generation
  • dynamic compilation
  • generate package
  • linker
  • on demand compilation
  • on-the-fly compilation

๐ŸŒˆ CATEGORY ๐ŸŒˆ

Glueing/Embedded C code โ€” that's the whole lane, no cap ๐Ÿ’…


keeping the receipts, exactly as written:

  • Copyright (c) Jean-Claude Wippler
  • Copyright (c) Steve Landers
  • Copyright (c) 2011-2024 Andreas Kupries

okay that's the whole page, bestie โ€” you made it to the end and honestly? main character energy ๐ŸŒŸ this was critcl_package(n), version 3.3.1, living in the doc section. go embed some C, slay, and remember: compile & run is a lifestyle ๐Ÿ’–โœจ๐Ÿฆ‹

๐Ÿ“ฆ where this page comes from