critcl_package(3tcl)
β¨ NAME β¨
bestie wake up, it's critcl_package(3tcl) β C Runtime In Tcl (aka CriTcl) β the official CriTcl Package Reference. π
πΈ SYNOPSIS πΈ
okay so first, the prerequisites, no skipping:
package require Tcl 8.6
package require critcl ?3.3.1?
package require platform ?1.0.2?
package require md5 ?2?
and then the whole entire roster of commands, in order, here she is π :
::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 π¦
welcomeeee to the C Runtime In Tcl (short: CriTcl) β the whole system for embedding and using C code from inside your Tcl scripts (http://core.tcl-lang.org/tcl). literally the glow-up of your C-and-Tcl situationship. π₯
The critcl package is the core of the system. Wanna see the whole picture? Go read Introduction To CriTcl. Wanna actually run the standalone critcl program? That's CriTcl Application. This core package is the one you use to embed C code into Tcl scripts. It also gives you access to the internals that other parts of the core lean on, and those are the ones worth knowing if you're the type to understand how the core works and what API it hands to the CriTcl Application. Those advanced sections are marked as such, so if you're just here to use the package, you can skip right past them, no guilt. π«Ά
This package lives in the Core Package Layer of CriTcl. the vibes, mapped:
+----------------+
|Applications |
| critcl |
| critcl::app |
+----------------+
*================*
|Core Packages |
| critcl |
| critcl::util |
*================*
+----------------+
|Support Packages|
| stubs::* |
| md5, platform |
| ... |
+----------------+
π API π
quick note before we get into it, bestie: instead of saying "a Tcl script with embedded C code" or "a Tcl script containing CriTcl commands" over and over like a broken record, we're just gonna call that a CriTcl script. and a file holding a CriTcl script usually has the extension .tcl or .critcl. periodt.
π EMBEDDED C CODE π
These commands below all append C code fragments onto the current module. Fragments show up in the module in the order they're appended, so earlier fragments (variables, functions, macros, whatever) are visible to the later ones. order matters, iykyk.
::critcl::ccodefragment β appends the C code in fragment to the current module, and returns the empty string. See Runtime Behaviour. β¨::critcl::ccommandtclname cname β exactly like the version documented below, except cname is the name of a C function that already exists. (so you're not writing it here, you're just pointing at it)::critcl::ccommandtclname arguments body ?option value...? β appends the code to create a Tcl command named tclname, plus the matching C function whose body is body, behaving exactly like Tcl's ownTcl_CreateObjCommand(docs athttps://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. Any names you leave out get the standard default names instead. ngl this is a way more low-level way to define a command than critcl::cproc β processing the items in objv is left entirely to you, the author, which means complete control over how the command's arguments get handled. See the Runtime Behaviour section.
Returns the empty string.
Each option can be one of:
-clientdatac-expression β hands over the client data for the new command. Defaults toNULL.-delprocc-expression β hands over a function pointer of typeTcl_CmdDeleteProc(seehttps://www.tcl-lang.org/man/tcl/TclLib/CrtObjCmd.htm) to act as the deletion function for the new command. Defaults toNULL.-cnameboolean β if it's false (which is the default), a name for the corresponding C function gets automatically derived from the fully-qualified tclname. Otherwise, the name of the C function is just the last component of tclname.
::critcl::cdatatclname data β appends the code to create a brand new Tcl command named tclname, which returns data as a ByteArray result. π
Returns the empty string.
::critcl::cconsttclname 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 takes no arguments. and unlikecritcl::cdata, resulttype can be any type known tocritcl::cproc. its semantics are equivalent to:
cproc $tclname {} $resulttype "return $value ;"
This is more efficient than critcl::cproc because there's no C function generated. efficient queen π
Returns the empty string.
::critcl::cdefineslist of glob patterns ?namespace? β arranges for Cenumand#definevalues that match one of the patterns in list of 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 one, and a pattern that matches nothing just gets ignored, no drama.
The Tcl variables get created when the module is compiled, using the preprocessor so it can properly find every matching C definition.
Produces no C code. the C definitions you want must already exist β this only reads them. π
::critcl::cprocname arguments resulttype body ?option value...? β appends a function having body as its body, plus another shim function to do all the needed conversions, plus the code to create a corresponding Tcl command named tclname. unlikecritcl::ccommand, the arguments and result are typed, and CriTcl generates the conversion code betweenTcl_Objvalues and C data types for you. See also Runtime Behaviour.
Returns the empty string.
- string
optionβ each may be one of:-cnameboolean β if false (the default), a name for the corresponding C function is automatically derived from the fully-qualified tclname. Otherwise, the name of the C function is the last component of tclname.-pass-cdataboolean β if false (the default), the shim function doing the conversion to and from the Tcl level does not pass theClientDataas the first argument to the function.-arg-offsetint β a non-negative integer,0by default, indicating how many hidden arguments come before the actual procedure arguments. used by higher-order code generators where there are prefix arguments that the function doesn't directly see but which still influence argument counting and extraction. sneaky but iconic
- string
resulttypeβ may be a predefined or a custom type. see the CriTcl cproc Type Reference for the full list of predefined types and how to extend them. unless otherwise noted, the Tcl return code is alwaysTCL_OK. - list
argumentsβ 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 items in it, item one is the name and item two is the default value. Sneaky little trick: you can do a limited form of variadic arguments using these default values. Like, for example:
int {x 1}
So here x is an optional argument, type int, default value 1. Cute, right?
Heads up bestie: argument conversion is completely bypassed when the argument isn't provided. So a custom converter that does validation does not get a chance to validate the default value. In that case you gotta check the value yourself in the body of the function. No cap.
Each argument type can be a predefined type or a custom one. Go look 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 reach for this one when the C function namednamealready exists. Reusing, not creating.::critcl::cinit text externalsβ appends the C code intextandexternals, but only after every other fragment appended by the previously-listed commands, no matter where this command sits in the CriTcl script relative to them. So like, everything they contain is visible. 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 gets access to the variable Tcl_Interp* interp, which references the Tcl interpreter currently being initialized.
externals goes 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β this one is just a convenient shorthand for
critcl::code {
#include <${path}>
}
π§© STUBS TABLE MANAGEMENT π§©
CriTcl versions 3 and later give you critcl::api, which creates and manipulates stubs tables β that's Tcl's dynamic linking mechanism handling the resolution of symbols between C extensions. New to this? Go read http://wiki.tcl-lang.org/285 for an intro, and check the section Stubs Tables for the details of CriTcl's particular variant. iykyk. β¨
Importing stubs tables (aka APIs) from another extension:
::critcl::api import name versionβ drops the following include directives into the CriTcl script and each of its companion.cfiles:#include <name/nameDecls.h>#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: ifnameis a fully-qualified name in a non-global namespace, e.g.c::stack, the namespace separators::get converted into underscores (_) in path names, C code, etc. Just so you know. π
name/nameDecls.h holds the stubs table type declarations, mapping macros, etc., and may include package-specific headers. See critcl::api header below. An #include directive gets 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 holds the stubs table variable definition and the function to initialize 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. The file is read and the internal representation of the stubs table is returned for use by the importing package. Otherwise, the empty string is returned.
One possible use? Automatically generating 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, so the user can declare a non-standard directory for the headers of the API. Any API name gets 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 namedname, with the signature specified byargumentsandresulttype. Code gets generated for a.declsfile, the corresponding public headers, and a stubs table usable bycritcl::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...?β every file matching a glob pattern gets copied into the directory containing the generated headers, and an#includedirective for it gets added toDecls.hfor the CriTcl script. Returns an error if a glob pattern matches nothing. Bestie, an empty match is not a vibe. π
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 eachfileshould exist in the external development environment. An#includedirective is added tofooDecls.h, butfileis not copied to the package header directory. Andfileis not a glob pattern, because CriTcl has no context β i.e. no 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. This 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 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].
Back in version 2, some meta data support was already present through ::critcl::license, but that was only used to generate license.txt. Glow-up era, fr fr. πΈ
::critcl::license author ?text...?β ignored in "compile & run" mode.
In "generate package" mode it provides info 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 anything 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 info 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 given 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 info takes precedence over any information specified through the generic API ::critcl::meta.
Calls to this command are cumulative. Stack 'em up. π
::critcl::meta key ?word...?β gives you arbitrary meta data outside of the following reserved keys:as::author,as::build::date,description,license,name,platform,require subject,summary, andversion. Its behaviour is like::critcl::subject, in that it treats all keys as a 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.
Okay so technically you can declare information for one of the reserved keys with this command, but that data gets ignored when the final meta data is assembled and written. So don't. π
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. Iconic behaviour honestly.
::critcl::meta? keyβ returns the value in the metadata associated withkey.
Used primarily to retrieve the name of the package from within utility packages that have to adapt C code templates to their environment. For example, critcl::class does this. King of reuse. π
::critcl::buildrequirement scriptβ gives you control over the capturing of dependencies declared viapackage require.scriptis evaluated and any dependencies declared within it are ignored, i.e. not recorded in the meta data. Bye bye dependency. π
CONTROL & INTERFACE ποΈ
Okay bestie, these commands are the ones that control the deets of compiling and linking a CriTcl script. The info you give them is used only to compile/link the object for that CriTcl script. Like, information for "FOO.tcl" is kept totally separate from information for "BAR.tcl". No cross-contamination, we love boundaries π
::critcl::cheaders ?arg...?β gives you extra header locations to look in.
Each argument is a glob pattern. If an argument begins with -, it's not a path, it's an argument to the compiler. Otherwise, the parent directory of each matching path is a directory to be searched for header files. Returns an error if a pattern matches no files. A pattern for a relative path gets resolved relative to the directory containing the CriTcl script.
Heads up: #include lines are not automatically generated for matching header files. You gotta use critcl::include or critcl::ccode as necessary to add them yourself. No free rides β¨
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...?β hands the link step extra libraries and library locations. A glob pattern that begins with-is added as an argument to the linker. Otherwise, matching files are 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 as scripts the files matching each glob pattern. 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.::critcl::tsources glob pattern...β provides the info about additional 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.
After the shared library has been loaded, the declared files are sourced in the same order that they were provided as arguments. Order matters, periodt π₯
::critcl::owns glob pattern...β 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 that could not be ascertained as such from previous commands likecritcl::csourcesandcritcl::tsourcesβ either because they were specified dynamically or because they were directly sourced.::critcl::cflags ?arg...?
Each arg is an argument to the compiler.
Calls to this command are cumulative.
::critcl::ldflags ?arg...?
Each arg is an argument to the linker.
Calls to this command are cumulative.
::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, so you can specify frameworks unconditionally without breaking your non-Mac build. Iconic behaviour honestly π
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 is8.4.::critcl::tkβ arranges to include the Tk headers and link to the Tk stubs.::critcl::preload lib...β arranges for the external shared library lib to be loaded before the shared library for the CriTcl script gets 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. Additional notes:
- 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:
FOOFOO.soFOO/FOO.soFOO/platform/FOO.so
PATH/FOO
The exact set searched depends on the existence of directory "PATH/FOO". If it exists, critcl searches:
FOO.soPATH/FOO/FOO.soPATH/FOO/platform/FOO.so
Otherwise it searches:
FOO.soPATH/FOO.soPATH/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. Sneaky but true π
FOO.so/PATH/FOO.so/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 tocritcl::cflags.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. Go big or go home π
INTROSPECTION π
The following commands control compilation and linking.
::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.::critcl::checklink ?label? textβ likecritcl::checkbut 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 usingcritcl::checkandcritcl::checklinkcan use this command to report results. Does nothing in compile & run mode. Tools like the CriTcl Application may redefine this command to implement their own message reporting. For example,critcl::appand 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.
::critcl::compiledβ returns true if the current CriTcl script is already compiled and false otherwise.
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.
::critcl::compilingβ returns true if a working C compiler is available and false otherwise.::critcl::doneβ returns true when the CriTcl script has been built and false otherwise. Only useful from within a CriTcl script. 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.::critcl::loadβ likecritcl::failedexcept 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 ποΈ
The following command manages global settings β i.e. configuration options which are independent of any CriTcl script.
bestie, you should pretty much never need this command to write a CriTcl script. It's a management command, and it's only useful to the CriTcl Application or similar tools. Not your vibe, usually π
::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),#linedirectives are embedded into the generated C code.
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.
Also lowkey a PSA before we get into the rest of these knobs: if you're out here building higher-level packages that generate their own C code β whether you're doing it directly or letting critcl do it for you β you really wanna read the Advanced: Location management section too, so you can see how critcl helps you generate your directives. Want examples? They ship with critcl itself. Go peek at critcl::iassoc and critcl::class. π«Ά
tracebool β when this isfalse(which, bestie, is the default β¨), no code that traces the entry and exit of CriTcl-backed commands in the CriTcl script gets inserted. And heads up: inserting that code implicitly activates the tracing facility in general. More deets incritcl::cutil.Ipath β one single global include path, used for all the files. Not set by default, so if you need it, say so.combineenum β okay so this one has β¨modesβ¨:dynamicβ the default, and the vibe is: object files get the suffix_pic.staticβ object files get the suffix_stubinstead.standaloneβ object files get no suffix at all, and the generated C files are compiled without using Tcl/Tk stubs. What you get are object files usable for static linking into a big shell.
languagestringkeepsrcbool β whenfalse(default bestie, you know the drill), the generated".c"files get deleted after the".o"files have been built. Clean up after yourself, iconic. πoutdirdirectory β the directory where a generated shared library gets placed. By default it gets dropped into the Result Cache.
π RESULT CACHE MANAGEMENT π
These commands control the Result Cache. Bestie, quick note: you do not need these just to write a CriTcl script. They're for the girlies doing more.
::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 it usually does not require any changes. Leave it alone, she's fine. π::critcl::clean_cache ?pattern...?β cleans the result cache, aka removes any and all files and directories in it. If you pass one or more patterns, then only the files and directories matching them get removed. Selective cleansing, we love to see it.
π οΈ BUILD CONFIGURATION π οΈ
These commands manage the build configuration, i.e. all that per-platform info about compilers, linkers, and their commandline options. Lowkey again: not needed if you're just writing a CriTcl script.
::critcl::readconfig pathβ reads the build configuration file at path and configures the package using the info 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 instead.::critcl::showallconfig ?chan?β converts the set of all known build configurations β from the currently active build configuration file, last set withcritcl::readconfigβ into a string and returns it. Or, if chan is provided, prints it to that channel. The full buffet. π::critcl::chooseconfig target ?nomatcherr?β matches target against all known targets, returning a list containing every matching one. This search happens on an exact basis first, then via glob matching. If nothing matches, the default move is to return an empty list. But β if nomatcherr is specified and set, an error gets thrown usingcritcl::errorinstead. So you can make her be dramatic if you want. π::critcl::setconfig targetβ configures the package to use the settings of target. Done and done.
π TOOL API π
These commands give tools like CriTcl Application (or similar) deeper access to the package's internals. And yep, once again: not needed to simply write a CriTcl script. This is the backstage pass, iykyk. π
::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 info resolved. No disguises, no cap. β¨::critcl::buildforpackage ?flag?β signals whether the next file is to be built for inclusion into a package. If not specified, 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. The expectation is that the build results get wrapped into a larger collection later. Playing the long game.::critcl::cnothingtodo fileβ checks whether there is anything to build for file. Vibe check on the build queue. π::critcl::cresults ?file?β returns information about building file, orinfo scriptif file is not provided. The result is a dictionary containing these 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 file is located in.mintclβ the minimum version of Tcl required by the package 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 sucessfully load and use file.tkβtrueif file requires Tk andfalseotherwise.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. Just a lil heads-up notice, bestie.::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 do their own style of error reporting. But there's one constraint they are not allowed to change: the procedure must not return to the caller. Periodt. π«::critcl::knowntargetsβ returns a list of the identifiers of all targets found during the last invocation ofcritcl::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. That's home base. π::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, okay?). 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. Stack 'em up. π::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 these 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 these 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. Sneaky but valid. π¦
π§ ADVANCED: EMBEDDED C CODE π§
For advanced use, these following commands β used by critcl::cproc itself β are exposed. Main character energy only. β¨
::critcl::argnames argumentsβ given an argument declaration as documented forcritcl::cproc, returns a list of the corresponding user-visible names.::critcl::argcnames argumentsβ given an argument declaration as documented forcritcl::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::argvardeclsand::critcl::argconversion.::critcl::argcsignature argumentsβ given an argument declaration as documented forcritcl::cproc, returns a list of the corresponding C parameter declarations.::critcl::argvardecls argumentsβ given an argument declaration as documented forcritcl::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 forcritcl::cproc, returns a list of C code fragments converting the user visible arguments found in the declaration fromTcl_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 theobjvarray. The default is1.::critcl::argoptional argumentsβ given an argument declaration as documented forcritcl::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 forcritcl::cproc, returns a list containing the default values for all optional arguments.::critcl::argsupportarguments β hand it an argument declaration (the kind documented over atcritcl::cproc) and it hands back a list of C code fragments β literally the ones you need to define the supporting types. iykyk π
πΈ CUSTOM BUILD CONFIGURATION πΈ
okay so this package gives you exactly one command for managing package-specific build config options β meaning the ones you, the developer, decided to add β¨
::critcl::userconfig definename description type ?default? β this one defines a custom build configuration option, and it wants a description, a type, and optionally a default value.
the type can either be bool, or a list of values π«Ά
- for
bool, the default value, if you actually specified one, has to be a boolean. if you didn't specify one, it just defaults totrue. - for a list of values, the default value, if specified, has to be a value that's actually found in that list. if you don't specify one, it defaults to the first value of the list.
the description is basically serving as in-code documentation for what the option even means, and otherwise it gets ignored. but β when a TEA wrapper is being generated, that description does get used for the configure option that's derived from the option declared by the command π
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. periodt π
::critcl::userconfig queryname β this queries the database of custom build configuration options for the current.critclfile and returns the chosen value π¦. that might just be the default if nothing was ever set via::critcl::userconfig set.
and THIS, bestie, is the moment where definitions and set values finally get brought together β with the set values validated against the definition. no cap.
::critcl::userconfig setname value β this one is for use by a tool, like thecritclapplication itself, to specify values for custom build configuration options π§
at the time this command runs, only the association between the option name and value gets recorded, and literally nothing else happens. why? because the system may not know whether an option with the specified name even exists when the command is invoked β nor what its type is. so this behaviour is necessary.
any and all validation is deferred to when the value of an option is asked for, via ::critcl::userconfig query.
which means it's fully possible to set values for any option we like, and the value only actually takes effect if such an option is both defined and used later on. iconic behaviour, honestly.
π§ ADVANCED: LOCATION MANAGEMENT π§
first, a lil intro for the girlies asking themselves "what even is location management" π
by default, critcl embeds #line directives into the generated C code, so that any errors, warnings and notes the C compiler finds during compilation refer to the .critcl file the faulty code came from β instead of referring to the generated .c file. chef's kiss π
this whole facility requires a tclsh that provides info frame. if it doesn't, then no #line directives get emitted at all π the command is supported by Tcl 8.5 and higher. it's also supported by Tcl 8.4, provided that it was compiled with the define -DTCL_TIP280. an example of such a thing is ActiveState's ActiveTcl.
most users will not care about this feature beyond simply wanting it to work and getting proper code references when they read compiler output πΈ
but β developers of higher-level packages that generate their own C code should care about this, so they can make sure their generated code contains proper references too. especially since this is key to separating bugs concerning code generated by the package itself from bugs in the user's code going into the package (if any) π
examples of such packages come with critcl itself β go peek at the implementations of packages critcl::iassoc and critcl::class.
to help these developers, eight commands are provided to manage such location information. they're listed below π
the main concept here is that they all operate on a single stored location β setting it, returning it, and clearing it. note that this location information is completely independent of the generation of #line directives within critcl itself.
::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. the result of the command is the empty string β¨::critcl::at::calleroffset β same as above, except the stored line number gets modified by the specified offset. in essence it's an implicit call ofcritcl::at::incr.::critcl::at::calleroffset level β same as above, but the level the location information is taken from gets modified too. level0is the caller,-1is its caller, etc.::critcl::at::hereβ stores the current location in the current procedure as a tuple of file name and linenumber. any previously stored location gets overwritten. result 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#linedirective, ready for embedding into some C code. the stored location is left untouched π«Ά note that the directive contains its own closing newline.
for proper nesting and use, it's recommended that such directives always get added to the beginning of a code fragment. this way, should deeper layers add their own directives, those come before ours and are thus inactive. end result: the outermost layer generating a directive will 'win', i.e. have its directive used. as it should π
::critcl::at::getβ just like the above, except it also clears the stored location.::critcl::at::=file line β lets the caller set the stored location to literally anything they want, outside of critcl's control. the result of the command is the empty string π::critcl::at::incrn...::critcl::at::incrtstr...
these two let you modify the line number of the stored location, changing it incrementally π the increment is specified either as a series of integer numbers (incr), or as a series of strings to consider (incrt). in the latter case, the delta is the number of line endings found in the strings.
::critcl::at::caller!::critcl::at::caller!offset::critcl::at::caller!offset level::critcl::at::here!
these are convenience commands, combining caller and here with get. i.e. they store the location and immediately return it formatted as a proper #line directive. also note that after their use, the stored location gets cleared π§
π ADVANCED: DIVERSIONS π
diversions are for higher-level packages generating their own C code, to make their use of critcl's commands that generate Embedded C Code easier π«Ά
these commands normally generate all of their C code for the current .critcl file β which may not be what a higher-level package actually wants π
with a diversion, the generator output can be redirected into memory, and from there on it can be handled and processed however the caller desires, before it gets committed to an actual .c file. obsessed β¨
an example of such a package comes with critcl itself β see the implementation of package critcl::class.
to help such developers, three commands are provided to manage diversions and the collection of C code in memory. they are:
::critcl::collect_beginβ this one starts the diversion of 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 π
::critcl::collect_endβ this one 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.
the command will throw an error if no diversion is active at all β which indicates a mismatch in the pairing of collect_begin and collect_end π
::critcl::collectscript β a convenience command that runs the script under diversion and returns the collected C code, ensuring the correct pairing ofcollect_beginandcollect_endπ
π ADVANCED: FILE GENERATION π
so, file generation is related to the diversions from the previous section, but they are absolutely not the same thing. even so, just like diversions, this feature is for higher-level packages generating their own C code π
three examples of utility packages using this facility come with critcl itself. go check out the implementations of packages critcl::literals, critcl::bitmap, and critcl::enum π
when you're splitting a package implementation into pieces, it's often sensible to have a bunch of pure C companion files containing low-level code β but those files might need information about the code in the main .critcl file π such declarations are normally not exportable, and using the stub table support doesn't make sense here, because this is completely internal to the package.
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::makepath contents β this creates the file path in a location where the C companion files of the package are able to pick it up by simply including path during their compilation, and without interfering with the outer system at all β¨
the generated file will contain the specified contents.
π CONCEPTS π
π MODES OF OPERATION/USE π
CriTcl can be used in three different modes of operation, called:
- Compile & Run, and
- Generate Package
- 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 to improve load times later π
the second mode, Generate Package, was introduced to enable the creation of (prebuilt) deliverable packages that don't depend on the existence of a build system β i.e. a C compiler β on the target machine. this was originally done through the experimental Critbind tool, and is now handled by the CriTcl Application, also named critcl π
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
Oh and about caching results β bestie, that's a whole thing, go read the Result Cache section for the tea. π΅
β¨ RUNTIME BEHAVIOUR β¨
Okay so the default behaviour of critcl, the package, is to procrastinate. Iconically. It defers the compilation, linking, and loading of any C code as much as it possibly can, because that whole operation is expensive β mostly in the time it eats. π°οΈ
In other words: the C code embedded into a .critcl file gets built only when the first C command or procedure it provides is actually invoked. Lazy queen behaviour, and honestly valid. π
This part of the system runs on standard functionality baked into the Tcl core, meaning:
- the
auto_indexvariable, which maps from commands to the scripts that provide them - the
unknowncommand, which uses that info when a command turns out to be needed
β οΈ Lowkey important limitation: you can't just useinfo commandsto check whether a critcl-defined command exists. Nah. You also have to search theauto_indexarray, in case it hasn't been built yet. π
You can flip this behaviour with the control command critcl::load. When it's invoked, the building β including loading of the result β is forced. And once that command has been invoked for a .critcl file, further definition of C code in that file is not allowed any longer. Door's closed, bestie. πͺ
ποΈ FILE MAPPING ποΈ
Every .critcl file is backed by a single private .c file containing that code, plus all the boilerplate needed to compile and link it as one single shared library. One file, one library, one vibe. β¨
The Embedded C Code fragments show up in that file in the exact same order they were defined in the .critcl file β with one exception. The C code you hand over via critcl::cinit gets shoved to the back, after all the other fragments. So basically:
- all fragments get access to the symbols defined by earlier fragments
- the
critcl::cinitfragment gets access to all of them, no matter where it sits in the.critclfile
π Note: a limitation of the current system is the near impossibility of C-level access between different critcl-based packages. And no, the problem isn't having to write and share the proper extern statements β it's 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. π Check the Runtime Behaviour section for the relevant critcl limitation, and remember that a lot of older platforms don't support the necessary resolution of symbols β which is, like, literally the whole reason stubs were invented for Tcl in the first place. π«
πΎ RESULT CACHE πΎ
Compiling C code takes forever, so critcl doesn't just defer it as much as it can (see Runtime Behaviour) β it also caches the results. Queen of not doing work twice. π
So here's the deal: the first time you use 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 β loaded directly, no compilation required β provided the contents of "FOO.tcl" didn't change.
The change detection is MD5-hash based. A single hash gets computed for each .critcl file, built from the hashes of all the C code fragments and configuration options β i.e. everything that affects the resulting binary. π
As long as the input file doesn't change per that hash, a previously built shared library found in the cache is reused, straight-up bypassing the compilation and link stages. Slay.
The commands for managing the cache live in the Result Cache Management section. But heads up: they're only useful to tools built on top of the package, like the CriTcl Application. Package writers have no need for them, fr fr.
And one last thing β the default directory for the cache is chosen based on the chosen build target. Which means the cache can sit on a shared (network) filesystem without you having to fear interference between machines of different architectures. π
π§ PRELOADING FUNCTIONALITY π§
π This section's audience is developers who want to understand and possibly modify the internals of the critcl package and application. Package writers, you can skip this one, bestie.
So how is the preloading of external libraries actually realized? Let's get into it.
Whenever a package declares libraries for preloading, critcl will build a supporting shared library that provides a Tcl package named "preload". This package isn't distributed separately β it ships as part of the package that needs the preload functionality. This support package exports a single Tcl command:
::preload library
...which gets invoked once per library to preload, with the absolute path of that library. The command then loads that 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 β as is the command ::critcl::runtime::loadlib, which generates the ifneeded script that Tcl's package management expects. This generated ifneeded script contains the invocations of ::preload.
Where to find everything:
- 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 thecritcl::apppackage.
βοΈ CONFIGURATION INTERNALS βοΈ
π Again: this section's audience is developers who want to understand and possibly modify the internals of the critcl package and application. Package writers, skip away, you're free. ποΈ
This is where we explain the syntax of configuration files, plus the configuration keys critcl uses to configure its build backend β i.e. how this part of the system gets at the compiler, linker, etc.
Pro tip: open the file containing the standard configurations ("path/to/critcl/Config") in the editor of your choice while you read this section, and use it as an extended set of examples going beyond the simple defaults shown here. π
First up: the keys, what their values mean, and examples pulled from the standard configurations distributed with the package. Note that when you write a custom configuration it's 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 setUSE_TCL_STUBS. Defaults to:
-DUSE_TCL_STUBS
tkstubsβ the compiler flag to setUSE_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 optimization level. Defaults to:
-O2
outputβ the compiler flags to set the output file of a compilation. Defaults to:
-o [list $outfile]
π¨ NOTE: observe 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. iykyk π
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β seepreproc_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 preloaded. 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β likeoutput, but for the linker. Defaults to the value ofoutput.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. π―
So the syntax expected from configuration files? That's governed by the rules below. And again β open the file containing the standard configurations ("path/to/critcl/Config") in the editor of your choice while reading this section, and use it as an extended set of examples for the syntax:
- 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. π
- 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] A logical line starting with the word "if" (modulo whitespace, bestie π) gets read as Tcl's
ifcommand and run just like that. So it has to follow Tcl's syntax forif, which can totally stretch across multiple logical lines. It runs in a safe interpreter. π«Ά - [4] Same energy for
set: a logical line starting with the word "set" (again, modulo whitespace) is interpreted as Tcl'ssetcommand and executed as such. So it follows Tcl's syntax forset, can span multiple logical lines too, and also runs in a safe interpreter. β¨ - [5] A line shaped like "platform variable value" defines a platform-specific configuration variable and value. The variable part has to be the name of one of the configuration keys listed earlier in this section, and the platform string identifies which platform the setting is for. Every setting with the same identification string forms one configuration block for that platform. π
- [6] The special one: a line of the form "platform when expression" marks the platform and every setting in its configuration block as conditional on expression.
If the build platform is neither a prefix of platform nor vice versa, the whole block gets ignored. Otherwise, expression is evaluated via expr, in the same safe interpreter used to run any set and if commands found in the configuration file (see above).
If it evaluates to true, this configuration block is considered the build platform for the host and gets chosen as the default configuration. A big example of this feature is the handling of OS X in the standard configuration file, where it picks 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) vs. new-style (ix86 32+64) universality. No cap. π
- [7] A line of the special form "platform copy sourceplatform" copies the configuration variables and values currently defined in the configuration block for sourceplatform over to platform, overwriting existing values and creating missing ones. Variables in platform that aren't defined by sourceplatform are left completely untouched.
The copied values can be overridden later in the configuration file. Multiple copy lines may exist for a platform, and they can be intermixed with normal configuration definitions. Only the last definition of a variable is used.
- [8] And last but never least, a line of the form "variable value" defines a default configuration variable and value. π
π§ STUBS TABLES π§
This section is for the devs making extensions that are not critcl-based, yet still want to interface with stubs the way critcl understands and uses them β either by exporting their own stubs table to a critcl-based extension, or by importing a critcl-based extension's stubs table into their own. iykyk π
To make all that happen, here's the stubs table information for a package foo.
- Notice how the capitalization flips around between
foo,Foo,FOO, etc. below β that's on purpose, it shows how to capitalize the actual package name in each context. π - All the relevant files must be available in a sub-directory
foowhich can be found on the include search paths. - That directory may contain a file
foo.decls. If it's present, it's assumed to hold the external representation of the stubs table that 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. β
- The directory must contain a header file
fooDecls.h. This file declares the exported API. Both exporting and importing packages use it. It's usually generated and must contain (in the order specified):- the declarations of the exported, i.e. public, functions of
foo, - the declaration of structure
FooStubsfor the stub table, - the C preprocessor macros which route the invocations of the public functions through the stubs table.
- the declarations of the exported, i.e. public, functions of
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, since it's allowed to use the exported functions directly. All importing packages however must define this macro, to ensure that they don't use any of the exported functions directly β only through the stubs table.
- If the exported functions need additional types for their proper declaration, then those types should go into a separate header file (any name you like), and
fooDecls.hshould contain an#includedirective to that 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. π
- The directory must contain a header file
fooStubLib.h. This file defines everything needed to use the API offoo. Consequently, it's used only by importing packages. It's usually generated and must contain (in the order specified):- An
#includedirective fortcl.h, withUSE_TCL_STUBSsurely defined. - An
#includedirective forfooDecls.h, withUSE_FOO_STUBSsurely defined. - A definition of the stubs table variable, i.e.
- An
const FooStubs* fooStubsPtr;
- 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 the stubs table and the stubs initializer function.
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.
- That last item, about
fooStubLib.h, differs from the regular stub stable system used by Tcl. The regular system assumes that a static librarylibfoostub.awas installed by packagefoo, 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. πΈ
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;
}
π EXAMPLES π
okay bestie, the real hands-on walkthrough lives in the section "Embedding C" inside Using CriTcl π go read that one, it's the whole glow-up tutorial β¨
π AUTHORS π
the absolute icons who made this happen π«Ά
- Jean Claude Wippler
- Steve Landers
- Andreas Kupries
π BUGS, IDEAS, FEEDBACK π
ngl, this document and the package it describes will undoubtedly contain bugs and other problems π like, it's software, that's just the vibe.
Please report them at:
https://github.com/andreas-kupries/critcl/issues
Ideas for enhancements you may have for either package, application, and/or the documentation are also very welcome fr fr π₯Ή and should be reported at the exact same place:
https://github.com/andreas-kupries/critcl/issues
like, same link, both times, that's just where the magic happens π
π KEYWORDS π
the tags, the search terms, the lil breadcrumbs πΈ:
- 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 shelf it lives on π
π COPYRIGHT π
- Copyright (c) Jean-Claude Wippler
- Copyright (c) Steve Landers
- Copyright (c) 2011-2024 Andreas Kupries
that's a wrap bestie πβ¨ This was the critcl_package(3tcl) page from the doc corner of the critcl world, sitting at version 3.3.1 β no cap, you made it all the way to the end, so proud of you π₯Ήπ«Ά now go compile something iconic π
π¦ where this page comes from
- ubuntu resolute: critcl 3.3.1+dfsg-1build1 Β· BSD
- ubuntu stonking: critcl 3.3.1+dfsg-1.1 Β· BSD
- Copyright (c) 2011-2024 Andreas Kupries
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms