.capnp

0 views
Skip to first unread message

Kenneth Larson

unread,
Aug 3, 2024, 5:22:23 PM8/3/24
to inicpieglit

This generates files myschema.capnp.h and myschema.capnp.c++ which contain C++ source codecorresponding to the types defined in myschema.capnp. Options exist to control output locationand import paths.

The above example generates C++ code, but the tool is able to generate output in any languagefor which a plugin is available. Compiler plugins are just regular programs namedcapnpc-language. For example, the above command runs capnpc-c++. More on how to writecompiler plugins.

At first glance, this may seem no more interesting than capnp encode: the syntax used to defineconstants in schema files is the same as the format accepted by capnp encode, right? There is,however, a big difference: constants in schema files may be defined in terms of other constants,which may even be imported from other files.

As a result, capnp eval is a great basis for implementing config files. For example, a largecompany might maintain a production server that serves dozens of clients and needs configurationinformation about each one. Rather than maintaining the config as one enormous file, it can bewritten as several separate files with a master file that imports the rest.

Such a configuration should be compiled to binary format using capnp eval before deployment,in order to verify that there are no errors and to make deployment easier and faster. While youcould technically ship the text configs to production and have the servers parse them directly(e.g. with capnp::SchemaParser), encoding before deployment is more efficient and robust.

I am testing it and I have a .capnp file in a repository, where there are also the generated C++ libraries.There is no need to generate any code for Python user libraries, as there is an API that loads the .capnp file definition at runtime.

How can I set up the user/client libraries' repositories CI pipelines to have the latest .capnp definition available?They should have access to the generated libraries but also the .capnp file (for Python projects).

Since Cap'n Proto schemas are backwards-compatible, it's not necessary for people to stay up-to-date unless they actually need to use the new features in the latest schema version. So, what I usually do is copy the .capnp file from its main repo into any dependent repo, and update that copy whenever needed. So if I'm making a change to the dependent repo, and it needs a new field that was recently added to the upstream repo, I go ahead and copy the .capnp file again as part of the change.

Granted, it's a rather ad hoc way of doing things, but it seems to work fine in my experience. The one thing you have to make sure of is that people always update the schema in its main repo first, and then copy the change to dependents. Never make a change directly in the dependent repo, since it could conflict with changes made upstream.

Text fields will behave differently depending on your version of Python. In Python 2.x, Text fields will expect and return a bytes string, while in Python 3.x, they will expect and return a unicode string. Data fields will always a return bytes string.

Note that this very much needs to match the type you wrote out. In general, you will always be sending the same message types out over a given channel or you should wrap all your types in an unnamed union. Unnamed unions are defined in the .capnp file like so:

The above methods only guaranteed to work if your file contains a single message. If you have more than 1 message serialized sequentially in your file, then you need to use these convenience functions:

Asyncio support was added to pycapnp in v1.0.0 utilizing the TwoWayPipe interface to libcapnp (instead of having libcapnp control the socket communication). The main advantage here is that standard Python socket libraries can be used with pycapnp (more importantly, TLS/SSL). Asyncio requires a bit more boiler plate to get started but it does allow for a lot more control than using the pycapnp socket wrapper.

At a basic level, asyncio splits the input and output streams of the tcp socket and sends it to the libcapnp TwoWayPipe interface. An async reader Python function/method is used to consume the incoming byte stream and an async writer Python function/method is used to write outgoing bytes to the socket.

Like the asyncio client, an asyncio server takes a bunch of boilerplate as opposed to using the socket wrapper. Servers generally have to handle a lot more error conditions than clients so they are generally more complicated to implement with asyncio.

Returns work with a bit of magic as well. If you return a promise, then it will be handled the same as if you returned a promise from a server method in the C++ API. Otherwise, your return statement will be filled into the results struct following the ordering in your spec, for example:

This library uses SemVer tags to indicate stable releases.While the goal is that master should always be passing all known tests, tagged releases are vetted more.When possible, use the latest release tag.

Consider this package's API as beta software, since the Cap'n Proto spec is not final.In the spirit of the Go 1 compatibility guarantee, I will make every effort to avoid making breaking API changes.The major cases where I reserve the right to make breaking changes are:

capnpc-go requires two annotations for all files: package and import.package is needed to know what package to place at the head of thegenerated file and what identifier to use when referring to the typefrom another package. import should be the fully qualified import pathand is used to generate import statement from other packages and todetect when two types are in the same package. For example:

In Cap'n Proto, the unit of communication is a message. A messageconsists of one or more segments -- contiguous blocks of memory. Thisallows large messages to be split up and loaded independently or lazily.Typically you will use one segment per message. Logically, a message isorganized in a tree of objects, with the root always being a struct (asopposed to a list or primitive). Messages can be read from and writtento a stream.

The Message and Segment types are the main types that application codewill use from this package. The Message type has methods for marshalingand unmarshaling its segments to the wire format. If the applicationneeds to read or write from a stream, it should use the Encoder andDecoder types.

The type for a generic reference to a Cap'n Proto object is Ptr. A Ptrcan refer to a struct, a list, or an interface. Ptr, Struct, List, andInterface (the pointer types) have value semantics and refer to data ina single segment. All of the pointer types have a notion of "valid".An invalid pointer will return the default value from any accessor andpanic when any setter is called.

In previous versions of this package, the Pointer interface was usedinstead of the Ptr struct. This interface and functions that use it arenow deprecated. See -capnproto2/wiki/New-Ptr-Typefor details about this API change.

Data accessors and setters (i.e. struct primitive fields and listelements) do not return errors, but pointer accessors and setters do.There are a few reasons that a read or write of a pointer can fail, butthe most common are bad pointers or allocation failures. For accessors,an invalid object will be returned in case of an error.

Since Go doesn't have generics, wrapper types provide type safety onlists. This package provides lists of basic types, and capnpc-gogenerates list wrappers for named types. However, if you need to usedeeper nesting of lists (e.g. List(List(UInt8))), you will need to use aPointerList and wrap the elements.

In addition an enum.String() function is generated that will convert the constants to a stringfor debugging or logging purposes. By default, the enum name is used as the tag value,but the tags can be customized with a $Go.tag or $Go.notag annotation.

capnpc-go generates type-safe Client wrappers for interfaces. For parameterlists and result lists, structs are generated as described above with the namesInterface_method_Params and Interface_method_Results, unless a single structtype is used. For example, for this interface:

A note about message ordering: when implementing a server method, youare responsible for acknowledging delivery of a method call. Failure todo so can cause deadlocks. See the server.Ack function for more details.

Canonicalize encodes a struct into its canonical form: a single-segment blob without a segment table. The result will be identicalfor equivalent structs, even as the schema evolves. The blob issuitable for hashing or signing.

SingleSegment returns a new arena with an expanding single-segmentbuffer. b can be used to populate the segment for reading or toreserve memory of a specific size. A SingleSegment arena does notreturn errors unless you attempt to access another segment.

CallOptions holds RPC-specific options for an interface call.Its usage is similar to the values in context.Context, but is onlyused for a single call: its values are not intended to propagate toother callees. An example of an option would be theCall.sendResultsTo field in rpc.capnp.

Generally, only RPC protocol implementers should provide types thatimplement Client: call ordering guarantees, promises, andsynchronization are tricky to get right. Prefer creating a serverthat wraps another interface than trying to implement Client.

If you use setup.py or manual compilation, you need capnp tocompile the schema, but not to load it later; this means that you candistribute the precompiled schemas, and the client machines will be able toload it without having to install the official capnproto distribution.

modname (the default) is interpreted as if it were the name of a Pythonmodule with the .capnp extension. This means that it is searched in allthe directories listed in sys.path and that you can use dotted names toload a schema inside packages or subpackages:

This is handy because it allows you to distribute the capnproto schemas alongthe Python packages, and to load them with no need to care where they are onthe filesystem, as long as the package is importable by Python.

c80f0f1006
Reply all
Reply to author
Forward
0 new messages