Skip to content

Commit

Permalink
Add java support (using JNA)
Browse files Browse the repository at this point in the history
This builds on #853 and adds a new language backend capable of
generating java bindings.

The generated bindings use [JNA](https://github.com/java-native-access/jna)
(I used the last version 5.13.0 while developping but this will probably
work with earlier ones as well)

Most of the new code lives in `bindgen/language_backend/java_jna.rs` and
is pretty straightfowards generation as most on the complicated things were done
in the previous refactoring.

The test suite has been updated to generate and compile the bindings for
java when running. On nice thing gained with this is the possibilibty to
put a configuration file specific to a language alongside the stardard
one and the test will take this one instead of the default one if it
exists (quite a few test use the `header` config to add some more code
inside the generated bindings, C or preprocessor code doesn't work well
in a java source file :P)

All tests are green, but that doesn't mean all the generated bindings
are correct, some cases may fail at runtime as I didn't test all
cases in a real program. I did however generate and use the bindings on a
non trivial lib I'm developping and they worked as expected.
  • Loading branch information
fredszaq committed Apr 15, 2024
1 parent e211464 commit 8f7cbfa
Show file tree
Hide file tree
Showing 182 changed files with 14,694 additions and 37 deletions.
9 changes: 8 additions & 1 deletion src/bindgen/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::bindgen::ir::{
Constant, Function, ItemContainer, ItemMap, Path as BindgenPath, Static, Struct, Type, Typedef,
};
use crate::bindgen::language_backend::{
CLikeLanguageBackend, CythonLanguageBackend, LanguageBackend,
CLikeLanguageBackend, CythonLanguageBackend, JavaJnaLanguageBackend, LanguageBackend,
};
use crate::bindgen::writer::SourceWriter;

Expand All @@ -37,6 +37,7 @@ pub struct Bindings {
/// and shouldn't do anything when written anywhere.
noop: bool,
pub package_version: String,
binding_crate_lib_name: String,
}

impl Bindings {
Expand All @@ -52,6 +53,7 @@ impl Bindings {
source_files: Vec<path::PathBuf>,
noop: bool,
package_version: String,
binding_crate_lib_name: String,
) -> Bindings {
Bindings {
config,
Expand All @@ -65,6 +67,7 @@ impl Bindings {
source_files,
noop,
package_version,
binding_crate_lib_name,
}
}

Expand Down Expand Up @@ -210,6 +213,10 @@ impl Bindings {
Language::Cython => {
self.write_with_backend(file, &mut CythonLanguageBackend::new(&self.config))
}
Language::JavaJna => self.write_with_backend(
file,
&mut JavaJnaLanguageBackend::new(&self.config, self.binding_crate_lib_name.clone()),
),
}
}

Expand Down
9 changes: 9 additions & 0 deletions src/bindgen/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ impl Builder {
Default::default(),
true,
String::new(),
Default::default(),
));
}

Expand All @@ -375,6 +376,8 @@ impl Builder {
result.extend_with(&parser::parse_src(x, &self.config)?);
}

let binding_crate_lib_name;

if let Some((lib_dir, binding_lib_name)) = self.lib.clone() {
let lockfile = self.lockfile.as_deref();

Expand All @@ -388,9 +391,14 @@ impl Builder {
/* existing_metadata = */ None,
)?;

binding_crate_lib_name = cargo.binding_crate_lib_name().to_string();

result.extend_with(&parser::parse_lib(cargo, &self.config)?);
} else if let Some(cargo) = self.lib_cargo.clone() {
binding_crate_lib_name = cargo.binding_crate_lib_name().to_string();
result.extend_with(&parser::parse_lib(cargo, &self.config)?);
} else {
binding_crate_lib_name = String::new()
}

result.source_files.extend_from_slice(self.srcs.as_slice());
Expand All @@ -407,6 +415,7 @@ impl Builder {
result.functions,
result.source_files,
result.package_version,
binding_crate_lib_name,
)
.generate()
}
Expand Down
20 changes: 15 additions & 5 deletions src/bindgen/cargo/cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ fn parse_dep_string(dep_string: &str) -> (&str, Option<&str>) {
pub(crate) struct Cargo {
manifest_path: PathBuf,
binding_crate_name: String,
binding_crate_lib_name: String,
lock: Option<Lock>,
metadata: Metadata,
clean: bool,
Expand Down Expand Up @@ -63,19 +64,24 @@ impl Cargo {
None
};

let manifest = cargo_toml::manifest(&toml_path)
.map_err(|x| Error::CargoToml(toml_path.to_str().unwrap().to_owned(), x))?;

// Use the specified binding crate name or infer it from the manifest
let binding_crate_name = match binding_crate_name {
Some(s) => s.to_owned(),
None => {
let manifest = cargo_toml::manifest(&toml_path)
.map_err(|x| Error::CargoToml(toml_path.to_str().unwrap().to_owned(), x))?;
manifest.package.name
}
None => manifest.package.name,
};

let binding_crate_lib_name = manifest
.lib
.and_then(|lib| lib.name)
.unwrap_or_else(|| binding_crate_name.clone());

Ok(Cargo {
manifest_path: toml_path,
binding_crate_name,
binding_crate_lib_name,
lock,
metadata,
clean,
Expand All @@ -86,6 +92,10 @@ impl Cargo {
&self.binding_crate_name
}

pub(crate) fn binding_crate_lib_name(&self) -> &str {
&self.binding_crate_lib_name
}

pub(crate) fn binding_crate_ref(&self) -> PackageRef {
match self.find_pkg_to_generate_bindings_ref(&self.binding_crate_name) {
Some(pkg_ref) => pkg_ref,
Expand Down
6 changes: 6 additions & 0 deletions src/bindgen/cargo/cargo_toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,19 @@ impl error::Error for Error {
#[derive(Clone, Deserialize, Debug)]
pub struct Manifest {
pub package: Package,
pub lib: Option<Lib>,
}

#[derive(Clone, Deserialize, Debug)]
pub struct Package {
pub name: String,
}

#[derive(Clone, Deserialize, Debug)]
pub struct Lib {
pub name: Option<String>,
}

/// Parse the Cargo.toml for a given path
pub fn manifest(manifest_path: &Path) -> Result<Manifest, Error> {
let mut s = String::new();
Expand Down
51 changes: 51 additions & 0 deletions src/bindgen/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use std::collections::{BTreeMap, HashMap};
use std::default::Default;
use std::fmt::{Display, Formatter};
use std::str::FromStr;
use std::{fmt, fs, path::Path as StdPath, path::PathBuf as StdPathBuf};

Expand All @@ -23,6 +24,7 @@ pub enum Language {
Cxx,
C,
Cython,
JavaJna,
}

impl FromStr for Language {
Expand All @@ -42,18 +44,48 @@ impl FromStr for Language {
"C" => Ok(Language::C),
"cython" => Ok(Language::Cython),
"Cython" => Ok(Language::Cython),
"Java-JNA" => Ok(Language::JavaJna),
"Java-Jna" => Ok(Language::JavaJna),
"java-jna" => Ok(Language::JavaJna),
"JavaJNA" => Ok(Language::JavaJna),
"JavaJna" => Ok(Language::JavaJna),
"javajna" => Ok(Language::JavaJna),
_ => Err(format!("Unrecognized Language: '{}'.", s)),
}
}
}

impl Display for Language {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Language::Cxx => {
"cxx"
}
Language::C => {
"c"
}
Language::Cython => {
"cython"
}
Language::JavaJna => {
"java-jna"
}
}
)
}
}

deserialize_enum_str!(Language);

impl Language {
pub(crate) fn typedef(self) -> &'static str {
match self {
Language::Cxx | Language::C => "typedef",
Language::Cython => "ctypedef",
_ => unimplemented!(),
}
}
}
Expand Down Expand Up @@ -894,6 +926,22 @@ pub struct CythonConfig {
pub cimports: BTreeMap<String, Vec<String>>,
}

/// Settings specific to Java bindings.
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
#[serde(default)]
pub struct JavaConfig {
/// Package to use
pub package: Option<String>,

/// Name of the generated interface
pub interface_name: Option<String>,

/// Extra definition to include inside the generated interface
pub extra_defs: Option<String>,
}

/// A collection of settings to customize the generated bindings.
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "snake_case")]
Expand Down Expand Up @@ -1018,6 +1066,8 @@ pub struct Config {
pub only_target_dependencies: bool,
/// Configuration options specific to Cython.
pub cython: CythonConfig,
/// Configuration options specific to Java (JNA backend)
pub java_jna: JavaConfig,
#[doc(hidden)]
#[serde(skip)]
/// Internal field for tracking from which file the config was loaded.
Expand Down Expand Up @@ -1070,6 +1120,7 @@ impl Default for Config {
pointer: PtrConfig::default(),
only_target_dependencies: false,
cython: CythonConfig::default(),
java_jna: JavaConfig::default(),
config_path: None,
}
}
Expand Down
48 changes: 31 additions & 17 deletions src/bindgen/ir/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,29 +336,43 @@ pub trait ConditionWrite {
impl ConditionWrite for Option<Condition> {
fn write_before<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) {
if let Some(ref cfg) = *self {
if config.language == Language::Cython {
out.write("IF ");
cfg.write(config, out);
out.open_brace();
} else {
out.push_set_spaces(0);
out.write("#if ");
cfg.write(config, out);
out.pop_set_spaces();
out.new_line();
match config.language {
Language::Cython => {
out.write("IF ");
cfg.write(config, out);
out.open_brace();
}
Language::Cxx | Language::C => {
out.push_set_spaces(0);
out.write("#if ");
cfg.write(config, out);
out.pop_set_spaces();
out.new_line();
}
Language::JavaJna => {
write!(out, "/* begin condition not supported {self:?} */");
out.new_line();
}
}
}
}

fn write_after<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) {
if self.is_some() {
if config.language == Language::Cython {
out.close_brace(false);
} else {
out.new_line();
out.push_set_spaces(0);
out.write("#endif");
out.pop_set_spaces();
match config.language {
Language::Cython => {
out.close_brace(false);
}
Language::Cxx | Language::C => {
out.new_line();
out.push_set_spaces(0);
out.write("#endif");
out.pop_set_spaces();
}
Language::JavaJna => {
out.new_line();
write!(out, "/* end condition not supported {self:?} */");
}
}
}
}
Expand Down
18 changes: 17 additions & 1 deletion src/bindgen/ir/constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::bindgen::ir::{
AnnotationSet, Cfg, ConditionWrite, Documentation, GenericParams, Item, ItemContainer, Path,
Struct, ToCondition, Type,
};
use crate::bindgen::language_backend::LanguageBackend;
use crate::bindgen::language_backend::{java_writable_literal, wrap_java_value, LanguageBackend};
use crate::bindgen::library::Library;
use crate::bindgen::writer::SourceWriter;
use crate::bindgen::Bindings;
Expand Down Expand Up @@ -708,6 +708,22 @@ impl Constant {
write!(out, " {} # = ", name);
language_backend.write_literal(out, value);
}
Language::JavaJna => {
if java_writable_literal(&self.ty, value) {
out.write("public static final ");
language_backend.write_type(out, &self.ty);
write!(out, " {} = ", self.export_name);
language_backend.write_literal(out, &wrap_java_value(value, &self.ty));
out.write(";")
} else {
write!(
out,
"/* Unsupported literal for constant {} */",
self.export_name
)
}
out.new_line();
}
}

condition.write_after(config, out);
Expand Down
2 changes: 2 additions & 0 deletions src/bindgen/ir/enumeration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,7 @@ impl Enum {
write!(out, "{}enum {}", config.style.cython_def(), tag_name);
}
}
_ => unimplemented!(),
}
out.open_brace();

Expand Down Expand Up @@ -775,6 +776,7 @@ impl Enum {
Language::C if config.style.generate_typedef() => out.write("typedef "),
Language::C | Language::Cxx => {}
Language::Cython => out.write(config.style.cython_def()),
_ => unimplemented!(),
}

out.write(if inline_tag_field { "union" } else { "struct" });
Expand Down
Loading

0 comments on commit 8f7cbfa

Please sign in to comment.