Skip to content

Commit

Permalink
Merge pull request #45 from jerel/borrow-api
Browse files Browse the repository at this point in the history
Release 0.11
  • Loading branch information
jerel committed Sep 19, 2023
2 parents 52c7daa + 6deb8fd commit 616ca54
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 37 deletions.
1 change: 1 addition & 0 deletions dart_example/test/main_test.dart
Expand Up @@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:io';
import 'dart:typed_data';

import 'package:dart_example/common.dart' show Arg, VecWrapper;
import 'package:logging/logging.dart';
import 'package:test/test.dart';
import 'package:dart_example/accounts.dart';
Expand Down
4 changes: 4 additions & 0 deletions example/src/application/simple.rs
Expand Up @@ -10,6 +10,10 @@ pub fn contacts() -> impl Stream<Item = Result<data::Contact, data::Error>> {
futures::stream::iter(vec![Ok(data::Contact::default())])
}

///
/// This is a docblock that was written in Rust and
/// will be added to the generated Dart code.
///
#[async_dart(namespace = "accounts")]
pub async fn contact(user_id: String) -> Result<data::Contact, data::Error> {
println!("async {:?}", thread::current().id());
Expand Down
2 changes: 2 additions & 0 deletions membrane/src/generators/functions.rs
Expand Up @@ -254,6 +254,7 @@ trait Callable {
impl Callable for Ffi {
fn begin(&mut self) -> &mut Self {
self.output += &self.fun.begin();
self.output += self.fun.docblock;
self
}

Expand Down Expand Up @@ -434,6 +435,7 @@ impl Callable for Ffi {
impl Callable for Web {
fn begin(&mut self) -> &mut Self {
self.output += &self.fun.begin();
self.output += self.fun.docblock;
self
}

Expand Down
14 changes: 14 additions & 0 deletions membrane/src/generators/imports.rs
@@ -1,4 +1,5 @@
use serde_reflection::{ContainerFormat, Format, Named, VariantFormat};
use std::fs::{read_to_string, write};
use std::process::exit;
use tracing::error;

Expand Down Expand Up @@ -95,3 +96,16 @@ fn extract_name(format: &Format) -> Option<Vec<String>> {
fn filter_named(item: &Named<Format>) -> Option<Vec<String>> {
extract_name(&item.value)
}

pub(crate) fn inject_imports(
path: std::path::PathBuf,
filter: impl FnMut(&str) -> Option<Vec<String>>,
) -> Result<(), std::io::Error> {
let dart_file = read_to_string(&path)?
.lines()
.filter_map(filter)
.flatten()
.collect::<Vec<String>>();

write(path, dart_file.join("\n"))
}
183 changes: 148 additions & 35 deletions membrane/src/lib.rs
Expand Up @@ -89,29 +89,80 @@ mod generators;
use generators::{
exceptions,
functions::{Builder, Writable},
loaders,
imports, loaders,
};
use membrane_types::heck::{ToSnakeCase, ToUpperCamelCase};
use serde_reflection::{
ContainerFormat, Error, Registry, Samples, Tracer, TracerConfig, VariantFormat,
};
use std::{
collections::{BTreeMap, BTreeSet, HashMap},
fs::{read_to_string, remove_file},
fs::remove_file,
io::Write,
os::raw::c_char,
path::{Path, PathBuf},
process::exit,
};
use tracing::{debug, info, warn};

#[derive(Debug, Default)]
#[derive(Debug)]
pub struct DartConfig {
pub logger: DartLoggerConfig,
versions: HashMap<&'static str, &'static str>,
logger: DartLoggerConfig,
v1_import_style: Vec<&'static str>,
}

impl Default for DartConfig {
fn default() -> Self {
Self {
versions: HashMap::from([
("sdk", ">=2.17.0 <3.0.0"),
("ffi", "^2.0.0"),
("ffigen", "^7.2.7"),
("logger", "^1.1.0"),
]),
logger: DartLoggerConfig::default(),
v1_import_style: vec![],
}
}
}

impl DartConfig {
/// Override the default version strings that are set in the generated pub package.
///
/// Valid options: sdk, ffi, ffigen, logger.
pub fn set_version(&mut self, name: &'static str, version: &'static str) {
self
.versions
.insert(name, version)
.expect("An unknown version cannot be set. Valid options: sdk, ffi, ffigen, logger.");
}

/// This config allows the logger code that is injected into generated code to be customized. Using this
/// you can change the logging dependency, adjust the version, and change the names of logger methods.
pub fn logger(&mut self, dart_config: DartLoggerConfig) {
self.logger = dart_config;
}

/// This config exists temporarily as a tool to incrementally migrate large codebases away from the old automatic
/// re-export behavior one namespace at a time. It will be removed in a future version. Add namespaces to
/// this config to retain the old Dart import/export behavior for borrowed types.
///
/// In the old behavior namespace `a` borrowing a type with `borrow = "b::Foo"` would add `export './b/b.dart show Foo;` to
/// the implementation file. In some situations this could result in conflicting type names in app files trying to use a
/// Membrane-generated API.
///
/// In the new behavior a namespace only exports its own types publicly and the developer must import borrowed
/// types (if needed) in app code. This means that types which use types from other namespaces will work but the app
/// scope won't be polluted with needlessly exported type names.
pub fn v1_import_style(&mut self, namespaces: Vec<&'static str>) {
self.v1_import_style = namespaces;
}
}

#[derive(Debug)]
pub struct DartLoggerConfig {
pub dependency_name: &'static str,
pub import_path: &'static str,
pub instance: &'static str,
pub info_log_fn: &'static str,
Expand All @@ -121,6 +172,7 @@ pub struct DartLoggerConfig {
impl Default for DartLoggerConfig {
fn default() -> Self {
Self {
dependency_name: "logging",
import_path: "package:logging/logging.dart",
instance: "Logger('membrane')",
info_log_fn: "info",
Expand Down Expand Up @@ -154,6 +206,7 @@ pub struct Function {
pub dart_transforms: &'static str,
pub dart_inner_args: &'static str,
pub location: SourceCodeLocation,
pub docblock: &'static str,
}

#[doc(hidden)]
Expand Down Expand Up @@ -733,16 +786,19 @@ uint8_t membrane_free_membrane_string(char *ptr);
format!("name: {}", self.package_name)
} else if ln.contains("sdk:") {
// ffigen >= 5 requires dart >= 2.17, so replace dart version from serde-reflection
" sdk: '>=2.17.0 <3.0.0'".to_owned()
format!(" sdk: '{}'", self.dart_config.versions["sdk"])
} else {
ln.to_owned()
}
})
.chain(vec![
" ffi: ^2.0.0".to_owned(),
" logging: ^1.0.2".to_owned(),
format!(" ffi: {}", self.dart_config.versions["ffi"]),
format!(
" {}: {}",
self.dart_config.logger.dependency_name, self.dart_config.versions["logger"]
),
"dev_dependencies:".to_owned(),
" ffigen: ^6.1.2\n".to_owned(),
format!(" ffigen: {}\n", self.dart_config.versions["ffigen"]),
])
.collect::<Vec<String>>()
.join("\n");
Expand Down Expand Up @@ -904,14 +960,27 @@ export './membrane_loader_ffi.dart' if (dart.library.html) './membrane_loader_we
.join("lib")
.join(namespace.to_string() + ".dart");

let head = format!(
r#"// AUTO GENERATED FILE, DO NOT EDIT
let head = if utils::new_style_export(&namespace, &self.dart_config) {
format!(
r#"// AUTO GENERATED FILE, DO NOT EDIT
//
// Generated by `membrane`
export './src/{ns}_ffi.dart' if (dart.library.html) './src/{ns}_web.dart';
export './src/{ns}/{ns}.dart' hide TraitHelpers;
"#,
ns = &namespace,
);
ns = &namespace,
)
} else {
format!(
r#"// AUTO GENERATED FILE, DO NOT EDIT
//
// Generated by `membrane`
export './src/{ns}_ffi.dart' if (dart.library.html) './src/{ns}_web.dart';
"#,
ns = &namespace,
)
};

let mut buffer = std::fs::File::create(path).expect("class could not be written at path");
buffer.write_all(head.as_bytes()).unwrap();
Expand Down Expand Up @@ -959,9 +1028,7 @@ import './membrane_loader.dart' as loader;
import './bincode/bincode.dart';
import './ffi_bindings.dart' show MembraneMsgKind, MembraneResponse, MembraneResponseKind;
import './{ns}/{ns}.dart';
export './{ns}/{ns}.dart' hide TraitHelpers;
{export}
final _bindings = loader.bindings;
final _loggingDisabled = bool.fromEnvironment('MEMBRANE_DISABLE_LOGS');
Expand Down Expand Up @@ -992,6 +1059,14 @@ class {class_name}Api {{
.instance
.replace("')", &format!(".{}')", &namespace))
.replace("\")", &format!(".{}\")", &namespace)),
export = if utils::new_style_export(namespace, &self.dart_config) {
"".to_string()
} else {
format!(
"\nexport './{ns}/{ns}.dart' hide TraitHelpers;\n",
ns = &namespace
)
}
);

let mut buffer = std::fs::File::create(path).expect("class could not be written at path");
Expand All @@ -1017,10 +1092,14 @@ class {class_name}Api {{

// perhaps this namespace has only enums in it and no functions
if self.namespaced_fn_registry.get(namespace).is_none() {
let head = format!(
"export './{ns}/{ns}.dart' hide TraitHelpers;",
ns = &namespace
);
let head = if utils::new_style_export(namespace, &self.dart_config) {
"".to_string()
} else {
format!(
"export './{ns}/{ns}.dart' hide TraitHelpers;",
ns = &namespace
)
};
let mut buffer = std::fs::File::create(path).expect("class could not be written at path");
buffer.write_all(head.as_bytes()).unwrap();

Expand All @@ -1040,8 +1119,7 @@ class {class_name}Api {{
import 'package:meta/meta.dart';
import './membrane_exceptions.dart';
import './{ns}/{ns}.dart';
export './{ns}/{ns}.dart' hide TraitHelpers;
{export}
@immutable
class {class_name}ApiError implements Exception {{
final e;
Expand All @@ -1060,7 +1138,15 @@ class {class_name}Api {{
const {class_name}Api();
"#,
ns = &namespace,
class_name = &namespace.to_upper_camel_case()
class_name = &namespace.to_upper_camel_case(),
export = if utils::new_style_export(namespace, &self.dart_config) {
"".to_string()
} else {
format!(
"export './{ns}/{ns}.dart' hide TraitHelpers;",
ns = namespace
)
}
);

let mut buffer = std::fs::File::create(path).expect("class could not be written at path");
Expand Down Expand Up @@ -1144,14 +1230,11 @@ class {class_name}Api {{
// and this is the borrowed path
non_owned_types.extend(borrowed_types.iter().map(|ty| format!("{}::{}", from_namespace, ty)));

let file_name = format!("{ns}.dart", ns = namespace);
let namespace_path = self.destination.join("lib/src").join(namespace);
let barrel_file_path = namespace_path.join(file_name);
let src_path = self.destination.join("lib/src");
let namespace_path = src_path.join(namespace);

let barrel_file = read_to_string(&barrel_file_path)
.unwrap()
.lines()
.filter_map(|line| {
imports::inject_imports(namespace_path.join(format!("{ns}.dart", ns = namespace)),
|line| {
// because CamelCasing the snake_cased `part 'central_usa.dart'` won't match the
// acronym borrow `CentralUSA` we instead convert the borrows to snake_case to do the match
if borrowed_types.iter().map(|t| t.to_snake_case()).collect::<Vec<String>>().contains(
Expand All @@ -1169,7 +1252,7 @@ class {class_name}Api {{
types = borrowed_types.join(",")
),
])
} else if line.starts_with("export '../serde") {
} else if line.starts_with("export '../serde") && !utils::new_style_export(namespace, &self.dart_config) {
Some(vec![
line.to_string(),
format!(
Expand All @@ -1181,16 +1264,46 @@ class {class_name}Api {{
} else {
Some(vec![line.to_string()])
}
})
.flatten()
.collect::<Vec<String>>();
}).unwrap();

if utils::new_style_export(namespace, &self.dart_config) {
imports::inject_imports(src_path.join(format!("{ns}_ffi.dart", ns = namespace)),
|line| {
if line.starts_with(&format!("import './{ns}/{ns}.dart'", ns = namespace)) {
Some(vec![
line.to_string(),
format!(
"import './{ns}/{ns}.dart' show {types};",
ns = from_namespace,
types = borrowed_types.join(",")
),
])
} else {
Some(vec![line.to_string()])
}
}).unwrap();

imports::inject_imports(src_path.join(format!("{ns}_web.dart", ns = namespace)),
|line| {
if line.starts_with(&format!("import './{ns}/{ns}.dart'", ns = namespace)) {
Some(vec![
line.to_string(),
format!(
"import './{ns}/{ns}.dart' show {types};",
ns = from_namespace,
types = borrowed_types.join(",")
),
])
} else {
Some(vec![line.to_string()])
}
}).unwrap();
}

borrowed_types.iter().for_each(|borrowed_type| {
let filename = format!("{}.dart", borrowed_type.to_snake_case());
let _ = remove_file(namespace_path.join(filename));
});

std::fs::write(barrel_file_path, barrel_file.join("\n")).unwrap();
});
});

Expand Down
4 changes: 4 additions & 0 deletions membrane/src/utils.rs
Expand Up @@ -58,6 +58,10 @@ pub(crate) fn display_code_location(location: Option<&Vec<SourceCodeLocation>>)
}
}

pub(crate) fn new_style_export<S: AsRef<str>>(namespace: S, config: &crate::DartConfig) -> bool {
!config.v1_import_style.contains(&namespace.as_ref())
}

#[cfg(test)]
mod tests {
use super::display_code_location;
Expand Down

0 comments on commit 616ca54

Please sign in to comment.