CTinker - C/C++ Project Introspection and Augmentation Tool
C Tinker, pronounced see-tinker (or humorously "stinker", as suggested by Chuck Ocheret) allows you to get in the middle of the build process of a make/Ninja-style project and augment the compilation and linking as well as extract and redirect artifacts using policies you can't implement otherwise even with LDFLAGS/CFLAGS magic.
Problem
More formally the problem CTinker solves can be stated as follows:
I need to get in the middle of a build process of a project I can know intimately but do not control and that I have no intention of maintaining a fork/patches for, or for which I need to obtain runtime dynamic control of the build process.
Solution
Overview
CTinker is capable of getting in the middle of virtually any build process by:
- Starting in the
supervisormode. - Creating a temporary directory full of toolkit-specific (e.g. for LLVM Clang it's
clang,aretc) symlinks referring back toCTinkerexecutable. - Setting up environ and a local socket to communicate with the
workers. - Invoking the build process as specified by the user.
- Being invoked for each tool invocation in a
workermode (based on environmental variables), communicating with thesupervisor, sending command-line arguments to thesupervisorprocess and then invoking the tool itself. - If specified, invoking
scriptinghandlers before and after the build as a whole (in thesupervisor) and before and after each intercepted tool invocation (in theworker).
As a further illustration, if the original process invocation chain for a sample build is as follows:
make => clang => lld, => make => clang, => clang => lld
then the same build instrumented with CTinker will produce the following process invocation chain:
ctinker => make => ctinker-clang => clang => ctinker-lld => lld, => make => ctinker-clang => clang, => ctinker-clang => clang => ctinker-lld => lld
Scripting
Scripting is the most powerful part of CTinker that provides an ability to really change how build functions
at runtime. It is implemented via a visitor pattern, invoking functions specified in the user-supplied script:
def ctinker_start(env: Dict[str, str], work_dir: Path):
"""Invoked by CTinker `supervisor` prior to the main build process
Changes to the `env` dictionary propagate to the main build process.
"""
pass
def ctinker_finish(env: Dict[str, str], work_dir: Path, tool_calls: List[Tuple[Any]], return_code: int):
"""Invoked by CTinker `supervisor` after the main build process exits
`tool_calls` is a `list` of `tuple`s of `(tool, tool_args, return_code, cwd, script_result)`, where `script_result`
is the value returned by `ctinker_after_tool`.
"""
pass
def ctinker_before_tool(env: Dict[str, str], tool: str, tool_args: List[str], work_dir: Path, cwd: Path):
"""Invoked by CTinker `worker` prior to the tool process
Changes to the `env` dictionary propagate to the tool process.
Changes to the `tool_args` list propagate to the tool process.
"""
pass
def ctinker_after_tool(env: Dict[str, str], tool: str, tool_args: List[str], work_dir: Path, cwd: Path,
return_code: int) -> Any:
"""Invoked by CTinker `worker` after the tool process exits
Returned value, **if truthy**, will be stored and will appear
as the last entry in the `tool_calls` passed to `ctinker_finish`
"""
pass
It is guaranteed that ctinker_start - ctinker_finish and ctinker_before_tool - ctinker_after_tool pairs will
be executed in the same supervisor and worker processes respectively and therefore you can pass values between
the start/finish and before/after functions (for example by a global or within the same instance of an object).
Help
$ ctinker --help
usage: ctinker [-h] [-s SCRIPT] [-o OUT] [-f {text,pickle}] [-p PATHS]
[-w WORK_DIR] -t {clang} [-l [{clang,__default}]]
[-L LINKER_TOOL_OVERRIDE]
[--linker-flags-name LINKER_FLAGS_NAME]
...
CTinker project introspection and augmentation tool
positional arguments:
command build command to execute
optional arguments:
-h, --help show this help message and exit
-s SCRIPT, --script SCRIPT
a Python script containing visitor-style hooks
(default: None)
-o OUT, --out OUT a path to a file where all tools, their arguments and
exit codes will be recorded (default: None)
-f {text,pickle}, --format {text,pickle}
the format of the output file (default: text)
-p PATHS, --path PATHS
prepend a leading PATH entry to be inherited by the
invoked command (default: None)
-w WORK_DIR, --work-dir WORK_DIR
sets a work directory to be something other than
current working directory (default: )
-t {clang}, --toolkit {clang}
enable specific compilation interception modes
(default: None)
-l [{clang,__default}], --linker-intercept [{clang,__default}]
intercept linker with --linker-flags-name env var
using the specified toolkit (default: None)
-L LINKER_TOOL_OVERRIDE, --linker-tool-override LINKER_TOOL_OVERRIDE
specify linker tool name directly (may not work if no
toolkit provides it) (default: None)
--linker-flags-name LINKER_FLAGS_NAME
specify linker environmental variable (default:
LDFLAGS)
Example
TBW
Troubleshooting
- Printing to
sys.stdoutfrom theworkeris dangerous as the stdout is often interpreted by the invoking tool which can lead to a crash in the tool expecting certain data format.print("debug!", file=sys.stderr)is generally safe.
Metadata
Release files for ctinker 0.0.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| ctinker-0.0.4.tar.gz | 16.3 kB | Without provenance | Details for ctinker-0.0.4.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| ctinker-0.0.4-py3-none-any.whl | Python 3 | none | any | Details for ctinker-0.0.4-py3-none-any.whl |
Total release size: 31.1 kB
Release files / ctinker-0.0.4.tar.gz
| Download URL | ctinker-0.0.4.tar.gz |
|---|---|
| Size | 16.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f4625dcf840176d4498eb821118293fdf105a61526a36d02104a019b1239810a
|
|
BLAKE2b-256 checksum How to use checksums |
a3634e60d5bd62df15e612bcd744b306e1445da63da8e5bf8fa8ce4ce34bcb0e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/3.1.1 pkginfo/1.5.0.1 requests/2.23.0 setuptools/46.1.3 requests-toolbelt/0.9.1 tqdm/4.46.0 CPython/3.8.2
|
Release files / ctinker-0.0.4-py3-none-any.whl
| Download URL | ctinker-0.0.4-py3-none-any.whl |
|---|---|
| Size | 14.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cf21f67aa437d664aa2c83471d5e369805a62a0d2727dda1006691cca14136a7
|
|
BLAKE2b-256 checksum How to use checksums |
9afd18b77a6c2a7315b5d3037f8de11e20479f6c0fac72432dcb9f7ab0cce3f5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/3.1.1 pkginfo/1.5.0.1 requests/2.23.0 setuptools/46.1.3 requests-toolbelt/0.9.1 tqdm/4.46.0 CPython/3.8.2
|
Security for ctinker 0.0.4
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.