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
|
# Call Rust code from a C binary
The purpose of this tutorial is to showcase how to mix different
programming languages using `justbuild`, along with the features that
these Rust rules have to foster the interoperability. For the sake of
simplicity, in this example we are going to write a library in Rust
that is consumed by a C binary. In particular, the program will read a
number from `stdin` and will print the absolute value, which is
computed via the call to a Rust function.
Spoiler: developers should pay attention to the definition of the
target for the `foo` library.
## Project structure
Let's start with the following structure.
```sh
$ tree
.
├── etc
│ └── repos.json
├── foo
│ ├── foo.h
│ ├── foo.rs
│ └── TARGETS
├── main.c
├── README.md
├── ROOT
└── TARGETS
2 directories, 8 files
```
## Required repositories
In the `repos.json` file we need import both the rules for compiling Rust and C code
```jsonc
// file: etc/repos.json
{ "main": "example"
, "repositories":
{ "example":
{ "repository": {"type": "file", "path": "."}
, "bindings": {"rules-rust": "rules-rust", "rules-cc": "rules-cc"}
}
, "rules-rust":
{ "repository":
{ "type": "git"
, "repository": "https://github.com/just-buildsystem/rules-rust"
, "branch": "master"
, "commit": "ed652442176aea086104479bb31aced501df48a2"
, "subdir": "rules"
}
}
, "rules-cc":
{ "repository":
{ "type": "git"
, "repository": "https://github.com/just-buildsystem/rules-cc"
, "branch": "master"
, "commit": "fac7e7680e00dfc63eec41a33dff86d31571eb4b"
, "subdir": "rules"
}
}
}
}
```
## The `foo` library
This library is made by a simple function that returns the absolute value of a number
```rust
// file: foo/foo.rs
#[no_mangle]
pub extern "C" fn foo(x: i32) -> i32 {
return x.abs();
}
```
As you may know, in C, before calling a function its signature must be
declared. Typically, this kind of information is written in header
files. The `justbuild` Rust rules allow for the definition of
companion header files, that can be maintained by the Rust library
developers. In this case, `foo.h` looks as follows
```C
/* file: foo/foo.h */
#pragma once
int foo(int);
```
Of course, we are not forced to write the corresponding header file,
and we could, for example, put the declaration of the functions we
want to use in the `main.c` file. However, having the headers make the
usage of the library much easier. Therefore, the Rust rules feature a
dedicated field `"c_hdrs"`, so, the target definition for the library
can be as follows
```jsonc
// file foo/TARGETS
{ "foo":
{ "type": ["@", "rules-rust", "rust", "library"]
, "name": ["foo"]
, "crate_root": ["foo.rs"]
, "native": ["true"]
, "c_hdrs": ["foo.h"]
, "stage": ["foo"]
}
}
```
With respect to the example of the [getting started
tutorial](../../getting-started/README.md), the above target
definition contains two new fields: `"native"` and `"c_hdrs"`. When
the field `"native"` evaluates to `true`, a native library is
generated instead of a Rust one. In the field `"c_hdrs"`, there are
listed the headers required to use the library. Since it makes sense
to set the field `"c_hdrs"` only for a native library, when `"c_hdrs"`
evaluates to a true value, a native library is implied. Therefore, for
this case, we can drop the `"native"` field
```jsonc
// file: foo/TARGETS
{ "foo":
{ "type": ["@", "rules-rust", "rust", "library"]
, "name": ["foo"]
, "crate_root": ["foo.rs"]
, "c_hdrs": ["foo.h"]
, "stage": ["foo"]
}
}
```
## The main
From the C consumer, the `foo` library can be seen as a normal C library
```C
/* file: main.c */
#include <stdio.h>
#include <stdlib.h>
#include "foo/foo.h"
int main(int argc, char **argv) {
if (argc < 2) {
fprintf(stderr, "Please provide one number as argument\n");
exit(1);
}
int x = atoi(argv[1]);
printf("absolute value of %d is %d\n", x, foo(x));
return 0;
}
```
and the target definition is as simple as follows:
```jsonc
// file: TARGETS
{ "main":
{ "type": ["@", "rules-cc", "CC", "binary"]
, "pure C": ["true"]
, "name": ["main"]
, "srcs": ["main.c"]
, "private-deps": [["foo", "foo"]]
}
}
```
It' worth to highlight that we simply stated that `"main"` depends on
the target `["foo", "foo"]`, and, at this level, we don't care how it
is defined.
## How to compile
The `"main"` target can be built issuing the following command
```sh
$ just-mr build main
```
Please refer to the "Let's build" section of the [getting-started
tutorial](../../getting-started/README.md) for more details on how to
find/select the `rustc` compiler.
## Additional exercises
To get more familiarity with the rules, you may want to:
- add a `["@", "rules-rust", "rust", "test"]`, which tests the `foo`
library.
- add a `["@", "rules-cc", "shell/script", "test"]` to test that the
main actually works.
|