1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403
|
# Hacking on GCLI
This document gives you hints to get started working with on source
code of [gcli](https://herrhotzenplotz.de/gcli/).
Please note that this document only captures the state of the code at
the some points in time and may be out of date. If you feel like this
is the case please submit bug reports or, even better, provide
patches.
## Building GCLI
We use handwritten Makefiles to build GCLI. This has a few advantages:
- Portability across many platforms, even many older ones
- I (Nico) know Makefiles fairly well
- Cross-Compilation can easily be done
- High flexibility
- Few to no dependencies
- Short compilation times
A few caveats are:
- The Makefile must work with at least 3 implementations of Make:
- BSD Make (bmake)
- Schily SMake (smake)
- GNU Make (gmake)
Some of these make implementations are very buggy (most notably GNU make)
- Getting target dependencies just right is not easy
For that reason I highly suggest testing with all three make implementations.
### General workflow
A hand written shell script called `configure` is run inside a
directory where to place build files.
This script checks the environment for various properties such as:
- The compiler to use
- Compiler options
- Target system properties
- Dependencies and Libraries
- Additional tooling that can be used
The script allows you to configure multiple build directories from
a single source directory (so called "out of tree builds").
The following example shows you how to configure a default build directory:
$ mkdir build
$ cd build/
$ ../configure
Once the configure script has run you can run make to build gcli:
$ make
To install gcli to the default prefix (`/usr/local`) you can run:
$ make install
To run the test suite:
$ make check
Note that running the test suite requires ATF and Kyua. More details
can be found below.
To run the compiler's built-in static code analyser:
$ make analyse
If you wish to change the compiler to be used you can set these in the
environment:
$ env CC=/usr/local/bin/clang17 ../configure
To build a default release build with optimisations you can run:
$ ../configure --release
Check the built-in help of the configure script for more details:
$ ../configure --help
#### Full Debug build
The configure script comes with a `--debug` flag that configures a
directory for a build with no optimisations and full debug info.
I suggest you use it for development purposes:
$ ../configure --debug --enable-maintainer
You can proceed as usual with make.
#### Sanitized Builds
TBD
#### Cross-Compilation
gcli supports cross compilation. A cross-compilation setup can be
achieved by setting one or more of the following environment
variables:
- `CC` to the target system (aka. host) compiler
- `CFLAGS` for setting `--target` and/or `--sysrooot`
- `CC_FOR_BUILD` to the build system (aka. build) compiler
- `CFLAGS_FOR_BUILD` for configuring build system `CFLAGS`
- `PKG_CONFIG_PATH` to the path where pkgconfig should look for `.pc` files
You possibly also need:
- `PKG_CONFIG_LIBDIR` to the path where the system `.pc` files are stored
- `PKG_CONFIG_SYSROOT_DIR` as a sysroot prefix in case the compiler doesn't prefix the paths internally
So, e.g. set up a sysroot for FreeBSD aarch64 in `/store/sysroot/arm64` and then build against it:
$ env \
> CFLAGS="--target=aarch64-unknown-freebsd14.3 --sysroot=/store/sysroot/arm64" \
> PKG_CONFIG_LIBDIR=/store/sysroot/arm64/usr/libdata/pkgconfig \
> PKG_CONFIG_PATH=/store/sysroot/arm64/usr/local/libdata/pkgconfig \
> PKG_CONFIG_SYSROOT_DIR=/store/sysroot/arm64 \
> ../configure --release
Checking for realpath ... /bin/realpath
Checking host compiler ... cc
Checking host compiler type ... clang
Checking host compiler target ... aarch64-unknown-freebsd14.3
Checking for build compiler... cc
Checking build compiler type... clang
Checking build compiler target ... amd64-unknown-freebsd14.3
Checking for pkg-config ... /usr/local/bin/pkg-config
Checking for libcurl ... found
Checking for libcrypto ... found
Checking for libedit ... not found
Checking for readline ... not found
Checking for lowdown ... found
Checking for pdjson ... not found
Checking whether host is Darwin for -lrt... no
Checking for ccache ... /usr/local/bin/ccache
Checking for install ... /usr/bin/install
Checking for perl ... /usr/local/bin/perl
Writing config.h
Configuration summary for gcli 2.10.0-devel:
Build system type: amd64-unknown-freebsd14.3
Host system type: aarch64-unknown-freebsd14.3
optimise for: release
Executable extension: Host: '', Build: ''
Object extension: Host: '.o', Build: '.o'
Library extension: Host: '.a', Build: '.a'
CC: cc
CC_FOR_BUILD: cc
CFLAGS: --target=aarch64-unknown-freebsd14.3 --sysroot=/store/sysroot/arm64
CFLAGS_FOR_BUILD:
LDFLAGS:
LDFLAGS_FOR_BUILD:
LIBCURL_CFLAGS: -I/store/sysroot/arm64/usr/local/include -I/store/sysroot/arm64/usr/include
LIBCURL_LIBS: -L/store/sysroot/arm64/usr/local/lib -lcurl
Using lowdown 2.0.2:
LIBLOWDOWN_CFLAGS: -I/store/sysroot/arm64/usr/local/include
LIBLOWDOWN_LIBS: -L/store/sysroot/arm64/usr/local/lib -llowdown -lm
WARNING: pdjson not found, using vendored version.
You may wish to install a system version instead.
Configuration done. You may now run make.
For more information about available build targets, run 'make help'.
When you now run make the compilers will be chosen appropriately.
The test suite will not work when cross-compiling. Notice how the
`CC` and `CC_FOR_BUILD` are omitted due to the fact that they are
the same clang, just with different flags for build and host.
If needed you may have to set the values of
- `EXEEXT`
- `OBJEXT`
- `LIBEXT`
This is the case on undetected Windows systems.
It should be possible to build gcli under cygwin this way.
## Tests
The test suite currently depends on Perl 5.
A somewhat recent version should suffice.
To run the test suite in a configured directory `build` run:
$ make -C build check
You may also invoke the test harness runner directly:
$ tests/run.pl -h
You should get a simple usage message from it.
### Examples
Suppose you are in the source tree root and your build root is in
`build`. To run all tests in parallel with 12 runners:
$ tests/run.pl -b build -j 12
To run all tests tagged wit "github":
$ tests/run.pl -b build github
To run the tests 3, 4 and 5 in parallel:
$ tests/run.pl -b build -j 3 3 4 5
# Code Style
Please use the BSD Style conventions for formatting your code. This means:
- Functions return type and name go on separate lines, no mixed code
and declarations (except in for loops):
void
foo(int bar)
{
int x, y, z;
x = bar;
for (int i = 0; i < 10; ++i)
z += i;
return x;
}
This allows to search for the implementation of a function through a
simple `grep -rn '^foo' .`.
- Use struct tags for structs, do not typedef them
struct foo {
int bar;
char const *baz;
};
static void
foodoo(struct foo const *const bar)
{
}
- Indent with tabs, align with spaces
`»` denotes a TAB character, `.` indicates a whitespace:
void
foo(struct foo const *thefoo)
{
» if (thefoo)
» » printf("%s: %d\n"
» » .......thefoo->wat,
» » .......thefoo->count);
}
- Try to have a max of 80 characters per line
I know we're not using punchcards anymore, however it makes the code
way more readable.
- Use C11
Please don't use C17 or even more modern features. Reason being that
I want gcli to be portable to older platforms where either no modern
compilers are available or where we have to rely on old gcc versions
and/or buggy vendor compilers. This also means that GNU extensions
are forbidden. If you use the compiler flags I mentioned above
you should get notified by the compiler.
There is a `.editorconfig` included in the source code that should
automatically provide you with all needed
options. [Editorconfig](https://editorconfig.org/#pre-installed) is a
plugin that is available for almost all notable editors out there. I
highly recommend you use it.
# Adding support for new forges
The starting point for adding forges is
[include/gcli/forges.h](include/gcli/forges.h). This file contains the
dispatch table for fetching data from various kinds of forges.
A pointer to the current dispatch table can be retrieved through a
call to `gcli_forge()`. You may have to adjust the routines called by
it to allow for automagic detection as well as overrides on the
command line for your new forge type. You should likely never call
`gcli_forge()` directly when adding a new forge type as there are
various frontend functions available that will do dispatching for the
caller.
## Parsing JSON
When you need to parse JSON Objects into C structs you likely want to
generate that code. Please see the [templates/](templates/) directory
for examples on how to do that. Currently the [PR Parser for
Github](templates/github/pulls.t) can act as an example for all
features available in the code generator.
The code generator is fully documented in [pgen.org](docs/pgen.org).
## Generating JSON
We not only need to parse JSON often, we also need to generate it
on the fly when submitting data to forge APIs.
For this the `gcli_jsongen_` family of functions exist. Since these
have been introduced quite late in the project their use is not
particularly wide-spread. However this may change in the future.
To use these, take a look at the header
[include/gcli/json_gen.h](include/gcli/json_gen.h) and also the use
in [src/gitlab/merge_requests.c](src/gitlab/merge_requests.c).
# User Frontend Features
The gcli command line tool links against libgcli. Through a context
structure it passes information like the forge type and user
credentials into the library.
All code for the command line frontend tool is found in the
[src/cmd/](src/cmd/) directory.
[src/cmd/gcli.c](src/cmd/gcli.c) is the entry point for the command
line tool. In this file you can find the dispatch table for all
subcommands of gcli.
## Subcommands
Subcommand implementations are found in separate C files in the
`src/cmd` subdirectory.
When parsing command line options please use `getopt_long`. Do not
forget to prefix your getopt string with a `+` as we do multiple calls
to `getopt_long` so it needs to reset some internal state.
## Output formatting
Output is usually formatted as a dictionary or a table. For these
cases gcli provides a few convenience functions and data structures.
The relevant header is [gcli/cmd/table.h](include/gcli/cmd/table.h).
Do not use these functions in the library code. It's only supposed
to be used from the command line tool.
### Tables
You can print tables by defining a list of columns first:
```C
gcli_tblcoldef cols[] = {
{ .... },
{ .... },
};
```
For a complete definition look at the header or uses of that interface
in e.g. [src/cmd/issues.c](src/cmd/issues.c).
You can then start adding rows to your table:
```C
gcli_tbl table = gcli_tbl_begin(cols, ARRAY_SIZE(cols));
for (int i = 0; i < list.whatever_size; ++i) {
gcli_tbl_add_row(table, ...);
}
```
The variadic arguments you need to provide depends on the columns
defined. Most relevant is the flags and type field. Make sure you get
data type sizes correct.
To dump the table to stdout use the following call:
```C
gcli_tbl_end(table);
```
This will print the table and free all resources acquired by calls to
the tbl routines. You may no reuse the handle returned by
`gcli_tbl_begin()` after this call. Instead, call the begin routine
again to obtain a new handle.
### Dictionaries
The dictionary routines act almost the same way as tables except that
you don't define the columns. Instead you obtain a handle through
`gcli_dict_begin` and add entries to the dictionary by calling
`gcli_dict_add` or one of the specialized functions for
strings. `gcli_dict_add` is the most generic of them all and provides
a printf-like format and variadic argument list. You can dump the
dictionary and free resources through a call to `gcli_dict_end`.
|