c3932e6267
* More detailed instructions in README I struggled to get the project to build for a while when first getting started, so have added some instructions; and also some usage instructions for automerge-c that show more clearly what is happening without `AMpush()`
158 lines
5.8 KiB
Markdown
158 lines
5.8 KiB
Markdown
automerge-c exposes an API to C that can either be used directly or as a basis
|
|
for other language bindings that have good support for calling into C functions.
|
|
|
|
# Building
|
|
|
|
See the main README for instructions on getting your environment set up, then
|
|
you can use `./scripts/ci/cmake-build Release static` to build automerge-c.
|
|
|
|
It will output two files:
|
|
|
|
- ./build/Cargo/target/include/automerge-c/automerge.h
|
|
- ./build/Cargo/target/release/libautomerge.a
|
|
|
|
To use these in your application you must arrange for your C compiler to find
|
|
these files, either by moving them to the right location on your computer, or
|
|
by configuring the compiler to reference these directories.
|
|
|
|
- `export LDFLAGS=-L./build/Cargo/target/release -lautomerge`
|
|
- `export CFLAGS=-I./build/Cargo/target/include`
|
|
|
|
If you'd like to cross compile the library for different platforms you can do so
|
|
using [cross](https://github.com/cross-rs/cross). For example:
|
|
|
|
- `cross build --manifest-path rust/automerge-c/Cargo.toml -r --target aarch64-unknown-linux-gnu`
|
|
|
|
This will output a shared library in the directory `rust/target/aarch64-unknown-linux-gnu/release/`.
|
|
|
|
You can replace `aarch64-unknown-linux-gnu` with any [cross supported targets](https://github.com/cross-rs/cross#supported-targets). The targets below are known to work, though other targets are expected to work too:
|
|
|
|
- `x86_64-apple-darwin`
|
|
- `aarch64-apple-darwin`
|
|
- `x86_64-unknown-linux-gnu`
|
|
- `aarch64-unknown-linux-gnu`
|
|
|
|
As a caveat, the header file is currently 32/64-bit dependant. You can re-use it
|
|
for all 64-bit architectures, but you must generate a specific header for 32-bit
|
|
targets.
|
|
|
|
# Usage
|
|
|
|
For full reference, read through `automerge.h`, or to get started quickly look
|
|
at the
|
|
[examples](https://github.com/automerge/automerge-rs/tree/main/rust/automerge-c/examples).
|
|
|
|
Almost all operations in automerge-c act on an AMdoc struct which you can get
|
|
from `AMcreate()` or `AMload()`. Operations on a given doc are not thread safe
|
|
so you must use a mutex or similar to avoid calling more than one function with
|
|
the same AMdoc pointer concurrently.
|
|
|
|
As with all functions that either allocate memory, or could fail if given
|
|
invalid input, `AMcreate()` returns an `AMresult`. The `AMresult` contains the
|
|
returned doc (or error message), and must be freed with `AMfree()` after you are
|
|
done to avoid leaking memory.
|
|
|
|
```
|
|
#include <automerge-c/automerge.h>
|
|
#include <stdio.h>
|
|
|
|
int main(int argc, char** argv) {
|
|
AMresult *docResult = AMcreate(NULL);
|
|
|
|
if (AMresultStatus(docResult) != AM_STATUS_OK) {
|
|
printf("failed to create doc: %s", AMerrorMessage(docResult).src);
|
|
goto cleanup;
|
|
}
|
|
|
|
AMdoc *doc = AMresultValue(docResult).doc;
|
|
|
|
// useful code goes here!
|
|
|
|
cleanup:
|
|
AMfree(docResult);
|
|
}
|
|
```
|
|
|
|
If you are writing code in C directly, you can use the `AMpush()` helper
|
|
function to reduce the boilerplate of error handling and freeing for you (see
|
|
examples/quickstart.c).
|
|
|
|
If you are wrapping automerge-c in another language, particularly one that has a
|
|
garbage collector, you can call `AMfree` within a finalizer to ensure that memory
|
|
is reclaimed when it is no longer needed.
|
|
|
|
An AMdoc wraps an automerge document which are very similar to JSON documents.
|
|
Automerge documents consist of a mutable root, which is always a map from string
|
|
keys to values. Values can have the following types:
|
|
|
|
- A number of type double / int64_t / uint64_t
|
|
- An explicit true / false / nul
|
|
- An immutable utf-8 string (AMbyteSpan)
|
|
- An immutable array of arbitrary bytes (AMbyteSpan)
|
|
- A mutable map from string keys to values (AMmap)
|
|
- A mutable list of values (AMlist)
|
|
- A mutable string (AMtext)
|
|
|
|
If you read from a location in the document with no value a value with
|
|
`.tag == AM_VALUE_VOID` will be returned, but you cannot write such a value explicitly.
|
|
|
|
Under the hood, automerge references mutable objects by the internal object id,
|
|
and `AM_ROOT` is always the object id of the root value.
|
|
|
|
There is a function to put each type of value into either a map or a list, and a
|
|
function to read the current value from a list. As (in general) collaborators
|
|
may edit the document at any time, you cannot guarantee that the type of the
|
|
value at a given part of the document will stay the same. As a result reading
|
|
from the document will return an `AMvalue` union that you can inspect to
|
|
determine its type.
|
|
|
|
Strings in automerge-c are represented using an `AMbyteSpan` which contains a
|
|
pointer and a length. Strings must be valid utf-8 and may contain null bytes.
|
|
As a convenience you can use `AMstr()` to get the representation of a
|
|
null-terminated C string as an `AMbyteSpan`.
|
|
|
|
Putting all of that together, to read and write from the root of the document
|
|
you can do this:
|
|
|
|
```
|
|
#include <automerge-c/automerge.h>
|
|
#include <stdio.h>
|
|
|
|
int main(int argc, char** argv) {
|
|
// ...previous example...
|
|
AMdoc *doc = AMresultValue(docResult).doc;
|
|
|
|
AMresult *putResult = AMmapPutStr(doc, AM_ROOT, AMstr("key"), AMstr("value"));
|
|
if (AMresultStatus(putResult) != AM_STATUS_OK) {
|
|
printf("failed to put: %s", AMerrorMessage(putResult).src);
|
|
goto cleanup;
|
|
}
|
|
|
|
AMresult *getResult = AMmapGet(doc, AM_ROOT, AMstr("key"), NULL);
|
|
if (AMresultStatus(getResult) != AM_STATUS_OK) {
|
|
printf("failed to get: %s", AMerrorMessage(getResult).src);
|
|
goto cleanup;
|
|
}
|
|
|
|
AMvalue got = AMresultValue(getResult);
|
|
if (got.tag != AM_VALUE_STR) {
|
|
printf("expected to read a string!");
|
|
goto cleanup;
|
|
}
|
|
|
|
printf("Got %zu-character string `%s`", got.str.count, got.str.src);
|
|
|
|
cleanup:
|
|
AMfree(getResult);
|
|
AMfree(putResult);
|
|
AMfree(docResult);
|
|
}
|
|
```
|
|
|
|
Functions that do not return an `AMresult` (for example `AMmapItemValue()`) do
|
|
not allocate memory, but continue to reference memory that was previously
|
|
allocated. It's thus important to keep the original `AMresult` alive (in this
|
|
case the one returned by `AMmapRange()`) until after you are done with the return
|
|
values of these functions.
|
|
|
|
Beyond that, good luck!
|