Skip to content

Commit 656aee0

Browse files
committed
Use LLVM's YAML::IO library to read binding configuration
Also improve the documentation of the binding configuration file with testable examples.
1 parent 383da84 commit 656aee0

File tree

16 files changed

+118
-170
lines changed

16 files changed

+118
-170
lines changed

Dockerfile

-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ RUN set -x \
1414
g++ openjdk-8-jdk-headless sbt cmake make curl git \
1515
zlib1g-dev \
1616
libgc-dev libunwind8-dev libre2-dev \
17-
nlohmann-json-dev \
1817
&& rm -rf /var/lib/apt/lists/*
1918

2019
ARG LOCALE=en_US.UTF-8

bindgen/ir/Enum.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "Enum.h"
2+
#include <sstream>
23

34
Enumerator::Enumerator(std::string name, int64_t value)
45
: name(std::move(name)), value(value) {}

bindgen/ir/IR.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "IR.h"
22
#include "../Utils.h"
3+
#include <sstream>
34

45
IR::IR(std::string libName, std::string linkName, std::string objectName,
56
std::string packageName, const LocationManager &locationManager)

bindgen/ir/LocationManager.cpp

+65-108
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,55 @@
11
#include "LocationManager.h"
22
#include "../Utils.h"
3-
#include "Enum.h"
4-
#include "Struct.h"
5-
#include <fstream>
6-
#include <stdexcept>
3+
#include <llvm/Support/MemoryBuffer.h>
4+
#include <llvm/Support/YAMLTraits.h>
75

8-
LocationManager::LocationManager(std::string mainHeaderPath)
9-
: mainHeaderPath(std::move(mainHeaderPath)) {}
10-
11-
void LocationManager::loadConfig(const std::string &path) {
12-
std::string realPath = getRealPath(path.c_str());
13-
14-
std::stringstream s;
15-
std::ifstream input(realPath);
16-
for (std::string line; getline(input, line);) {
17-
s << line;
6+
template <> struct llvm::yaml::MappingTraits<HeaderEntry> {
7+
static void mapping(llvm::yaml::IO &io, HeaderEntry &headerEntry) {
8+
io.mapRequired("path", headerEntry.path);
9+
io.mapRequired("object", headerEntry.object);
10+
io.mapOptional("names", headerEntry.names);
1811
}
19-
config = json::parse(s.str());
20-
validateConfig(config);
21-
}
12+
};
2213

23-
void LocationManager::validateConfig(const json &config) const {
24-
if (!config.is_object()) {
25-
throw std::invalid_argument(
26-
"Invalid configuration. Configuration should be an object.");
27-
}
28-
for (auto it = config.begin(); it != config.end(); ++it) {
29-
std::string headerName = it.key();
30-
if (headerName.empty()) {
31-
throw std::invalid_argument("Invalid configuration. Header name "
32-
"should not be an empty string.");
14+
template <> struct llvm::yaml::MappingTraits<HeaderEntryName> {
15+
static void mapping(llvm::yaml::IO &io, HeaderEntryName &headerEntryName) {
16+
// Dynamically look up the available keys when only one key is given.
17+
//
18+
// ```yaml
19+
// - struct point: Point
20+
// ```
21+
if (io.keys().size() == 1) {
22+
for (auto key : io.keys()) {
23+
headerEntryName.original = key;
24+
io.mapRequired(headerEntryName.original.c_str(),
25+
headerEntryName.target);
26+
}
27+
} else {
28+
io.mapRequired("original", headerEntryName.original);
29+
io.mapRequired("target", headerEntryName.target);
3330
}
34-
json headerEntry = it.value();
35-
validateHeaderEntry(headerEntry);
3631
}
37-
}
32+
};
3833

39-
void LocationManager::validateHeaderEntry(const json &headerEntry) const {
40-
if (headerEntry.is_string()) {
41-
std::string object = headerEntry.get<std::string>();
42-
if (object.empty()) {
43-
throw std::invalid_argument("Invalid configuration. Each header "
44-
"entry should contain non-empty "
45-
"value.");
46-
}
47-
} else if (headerEntry.is_object()) {
48-
std::unordered_set<std::string> headerKeys = {"object", "names"};
49-
validateKeys(headerEntry, headerKeys);
50-
if (headerEntry.find("object") == headerEntry.end()) {
51-
throw std::invalid_argument("Invalid configuration. Header entry "
52-
"that is represented as an object "
53-
"should contain 'object' property.");
54-
}
55-
json object = headerEntry["object"];
56-
if (!object.is_string() || object.get<std::string>().empty()) {
57-
throw std::invalid_argument("Invalid configuration. 'object' "
58-
"property should be a not empty "
59-
"string.");
60-
}
61-
if (headerEntry.find("name") != headerEntry.end()) {
62-
validateNames(headerEntry["names"]);
63-
}
64-
} else {
65-
throw std::invalid_argument("Invalid configuration. Header entry "
66-
"should be a string or an object.");
67-
}
68-
}
34+
LLVM_YAML_IS_SEQUENCE_VECTOR(HeaderEntry)
35+
LLVM_YAML_IS_SEQUENCE_VECTOR(HeaderEntryName)
6936

70-
void LocationManager::validateKeys(
71-
const json &object, const std::unordered_set<std::string> &keys) const {
72-
for (auto it = object.begin(); it != object.end(); ++it) {
73-
if (keys.find(it.key()) == keys.end()) {
74-
throw std::invalid_argument(
75-
"Invalid configuration. Unknown key: '" + it.key() + "'.");
76-
}
77-
}
78-
}
37+
LocationManager::LocationManager(std::string mainHeaderPath)
38+
: headerEntries(), mainHeaderPath(std::move(mainHeaderPath)) {}
7939

80-
void LocationManager::validateNames(const json &names) const {
81-
if (!names.is_object()) {
82-
throw std::invalid_argument("Invalid configuration. Library property "
83-
"'names' should be an object.");
84-
}
85-
for (auto it = names.begin(); it != names.end(); ++it) {
86-
if (!it.value().is_string()) {
87-
throw std::invalid_argument(
88-
"Invalid configuration. property 'names'"
89-
" should contain only string values.");
90-
}
91-
}
40+
bool LocationManager::loadConfig(const std::string &path) {
41+
llvm::SmallString<4096> realPath;
42+
if (llvm::sys::fs::real_path(path, realPath))
43+
return false;
44+
45+
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> mb =
46+
llvm::MemoryBuffer::getFile(realPath);
47+
if (!mb)
48+
return false;
49+
50+
llvm::yaml::Input input(mb->get()->getBuffer());
51+
input >> headerEntries;
52+
return true;
9253
}
9354

9455
bool LocationManager::inMainFile(const Location &location) const {
@@ -99,40 +60,36 @@ bool LocationManager::isImported(const Location &location) const {
9960
if (location.getPath().empty()) {
10061
return false;
10162
}
102-
json headerEntry = getHeaderEntry(location);
103-
return !headerEntry.empty();
63+
return getHeaderEntryIndex(location) != headerEntries.size();
10464
}
10565

106-
json LocationManager::getHeaderEntry(const Location &location) const {
107-
for (auto it = config.begin(); it != config.end(); ++it) {
108-
std::string pathToHeader = it.key();
109-
if (startsWith(pathToHeader, "/")) {
110-
/* full path */
111-
if (location.getPath() == pathToHeader) {
112-
return it.value();
113-
}
114-
} else if (endsWith(location.getPath(), "/" + pathToHeader)) {
115-
return it.value();
116-
}
117-
}
118-
return json::object();
66+
std::vector<HeaderEntry>::size_type
67+
LocationManager::getHeaderEntryIndex(const Location &location) const {
68+
auto isHeader = [&](const HeaderEntry &headerEntry) {
69+
if (startsWith(headerEntry.path, "/") &&
70+
location.getPath() == headerEntry.path)
71+
return true;
72+
return endsWith(location.getPath(), "/" + headerEntry.path);
73+
};
74+
auto it =
75+
std::find_if(headerEntries.begin(), headerEntries.end(), isHeader);
76+
return it - headerEntries.begin();
11977
}
12078

12179
std::string LocationManager::getImportedType(const Location &location,
12280
const std::string &name) const {
123-
json headerEntry = getHeaderEntry(location);
124-
if (headerEntry.is_string()) {
125-
return headerEntry.get<std::string>() + "." +
126-
handleReservedWords(replaceChar(name, " ", "_"));
127-
}
128-
std::string scalaObject = headerEntry["object"];
81+
auto index = getHeaderEntryIndex(location);
82+
if (index == headerEntries.size())
83+
return name;
84+
85+
auto headerEntry = headerEntries[index];
12986

130-
if (headerEntry.find("names") != headerEntry.end()) {
131-
/* name mapping */
132-
json names = headerEntry["names"];
133-
if (names.find(name) != names.end()) {
134-
return scalaObject + "." + names[name].get<std::string>();
87+
for (auto const &headerEntryName : headerEntry.names) {
88+
if (headerEntryName.original == name) {
89+
return headerEntry.object + "." + headerEntryName.target;
13590
}
13691
}
137-
return scalaObject + "." + handleReservedWords(replaceChar(name, " ", "_"));
92+
93+
return headerEntry.object + "." +
94+
handleReservedWords(replaceChar(name, " ", "_"));
13895
}

bindgen/ir/LocationManager.h

+16-17
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,26 @@
22
#define SCALA_NATIVE_BINDGEN_LOCATIONMANAGER_H
33

44
#include "Location.h"
5-
#include <nlohmann/json.hpp>
5+
#include <map>
66
#include <string>
7-
#include <unordered_map>
8-
#include <unordered_set>
7+
#include <vector>
98

10-
using json = nlohmann::json;
9+
struct HeaderEntryName {
10+
std::string original;
11+
std::string target;
12+
};
13+
14+
struct HeaderEntry {
15+
std::string path;
16+
std::string object;
17+
std::vector<HeaderEntryName> names;
18+
};
1119

1220
class LocationManager {
1321
public:
1422
explicit LocationManager(std::string mainHeaderPath);
1523

16-
void loadConfig(const std::string &path);
24+
bool loadConfig(const std::string &path);
1725

1826
bool inMainFile(const Location &location) const;
1927

@@ -26,19 +34,10 @@ class LocationManager {
2634
const std::string &name) const;
2735

2836
private:
37+
std::vector<HeaderEntry>::size_type
38+
getHeaderEntryIndex(const Location &location) const;
39+
std::vector<HeaderEntry> headerEntries;
2940
std::string mainHeaderPath;
30-
json config;
31-
32-
json getHeaderEntry(const Location &location) const;
33-
34-
void validateConfig(const json &config) const;
35-
36-
void validateHeaderEntry(const json &headerEntry) const;
37-
38-
void validateNames(const json &names) const;
39-
40-
void validateKeys(const json &object,
41-
const std::unordered_set<std::string> &keys) const;
4241
};
4342

4443
#endif // SCALA_NATIVE_BINDGEN_LOCATIONMANAGER_H

build.sbt

+2-2
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ lazy val docs = nativeProject("docs")
156156
.link("vector")
157157
.packageName("org.example"), {
158158
val pathToHeader = docs3rdPartyBindingsDirectory.value / "geometry.h"
159-
val pathToConfig = docs3rdPartyBindingsDirectory.value / "config.json"
159+
val pathToConfig = docs3rdPartyBindingsDirectory.value / "config.yml"
160160
//#sbt-binding-config
161161
NativeBinding(pathToHeader)
162162
.bindingConfig(pathToConfig)
@@ -173,7 +173,7 @@ lazy val docs = nativeProject("docs")
173173
.name("WordCount")
174174
.link("wordcount")
175175
.packageName("org.example.wordcount")
176-
.bindingConfig(docsScalaNativeBindingsDirectory.value / "config.json")
176+
.bindingConfig(docsScalaNativeBindingsDirectory.value / "config.yml")
177177
}
178178
),
179179
nativeBindgenPath := {

docs/src/paradox/configuration.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ When the exclude prefix is set to `__`, then the resulting bindings will be:
2828

2929
## Binding Configuration File
3030

31-
The binding configuration is a JSON file which allows to map the path of
31+
The binding configuration is a YAML file which allows to map the path of
3232
a header file to the associated object as well as the names of the C
3333
types and symbols to their respective Scala types and definitions. The
3434
configuration file can be used when integrating with third party
@@ -50,7 +50,7 @@ If you need to generate bindings that uses types from bindings that have not bee
5050

5151
To use this binding, create a configuration file with the folllowing content, where `path` is the name of the header file (usually the part of the path inside the `/usr/include` or `/usr/local/include` directory), `object` is the fully qualified name of the Scala object (i.e. package name as well as the Scala object name) and finally `names` for each of the types:
5252

53-
@@snip [vector.h] (../test/resources/3rd-party-bindings/config.json)
53+
@@snip [vector.h] (../test/resources/3rd-party-bindings/config.yml)
5454

5555
Now in the library you are creating a binding for, any usage of `struct point`:
5656

@@ -71,7 +71,7 @@ from `<stdio.h>`:
7171

7272
We can then write a binding configuration that maps the header name to the object defined in Scala Native.
7373

74-
@@snip [vector.h] (../test/resources/scala-native-bindings/config.json)
74+
@@snip [vector.h] (../test/resources/scala-native-bindings/config.yml)
7575

7676
@@@ note
7777

docs/src/paradox/contrib/cmake.md

+2-6
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,13 @@ Building `scala-native-bindgen` requires the following tools and libraries:
55
- [CMake] version 3.9 or higher
66
- [GNU Make]
77
- [LLVM] and [Clang] version 5.0 or 6.0.
8-
- [nlohmann/json] version 3.1.2 or higher
98

109
```sh
1110
# On apt-based systems
12-
apt install cmake make clang-6.0 libclang-6.0-dev llvm-6.0-dev \
13-
nlohmann-json-dev
11+
apt install cmake make clang-6.0 libclang-6.0-dev llvm-6.0-dev
1412

1513
# With `brew`
16-
brew tap nlohmann/json
17-
brew install cmake llvm@6 nlohmann_json
14+
brew install cmake llvm@6
1815
```
1916

2017
To run the tests you also need the required Scala Native libraries.
@@ -47,4 +44,3 @@ CXXFLAGS=-g cmake ..
4744
[LLVM]: https://llvm.org/
4845
[Clang]: https://clang.llvm.org/
4946
[Scala Native setup guide]: http://www.scala-native.org/en/latest/user/setup.html
50-
[nlohmann/json]: https://github.com/nlohmann/json

docs/src/test/resources/3rd-party-bindings/config.json

-9
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
- path: vector.h
3+
object: com.example.custom.binding.Vector
4+
names:
5+
# Short name mapping
6+
- struct point: Point
7+
# Full name mapping
8+
- original: struct lineSegment
9+
target: LineSegment

docs/src/test/resources/scala-native-bindings/config.json

-8
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
- path: stdio.h
3+
object: scala.scalanative.native.stdio
4+
- path: _stdio.h
5+
object: scala.scalanative.native.stdio

scripts/prepare-release.sh

+1-2
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,9 @@ fi
1919

2020
MACOS_EXEC="$ROOT/scala-native-bindgen-darwin"
2121
if [[ "$(uname -s)" = "Darwin" ]] && [[ ! -e "$MACOS_EXEC" ]]; then
22-
json_dir="$(ls /usr/local/Cellar/nlohmann_json/ | sort | tail -n 1)/include"
2322
rm -rf bindgen/target
2423
mkdir -p bindgen/target
25-
(cd bindgen/target && cmake -DSTATIC_LINKING=ON -DCMAKE_CXX_FLAGS="-isystem $json_dir" ..)
24+
(cd bindgen/target && cmake -DSTATIC_LINKING=ON ..)
2625
make -C bindgen/target
2726
cp bindgen/target/scala-native-bindgen "$MACOS_EXEC"
2827
fi

0 commit comments

Comments
 (0)