Skip to content

Commit d4ba0ab

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 80dd428 commit d4ba0ab

File tree

37 files changed

+539
-202
lines changed

37 files changed

+539
-202
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

+32-5
Original file line numberDiff line numberDiff line change
@@ -138,23 +138,50 @@ lazy val sbtPlugin = project("sbt-scala-native-bindgen", ScriptedPlugin)
138138
publishLocal := publishLocal.dependsOn(tools / publishLocal).value
139139
)
140140

141+
lazy val docsUsingBindingsDirectory = settingKey[File]("vector source")
142+
lazy val docs3rdPartyBindingsDirectory = settingKey[File]("geometry source")
143+
lazy val docsScalaNativeBindingsDirectory = settingKey[File]("wordcount source")
144+
141145
lazy val docs = nativeProject("docs")
142146
.enablePlugins(GhpagesPlugin, ParadoxSitePlugin, ParadoxMaterialThemePlugin)
143147
.enablePlugins(ScalaNativeBindgenPlugin)
144148
.settings(
145149
publish / skip := true,
146-
Test / nativeBindings += {
147-
NativeBinding((Test / resourceDirectory).value / "vector.h")
150+
docsUsingBindingsDirectory := (Test / resourceDirectory).value / "using-bindings",
151+
docs3rdPartyBindingsDirectory := (Test / resourceDirectory).value / "3rd-party-bindings",
152+
docsScalaNativeBindingsDirectory := (Test / resourceDirectory).value / "scala-native-bindings",
153+
Test / nativeBindings ++= Seq(
154+
NativeBinding(docsUsingBindingsDirectory.value / "vector.h")
148155
.name("vector")
149156
.link("vector")
150-
.packageName("org.example")
151-
},
157+
.packageName("org.example"),
158+
NativeBinding(docs3rdPartyBindingsDirectory.value / "geometry.h")
159+
.name("Geometry")
160+
.link("geometry")
161+
.packageName("org.example.geometry")
162+
.bindingConfig(docs3rdPartyBindingsDirectory.value / "config.yml"), {
163+
val pathToHeader = docsScalaNativeBindingsDirectory.value / "wordcount.h"
164+
val pathToConfig = docsScalaNativeBindingsDirectory.value / "config.yml"
165+
//#sbt-binding-config
166+
NativeBinding(pathToHeader)
167+
//#sbt-binding-config
168+
.name("WordCount")
169+
.link("wordcount")
170+
.packageName("org.example.wordcount")
171+
.excludePrefix("__")
172+
//#sbt-binding-config
173+
.bindingConfig(pathToConfig)
174+
//#sbt-binding-config
175+
}
176+
),
152177
nativeBindgenPath := {
153178
Some(
154179
(ThisBuild / baseDirectory).value / "bindgen/target/scala-native-bindgen")
155180
},
156181
Test / nativeBindgen / target := (Test / scalaSource).value / "org/example",
157-
compileTask("vector", Test / resourceDirectory),
182+
compileTask("vector", docsUsingBindingsDirectory),
183+
compileTask("geometry", docs3rdPartyBindingsDirectory),
184+
compileTask("wordcount", docsScalaNativeBindingsDirectory),
158185
libraryDependencies += "org.scalatest" %%% "scalatest" % "3.2.0-SNAP10" % Test,
159186
Paradox / paradoxProperties ++= Map(
160187
"github.base_url" -> scmInfo.value.get.browseUrl.toString

docs/src/paradox/cli.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ The generated bindings can be configured using the different options and it is a
4545

4646
| Option | Description
4747
|----------------------|---------------------------------------------------------------------------------|
48-
| `--link` | Library to link with, e.g. `--link` uv for libuv.
48+
| `--link` | Library to link with, e.g. `--link uv` for libuv.
4949
| `--no-link` | Library does not require linking.
50-
| `--name` | Scala object name that contains bindings. Default value set to library name.
50+
| `--name` | Scala object name that contains bindings. Defaults to the library name.
5151
| `--package` | Package name of generated Scala file.
52-
| `--exclude-prefix` | Functions and unused typedefs will be removed if their names have given prefix.
53-
| `--binding-config` | Path to a config file that contains the information about bindings that should be reused. See @ref:[Integrating Bindings](integrating-bindings.md) for more information.
52+
| `--exclude-prefix` | Functions and unused typedefs will be removed if their names have the given prefix.
53+
| `--binding-config` | Path to a config file that contains the information about bindings that should be reused. See @ref:[Configuration](configuration.md) for more information.
5454
| `--extra-arg` | Additional argument to append to the compiler command line.
5555
| `--extra-arg-before` | Additional argument to prepend to the compiler command line.

0 commit comments

Comments
 (0)