NAME¶
critcl_use - Using Critcl
DESCRIPTION¶
Welcome to the
C Runtime In Tcl,
CriTcl for short, a system to
build C extension packages for Tcl on the fly, from C code embedded within Tcl
scripts, for all who wish to make their code go faster.
This document is a (hopefully) gentle introduction to Critcl by way of a series
of small examples.
Readers which came directly to this document through a search or similar, and
are thus in need of an overview of the whole system, are advised to read the
Introduction To CriTcl first.
The examples here cover both how to embed C into Tcl with it, and how to build
the distributable packages. As such the intended audience are both writers of
packages with embedded C, and people building such packages. To make things
easier the two themes each have their own section in this document, enabling
all readers to quickly skip the part they are not interested in.
The sources of Critcl, should you have gotten them, contain several larger
examples show-casing various aspects of the system. These demonstration
packages can all be found in the sub-directory "
examples/"
of the sources.
EMBEDDING C¶
This is the section for developers writing, or wishing to write, a package
embedding C into Tcl via critcl.
I guess that we are allowed to asssume that you, gentle reader, are here because
you have written some Tcl code which is not fast enough (any more) and you
wish to make it "go faster" by replacing parts (or all) of it with
speedy C.
Another, and I believe reasonable assumption to make would be that you have
already investigated and ruled out or done things like changes to data
structures and algorithms which reduce O(n*n) work to O (n*log n), O(n), or
even O(1). Of course, nothing prevents you from forging ahead here even if you
have not done such. Still, even in that case I would recommend that you
consider investigating this line of making your code go faster as well.
Now, with these introductory words out of the way, lets jump into the meat of
things.
A SIMPLE PROCEDURE¶
Starting simple, let us assume that the Tcl code in question is something like
proc math {x y z} {
return [expr {(sin($x)*rand())/$y**log($z)}]
}
with the expression pretending to be something very complex and slow. Converting
this to C we get:
package require critcl
critcl::cproc math {double x double y double z} double {
double up = rand () * sin (x);
double down = pow(y, log (z);
return up/down;
}
Notable about this translation:
- [1]
- All the arguments got type information added to them, here
"double". Like in C the type precedes the argument name. Other
than that it is pretty much a Tcl dictionary, with keys and values
swapped.
- [2]
- We now also have to declare the type of the result, here
"double", again.
- [3]
- The reference manpage lists all the legal C types supported as arguments
and results.
HANDLING A VARIABLE NUMBER OF ARGUMENTS¶
In
A Simple Procedure we demonstrated how easy a translation to C can be.
Is it still as easy when we introduce something moderately complex like
handling a variable number of arguments ? A feature which is needed to handle
commands with options and optional arguments ? Unfortunately not. We can use
critcl::cproc only if the number of arguments is known beforehand, i.e.
at the time of declaration. This of course also means that they do not support
default arguments either.
Thus, to handle something like the example below
proc math {args} {
set sum 0
foreach y { set sum [expr {$sum + $y}] }
return $sum
}
we have to use
critcl::ccommand instead.
Its advantage: Access to the low-level C arguments representing the Tcl
arguments of the command. That allows things like variable number of
arguments, optional arguments, options, etc.
Its disadvantage: Access to the low-level C arguments representing the Tcl
arguments of the command. Where
critcl::cproc handles the task of
converting from Tcl to C values (for arguments) and back (for the result),
with
critcl::commands we have to do this on our own.
Here is the translation of the example:
package require critcl
critcl::ccommand math {cd ip oc ov} {
double sum = 0;
double y;
while (oc) {
if (Tcl_GetDoubleFromObj (ip, ov[oc], &y) != TCL_OK) {
return TCL_ERROR;
}
sum += y;
oc --;
}
Tcl_SetObjResult (ip, Tcl_NewDoubleObj (sum));
return TCL_OK:
}
Notable about this translation:
- [1]
- As promised/threatened, all the conversions between the Tcl and C domains
are exposed, and the developer should know her way around Tcl's C
API.
- [2]
- The four arguments "cd ip oc ov" are our names for the low-level
arguments holding
- [1]
- ClientData (reference)
- [2]
- Tcl_Interp (reference)
- [3]
- Number of arguments, and
- [4]
- Array of argument values, each a Tcl_Obj*.
- This list of arguments, while not optional in itself, is allowed to be
empty, and/or to contain empty strings as argument names. If we do that
critcl will supply standard names for the missing pieces, namely:
- [1]
- clientdata
- [2]
- interp
- [3]
- objc
- [4]
- objv
DATA AS A TCL COMMAND¶
Here we assume that we have a Tcl procedure which returns a fixed string. In the
final product we are going to C to hide this string from the casual user.
proc somedata {} {
return {... A large blob of characters ...}
}
The translation of this is simple and easy:
package require critcl
critcl::cdata somedata {... A large blob of characters ...}
There is nothing really notable here.
BLOCKS OF ARBITRARY C¶
Often just defining Tcl commands in C, as demonstrated in the sections
A
Simple Procedure,
Handling A Variable Number Of Arguments, and
Data As A Tcl Command is not really enough. For example we may have
several of our new C commands using the same code over and over, and we wish
avoid this duplication. Or we wish to pull in declarations and definitions
from some external library.
In both cases we require the ability to embed an unstructured block of C code
which can contain whatever we want, defines, functions, includes, etc. without
being directly tied to Tcl commands. The command
critcl::code provides
us with exactly that. As our example now an excerpt taken from real code, the
top of the "
sha1c.tcl" critcl file in the
sha1 module
of
Tcllib:
critcl::ccode {
#include "sha1.h"
#include <stdlib.h>
#include <assert.h>
static
Tcl_ObjType sha1_type; /* fast internal access representation */
static void
sha1_free_rep(Tcl_Obj* obj)
{
SHA1_CTX* mp = (SHA1_CTX*) obj->internalRep.otherValuePtr;
Tcl_Free(mp);
}
...
}
We see here the beginning of the C code defining a custom
Tcl_ObjType
holding the data of a SHA1 context used during the incremental calculation of
sha1 hashes.
LIFTING CONSTANTS¶
When writing a critcl-based package to make a third-party library available to
scripts we do not only have to make the relevant functions available as
commands, often we also have to know all the various constants, flags, etc.
these functions take.
Rather than writing such magic numbers directly we would greatly prefer to use
symbolic names instead. Instead of providing one or more commands to list and
map the magic numbers to strings critcl
only provides a single command
which allows the export of C defines and enumeration values, mapping them to
Tcl variables of the given names, whose values are the associated magic
numbers.
This is good enough because the developers of the third-party library were very
likely like us and wanted to use symbolic names instead of magic numbers.
Which in C are declared as via defines and enumeration types. We just have to
lift them up.
Our example comes from
cryptkit, a Tcl binding to
cryptlib, a
cryptography library. The command
critcl::cdefines CRYPT_* ::crypt
maps all Cryptlib specific #defines and enums into the namespace
::crypt,
telling critcl to create aliases to the symbols.
Similarly
critcl::cdefines {
NULL
TRUE
FALSE
TCL_OK
TCL_ERROR
} ::crypt
maps the listed defines into the namespace
::crypt.
An
important thing to note: These commands
do not create the
defines in the C level. They only lift pre-existing material. Which can come
from the headers of the third-party library, the usual case, but also from
Blocks of arbitrary C.
A corrollary to the above: What is not where, cannot be lifted. All listed names
and patterns which have no actual C code declaring them are ignored, i.e. not
mapped.
A notable thing in the example shown in the section about
Blocks of arbitrary
C is the
statement. Where does this header come from ? Looking at the Tcllib module we
will find that the header is actually a sibling to the "
sha1c.tcl" file containing the embedded C code. However,
critcl does not know that. It has to be told. While without that
knowledge it will invoke the compiler just fine, the compilation will fail
because the header is not on the include paths used by the compiler, and
therefore will not be found.
For this we have the
critcl::cheaders command. It enables us to either
tell the compiler the path(s) where the required headers can be found, using
critcl::cheaders -I/path/to/headers/
or to tell it directly which headers we are using and where they live:
And now critcl knows that "
sha1.h" is important, and that it
lives besides the "
.critcl" file which referenced it
(because of the relative path used).
Note that this doesn't absolve us
of the need to "#include" the header through a
critcl::ccode
block. This only tells critcl where it lives so that it can configure the
compiler with the proper include paths to actually find it on use.
Further note that a C development environment is usually configured to find all
the system headers, obviating the need for a
critcl::cheaders
declaration when such are used. For these a plain "#include" in a
critcl::ccode block is good enough. In other words, the second form of
invoking
critcl::cheaders is pretty much only for headers which
accompany the "
.critcl" file.
SEPARATE C SOURCES¶
In all of the examples shown so far the C code was fully embedded in a "
.critcl" file. However, if the C part is large it can make sense
to break it out of the "
.critcl" file into one or more
separate proper "
.c" file(s).
The
critcl::csources command can then be used to make this code known to
the original "
.critcl" file again. This command accepts the
paths to the "
.c" files as arguments, and glob patterns as
well. Our example comes from the
struct::graph package in Tcllib. Its
core C functions are in separate files, and the "
.critcl"
code then makes them known via:
namespace eval ::struct {
# Supporting code for the main command.
critcl::cheaders graph/*.h
critcl::csources graph/*.c
...
}
which tells critcl that these files are in the subdirectory "
graph" relative to the location of "
graph_c.tcl", which is the relevant "
.critcl"
file.
This example also demonstrates again the use of
critcl::cheaders, which
we also saw in section
Finding header files.
FINDING EXTERNAL LIBRARIES¶
When creating a package exposing some third-party library to Tcl
Finding
header files is only the first part, to enable failure-free compilation.
We also have to find the library/ies themselves so that they can be linked to
our package. This is described here. The last issue,
Lifting constants
from C to Tcl for the use by scripts is handled in a separate section and
example.
The relevant command is
critcl::clibraries. Its basic semantics are like
that of
critcl::cheaders, i.e. It enables us to tell the linker the
path(s) where the required libraries can be found, using
critcl::clibraries -L/path/to/libraries/
name them
or tell it directly which libraries we are using and where they live:
critcl::clibraries /path/to/library/foo.so
This last way of using should be avoided however, as it intermingles searching
and naming, plus the name is platform dependent.
For OS X we additionally have the
critcl::framework command which enables
us to name the frameworks used by our package. Note that this command can be
used unconditionally. If the build target is not OS X it is ignored.
CUSTOMIZING THE COMPILE AND LINK STEPS¶
The commands
critcl::cflags and
critcl::ldflags enable you to
provide custom options to the compile and link phases for a "
.critcl" file.
This usually becomes necessary if the C code in question comes from an external
library we are writing a Tcl binding for, with multiple configurations to
select, non-standard header locations, and other things. Among the latter,
especially platform-specific settings, for example byteorder.
This makes
critcl::check an important adjunct command, as this is the API
for
Checking The Environment, and then selecting the compile & link
flags to use.
I currently have no specific example to demonstrate these commands.
HAVING BOTH C AND TCL FUNCTIONALITY¶
Often enough only pieces of a package require recoding in C to boost the whole
system. Or, alternatively, the package in question consists of a low-level
layer C with a Tcl layer above encoding policies and routing to the proper
low-level calls, creating a nicer (high-level) API to the low-level
functionality, etc.
For all of this we have to be able to write a package which contains both C and
Tcl, nevermind the fact the C parts are embedded in Tcl.
The easiest way to structure such a package is to have several files, each with
a different duty. First, a "
.critcl" file containing the
embedded C, and second one or more "
.tcl" files providing
the Tcl parts. Then use the
critcl::tsources command in the
"
.critcl" file to link the two parts together, declaring the
"
.tcl" files as necessary companions of the C part.
package require critcl
critcl::tsources your-companion.tcl ; # Companion file to use
... embedded C via critcl commands ...
With a declaration as shown above the companion file will be automatically
sourced when the C parts are made available, thus making the Tcl parts
available as well.
USING C WITH TCL FUNCTIONALITY AS FALLBACK¶
There is one special case of
Having both C and Tcl functionality which
deserves its own section.
The possibility of not having the fast C code on some platform, and using a
slower Tcl implementation of the functionality. In other words, a fallback
which keeps the package working in the face of failure to build the C parts. A
more concrete example of this would be a module implementing the SHA hash, in
both C and Tcl, and using the latter if and only if the C implementation is
not available.
There two major possibilities in handling such a situation.
- [1]
- Keep all the pieces separated. In that scenario our concrete example would
be spread over three packages. Two low-level packages sha::c and
sha::tcl containing the two implementations of the algorithm, and,
thirdly, a coordinator package sha which loads either of them,
based on availability.
The Tcllib bundle of packages contains a number of packages structured in
this manner, mostly in the struct module.
Writing the C and Tcl parts should be simple by now, with all the examples
we had so far. The only non-trivial part is the coordinator, and even that
if and only if we wish to make it easy to write a testsuite which can
check both branches, C, and Tcl without gymnastics. So, the most basic
coordinator would be
set sha::version 1
if {[catch {
package require sha::c $sha::version
}]} {
package require sha::tcl $sha::version
}
package provide sha $sha::version
- It tries to load the C implementation first, and falls back to the Tcl
implementation if that fails. The code as is assumes that both
implementations create exactly the same command names, leaving the caller
unaware of the choice of implementations.
A concrete example of this scheme can be found in Tcllib's md5
package. While it actually uses ythe Trf as its accelerator, and
not a critcl-based package the principle is the same. It also demonstrates
the need for additional glue code when the C implementation doesn't
exactly match the signature and semantics of the Tcl implementation.
This basic coordinator can be easily extended to try more than two packages
to get the needed implementation. for example, the C implementation may
not just exist in a sha::c package, but also bundled somewhere else.
Tcllib, for example, has a tcllibc package which bundles all the C parts
of its packages which have them in a single binary.
Another direction to take it in is to write code which allows the loading of
multiple implementations at the same time, and then switching between them
at runtime. Doing this requires effort to keep the implementations out of
each others way, i.e. they cannot provide the same command names anymore,
and a more complex coordinator as well, which is able to map from the
public command names to whatever is provided by the implementation.
The main benefit of this extension is that it makes testing the two
different implementations easier, simply run through the same set of tests
multiple times, each time with different implementation active. The
disadvantage is the additional complexity of the coordinator's internals.
As a larger example of this technique here is the coordinator "
modules/struct/queue.tcl" handling the C and Tcl
implementations of Tcllib's struct::queue package:
# queue.tcl --
# Implementation of a queue data structure for Tcl.
package require Tcl 8.4
namespace eval ::struct::queue {}
## Management of queue implementations.
# ::struct::queue::LoadAccelerator --
# Loads a named implementation, if possible.
proc ::struct::queue::LoadAccelerator {key} {
variable accel
set r 0
switch -exact -- $key {
critcl {
# Critcl implementation of queue requires Tcl 8.4.
if {![package vsatisfies [package provide Tcl] 8.4]} {return 0}
if {[catch {package require tcllibc}]} {return 0}
set r [llength [info commands ::struct::queue_critcl]]
}
tcl {
variable selfdir
if {
[package vsatisfies [package provide Tcl] 8.5] &&
![catch {package require TclOO}]
} {
source [file join $selfdir queue_oo.tcl]
} else {
source [file join $selfdir queue_tcl.tcl]
}
set r 1
}
default {
return -code error "invalid accelerator/impl. package $key: must be one of [join [KnownImplementations] {, }]"
}
}
set accel($key) $r
return $r
}
# ::struct::queue::SwitchTo --
# Activates a loaded named implementation.
proc ::struct::queue::SwitchTo {key} {
variable accel
variable loaded
if {[string equal $key $loaded]} {
# No change, nothing to do.
return
} elseif {![string equal $key ""]} {
# Validate the target implementation of the switch.
if {![info exists accel($key)]} {
return -code error "Unable to activate unknown implementation \"$key\""
} elseif {![info exists accel($key)] || !$accel($key)} {
return -code error "Unable to activate missing implementation \"$key\""
}
}
# Deactivate the previous implementation, if there was any.
if {![string equal $loaded ""]} {
rename ::struct::queue ::struct::queue_$loaded
}
# Activate the new implementation, if there is any.
if {![string equal $key ""]} {
rename ::struct::queue_$key ::struct::queue
}
# Remember the active implementation, for deactivation by future
# switches.
set loaded $key
return
}
# ::struct::queue::Implementations --
# Determines which implementations are
# present, i.e. loaded.
proc ::struct::queue::Implementations {} {
variable accel
set res {}
foreach n [array names accel] {
if {!$accel($n)} continue
lappend res $n
}
return $res
}
# ::struct::queue::KnownImplementations --
# Determines which implementations are known
# as possible implementations.
proc ::struct::queue::KnownImplementations {} {
return {critcl tcl}
}
proc ::struct::queue::Names {} {
return {
critcl {tcllibc based}
tcl {pure Tcl}
}
}
## Initialization: Data structures.
namespace eval ::struct::queue {
variable selfdir [file dirname [info script]]
variable accel
array set accel {tcl 0 critcl 0}
variable loaded {}
}
## Initialization: Choose an implementation,
## most preferred first. Loads only one of the
## possible implementations. And activates it.
namespace eval ::struct::queue {
variable e
foreach e [KnownImplementations] {
if {[LoadAccelerator $e]} {
SwitchTo $e
break
}
}
unset e
}
## Ready
namespace eval ::struct {
# Export the constructor command.
namespace export queue
}
package provide struct::queue 1.4.2
- In this implementation the coordinator renames the commands of the
low-level packages to the public commands, making the future dispatch as
fast as if the commands had these names anyway, but also forcing a spike
of bytecode recompilation if switching is ever done at the runtime of an
application, and not just used for testing, and possibly disrupting
introspection by the commands, especially if they move between different
namespaces.
A different implementation would be to provide the public commands as
procedures which consult a variable to determine which of the loaded
implementations is active, and then call on its commands. This doesn't
disrupt introspection, nor does it trigger bytecode recompilation on
switching. But it takes more time to dispatch to the actual
implementation, in every call of the public API for the package in
question.
A concrete example of this scheme can be found in Tcllib's crc32
package.
- [2]
- Mix the pieces together. Please note that while I am describing how to
make this work I strongly prefer and recommend to use the previously shown
approach using separate files/packages. It is much easier to understand
and maintain. With this warning done, lets go into the nuts and bolts.
If we care only about mode "compile & run" things are
easy:
package require critcl
if {![critcl::compiling]} {
proc mycommand {...} {
...
}
} else {
critcl::cproc mycommand {...} {
...
}
}
- The command critcl::compiling tells us whether we have a compiler
available or not, and in the latter case we implement our command in Tcl.
Now what happens when we invoke mode "generate package" ?
UNLAZY PACKAGES¶
By default critcl is a bit inconsistent between modes "compile &
run" and "generate package". The result of the latter is a
standard Tcl package which loads and sources all of its files immediately when
it is required. Whereas "compile & run" defers actual
compilation, linking, and loading until the first time one of the declared
commands is actually used, making this very lazy.
This behaviour can be quite unwanted if Tcl companion files, or other users of
the C commands use introspection to determine the features they have
available. Just using [
info commands] doesn't cut it, the
auto_index array has to be checked as well, making things quite
inconvenient for the users.
To fix this issue at the source, instead of in each user, be it inside of the
package itself, or other packages, we have the command
critcl::load.
Used as the last command in a "
.critcl" file it forces the
compile, link, and load trinity, ensuring that all C commands are available
immediately.
package require critcl
... Declare C procedures, commands, etc.
critcl::load ; # Force build and loading.
Note that is not allowed, nor possible to use critcl commands declaring anything
after
critcl::load has been called. I.e., code like
package require critcl
... Declare C procedures, commands, etc.
critcl::load ; # Force build and loading.
... More declarations of C code, ...
critcl::code { ... }
will result in an error. The only package-related commands still allowed are
- [1]
- critcl::done
- [2]
- critcl::failed
- [3]
- critcl::load
as these only query information, namely the build status, and are protected
against multiple calls.
CHECKING YOUR C¶
As said several times, by default critcl defers the compile and link steps for a
file until it is needed, i.e. the first command of the "
.critcl" file in question is actually invoked.
This not only has the effect of lazily loading the package's functionality, but
also, when developing using mode "compile & run", of us not
seeing any errors in our code until we are actually trying to run some
demonstration.
If we do not wish to have such a delay we have to be able to force at least the
execution of the compile step.
The command
critcl::failed is exactly that. When called it forcibly
builds the C code for the "
.critcl" file it is part of, and
returns a boolean vlaue signaling failure (
true), or success (
false).
package require critcl
... Declare C procedures, commands, etc.
if {[critcl::failed]} {
... signal error
}
It is related and similar to
critcl::load, the command to overcome the
lazy loading, as shown in section
Unlazy Packages.
Like it is not allowed, nor possible to use critcl commands declaring anything
after
critcl::failed has been called, making it pretty much the last
critcl command in a "
.critcl" file. Code like
package require critcl
... Declare C procedures, commands, etc.
if {[critcl::failed]} { ... }
... More declarations of C code, ...
critcl::code { ... }
will result in an error. The only package-related commands still allowed are
- [1]
- critcl::done
- [2]
- critcl::failed
- [3]
- critcl::load
as these only query information, namely the build status, and are protected
against multiple calls.
WHICH TCL ?¶
When building the shared library from the embedded C sources one of the things
critcl does for us is to provide the Tcl headers, especially the stubs
declarations.
By default these are the Tcl 8.4 headers and stubs, which covers 90% of the
cases. What when the package in question is meant for use with Tcl 8.5 or
higher, using C-level features of this version of Tcl.
Use the
critcl::tcl command to declare to critcl the minimum version of
Tcl required to operate the package. This can be either
8.4,
8.5, or
8.6, and critcl then supplies the proper headers and
stubs.
package require critcl
critcl::tcl 8.5
... Declare your code ...
stubs. For our convenience we have a simple, single command to activate all the
necessary machinery, with critcl supplying the header files and stubs C code,
instead of having to make it work on our own via
critcl::cflags,
critcl::ldflags,
critcl::cheaders,
critcl::csources.
This command is
critcl::tk.
package require critcl
critcl::tk ; # And now critcl knows to put in the Tk headers and other support.
... Declare your code ...
Please note that this doesn't release you from the necessity of learning Tk's C
API and how to use it to make a widget work. Sorry.
CHECKING THE ENVIRONMENT¶
library. The headers for this library may be found in non-standard locations,
ditto for the library/ies itself. We may not have the headers and/or library
on the build host. Types with platform-dependent sizes and definitions.
Endianness issues. Any number of things.
TEA-based packages can use
autoconf and various predefined macros to deal
with all this. We have the
Power Of Tcl (tm) and
critcl::check.
This command takes a piece of C code as argument, like
critcl::ccode.
Instead of saving it for later it however tries to compile it immediately,
using the current settings, and then returns a boolean value reporting on the
success (
true) or failure (
false). From there we can then
branch to different declarations.
As example let us check for the existence of some header "
FOO.h":
package require critcl
if {[critcl::check {
#include <FOO.h>
}]} {
... Code for when FOO.h is present.
} else {
... Code for when FOO.h is not present.
}
Should we, on the other hand, wish to search for the header ourselves, in
non-standard locations we have the full power of Tcl available, i.e. loops,
the
file and
glob commands, etc., which can then be followed by
a
critcl::cheader command to declare the location we found (See also
Finding header files).
A nice extension to critcl would be a package collecting pocedures for common
tasks like that, sort of like an
autoconf for Tcl.
critcl::config seems to be nice name for such a package.
Obvious adjunct commands which can be driven by results from
critcl::check are
- critcl::cflags
- critcl::cheaders
- critcl::clibraries
- critcl::framework
- critcl::ldflags
Less obvious, yet still valid are also
- critcl::ccode
- critcl::ccommand
- critcl::cdata
- critcl::cproc
- critcl::csources
- critcl::ctsources
and pretty much everything else you can imagine.
LICENSE INVOKED¶
When writing packages it is always good manners to provide prospective users
with the license the package is under, so that they can decide whether they
truly want to use the package, or not.
As critcl-based packages often consist of only a single file a nice way of doing
that is to embed the license in that file. By using a critcl command, namely
critcl::license this information is then also available to the critcl
application, which can put it into a standard location, i.e. "
license.terms", of the generated packages.
I currently have no specific example to demonstrate the command.
BUILDING CRITCL PACKAGES¶
This is the section for developers having to generate packages from "
.critcl" files, i.e binaries for deployment,
GETTING HELP ...¶
prints the basics of using the application to
stdout.
PRE-FILLING THE RESULT CACHE¶
The default mode of the
critcl application is to take a series of "
.critcl" files, build their binaries, and leave them behind in the
result cache. When the files are later actually used the compile and link
steps can be skipped, leading to shorter load times.
The command line for this is
or, to process multiple files
critcl foo.tcl bar.tcl ...
One thing to be aware of, should
critcl find that the cache already
contains the results for the input files, no building will be done. If you are
sure that these results are outdated use the option
-force to
force(sic!)
critcl to rebuild the binaries.
For debugging purposes it may be handy to see the generated intermediate "
.c" files as well. Their removal from the cache can be prevented
by specifying the option
-keep.
These can be combined, of course.
BUILDING A PACKAGE¶
To build the binary package for a "
.critcl" file, instead of
Pre-Filling The Result Cache, simply specify the option
-pkg.
This will geneate a package named
foo. A simpler alternative to the above
is
The application will automatically assume that the input file to look for is
"
foo.tcl".
But what when the name of the input file is not the name of the package to build
? This we can handle as well:
The argument
foo specifies the name, and "
bar.tcl" is
the file to process.
Going back to the very first example, it is of course possible to use an
absolute path to specify the file to process:
critcl -pkg /path/to/foo.tcl
The package name derived from that is still
foo.
BUILDING AND INSTALLING A PACKAGE¶
Here we assume that you know the basics of how to build a package. If not,
please read section
Building A Package first.
By default
critcl will place all newly-made packages in the subdirectory
"
lib" of the current working directory. I.e. running
will create the directory "
lib/foo" which contains all the
files of the package.
When this behaviour is unwanted the option
-libdir is available, allowing
the explicit specification of the destination location to use.
critcl -pkg -libdir /path/to/packages foo
A common use might be to not only build the package in question, but to also
immediately install it directly in the path where the user's
tclsh will
be able to find it. Assuming, for example, that the
tclsh in question
is installed at "
/path/to/bin/tclsh", with the packages
searched for under "
/path/to/lib" ([
info library]),
the command
critcl -pkg -libdir /path/to/lib foo
will build the package and place it in the directory "
/path/to/lib/foo".
BUILDING FOR DEBUGGING¶
Here we assume that you know the basics of how to build a package. If not,
please read section
Building A Package first.
An important issue, when there is trouble with the package, debugging becomes
necessary a evil. Critcl supports this through the
-debug option. Using
it enables various build modes which help with that.
For example, to activate the Tcl core's built-in memory debugging subsystem
build your package with
critcl -pkg -debug memory foo
The resulting binary for package
foo will use Tcl's debug-enabled
(de)allocation functions, making them visible to Tcl's
memory command.
This of course assumes that the Tcl core used was also built for memory
debugging.
Further, built your package with
critcl -pkg -debug symbols foo
to see the
foo's symbols (types, functions, variables, etc.) when
inspecting a "
core" file it is involved in with a symbolic
debugger,
To activate both memory debugging and symbols use either
critcl -pkg -debug all foo
or
critcl -pkg -debug symbols -debug memory foo
RETARGETING THE BINARIES¶
The configuration settings
critcl uses to drive the compiler, linker,
etc. are by default selected based on the platform it is run on, to generate
binaries which properly work on this platform.
There is one main use-case for overriding this selection, which is done with the
option
-target:
- [1]
- Cross-compilation. The building of binaries for a platform T while critcl
actually runs on platform B. The standard configuration of critcl
currently has settings for two cross-compilation targets. So, to build
32bit Windows binaries on a Linux host which has the Xmingw
cross-compilation development environment installed use
critcl -pkg -target mingw32 foo
- Similarly, building a package for use on ARM processors while critcl is
running in an Intel environment use
critcl -pkg -target linux-arm foo
- Note that both configurations assume that the cross-compiling compiler,
linke, etc. are found first in the PATH.
CUSTOM CONFIGURATIONS¶
The compiler configurations coming with
critcl currently cover all hosts
having
gcc installed (the foremost among these are Linux and OS X),
plus the native compilers of the more common unix-like operating systems, i.e.
Solaris, HP-UX, and AIX, and, on the non-unix side, Windows.
Developers using operating systems and compilers outside of this range will
either have to install a
gcc-based development environment, i.e. get
into the covered range, or write their own custom configuration and then tell
critcl about it.
The latter is the easier part, given that
critcl supports the option
-config whose argument is the path to the file containing the custom
configuration(s). I.e.
critcl -config /path/to/config ...
will run
critcl with the custom configuration in "
/path/to/config", with the other options and arguments as
explained in previous sections. Depending on the choice of name for the new
configuration(s) this may or may not require a
-target option to select
the configuration needed.
For the former, the writing of the custom configuration, the reader is refered
to the section "Configuration Internals" of the
CriTcl Package
Reference for the necessary details. This is an advanced topic pretty much
out of scope for this tutorial beyond what was already said.
Sometimes the use of
critcl::headers might not be enough for a package to
find its headers. Maybe they are outside of the paths checked by the setup
code. To help the application recognizes the option
-I which allows the
user to supply a single additional include path to use during the build phase
of the package.
Simply use
critcl -I /path/to/header ...
and the specified header will be handed to the package to be built.
INTROSPECTION OF TARGETS AND CONFIGURATIONS¶
To see a list containing the names of all the available configurations, run
The configuration settings for either the default or user-chosen target can be
inspected on
stdout with
and
critcl -show -target TARGET
The raw contents of the configuration file used by
critcl are dumped to
stdout with
All of the above can of course be combined with custom configuration files.
AUTHORS¶
Jean Claude Wippler, Steve Landers, Andreas Kupries
BUGS, IDEAS, FEEDBACK¶
This document, and the package it describes, will undoubtedly contain bugs and
other problems. 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 and should be reported at
https://github.com/andreas-kupries/critcl/issues as well.
KEYWORDS¶
C code, Embedded C Code, 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
COPYRIGHT¶
Copyright (c) Jean-Claude Wippler
Copyright (c) Steve Landers
Copyright (c) 2011-2013 Andreas Kupries