Remove some duplication in the documentation of API that's shared between Rust and C++

This change makes the start of sharing the docs for the `TimerMode` enum
between Rust and C++. The reference to Timer::start in there works as
both doxygen and rustdoc find the right reference, but this needs
careful editing in the future and double-checking!

Another "caveat" is that the docs for the TimerMode enum say that the
enum is defined in the file "sixtyfps_generated_public.h", which is
correct as-is but not as pretty as "sixtyfps.h". I tried various ways
with \file and \includedoc, but couldn't get it working differently.

To implement this, the cppdocs steps now also runs cbindgen and cbindgen
generates a new sixtyfps_generated_public.h file that contains types we
do want to have in the public sixtyfps namespace.
This commit is contained in:
Simon Hausmann 2022-01-05 14:40:17 +01:00 committed by Simon Hausmann
parent 81602353e2
commit 174fd2659f
6 changed files with 72 additions and 36 deletions

View file

@ -20,5 +20,9 @@ fn main() -> Result<(), anyhow::Error> {
println!("cargo:GENERATED_INCLUDE_DIR={}", output_dir.display()); println!("cargo:GENERATED_INCLUDE_DIR={}", output_dir.display());
cbindgen::gen_all(&root_dir, &output_dir) let dependencies = cbindgen::gen_all(&root_dir, &output_dir)?;
for path in dependencies {
println!("cargo:rerun-if-changed={}", path.display());
}
Ok(())
} }

View file

@ -3,16 +3,19 @@
use anyhow::Context; use anyhow::Context;
use std::iter::Extend; use std::iter::Extend;
use std::path::Path; use std::path::{Path, PathBuf};
// cspell::ignore compat constexpr corelib sharedvector pathdata // cspell::ignore compat constexpr corelib sharedvector pathdata
fn ensure_cargo_rerun_for_crate(crate_dir: &Path) -> anyhow::Result<()> { fn ensure_cargo_rerun_for_crate(
println!("cargo:rerun-if-changed={}", crate_dir.display()); crate_dir: &Path,
dependencies: &mut Vec<PathBuf>,
) -> anyhow::Result<()> {
dependencies.push(crate_dir.to_path_buf());
for entry in std::fs::read_dir(crate_dir)? { for entry in std::fs::read_dir(crate_dir)? {
let entry = entry?; let entry = entry?;
if entry.path().extension().map_or(false, |e| e == "rs") { if entry.path().extension().map_or(false, |e| e == "rs") {
println!("cargo:rerun-if-changed={}", entry.path().display()); dependencies.push(entry.path());
} }
} }
Ok(()) Ok(())
@ -76,7 +79,11 @@ extern "C" {{
) )
} }
fn gen_corelib(root_dir: &Path, include_dir: &Path) -> anyhow::Result<()> { fn gen_corelib(
root_dir: &Path,
include_dir: &Path,
dependencies: &mut Vec<PathBuf>,
) -> anyhow::Result<()> {
let mut config = default_config(); let mut config = default_config();
let items = [ let items = [
@ -150,6 +157,7 @@ fn gen_corelib(root_dir: &Path, include_dir: &Path) -> anyhow::Result<()> {
"sixtyfps_color_darker", "sixtyfps_color_darker",
"sixtyfps_image_size", "sixtyfps_image_size",
"sixtyfps_image_path", "sixtyfps_image_path",
"TimerMode", // included in generated_public.h
] ]
.iter() .iter()
.map(|x| x.to_string()) .map(|x| x.to_string())
@ -158,7 +166,7 @@ fn gen_corelib(root_dir: &Path, include_dir: &Path) -> anyhow::Result<()> {
let mut crate_dir = root_dir.to_owned(); let mut crate_dir = root_dir.to_owned();
crate_dir.extend(["sixtyfps_runtime", "corelib"].iter()); crate_dir.extend(["sixtyfps_runtime", "corelib"].iter());
ensure_cargo_rerun_for_crate(&crate_dir)?; ensure_cargo_rerun_for_crate(&crate_dir, dependencies)?;
let mut string_config = config.clone(); let mut string_config = config.clone();
string_config.export.exclude = vec!["SharedString".into()]; string_config.export.exclude = vec!["SharedString".into()];
@ -296,6 +304,20 @@ fn gen_corelib(root_dir: &Path, include_dir: &Path) -> anyhow::Result<()> {
.write_to_file(include_dir.join(internal_header)); .write_to_file(include_dir.join(internal_header));
} }
// Generate a header file with some public API (enums, etc.)
let mut public_config = config.clone();
public_config.namespaces = Some(vec!["sixtyfps".into()]);
public_config.export.item_types = vec![cbindgen::ItemType::Enums];
public_config.export.include = vec!["TimerMode".into()];
public_config.export.exclude.clear();
cbindgen::Builder::new()
.with_config(public_config)
.with_src(crate_dir.join("timers.rs"))
.generate()
.context("Unable to generate bindings for sixtyfps_generated_public.h")?
.write_to_file(include_dir.join("sixtyfps_generated_public.h"));
config.export.body.insert( config.export.body.insert(
"ItemTreeNode".to_owned(), "ItemTreeNode".to_owned(),
" constexpr ItemTreeNode(Item_Body x) : item {x} {} " constexpr ItemTreeNode(Item_Body x) : item {x} {}
@ -335,6 +357,7 @@ fn gen_corelib(root_dir: &Path, include_dir: &Path) -> anyhow::Result<()> {
.with_include("sixtyfps_image.h") .with_include("sixtyfps_image.h")
.with_include("sixtyfps_pathdata.h") .with_include("sixtyfps_pathdata.h")
.with_include("sixtyfps_brush.h") .with_include("sixtyfps_brush.h")
.with_include("sixtyfps_generated_public.h")
.with_header(format!( .with_header(format!(
r" r"
#define SIXTYFPS_VERSION_MAJOR {} #define SIXTYFPS_VERSION_MAJOR {}
@ -367,7 +390,11 @@ namespace sixtyfps {
Ok(()) Ok(())
} }
fn gen_backend_qt(root_dir: &Path, include_dir: &Path) -> anyhow::Result<()> { fn gen_backend_qt(
root_dir: &Path,
include_dir: &Path,
dependencies: &mut Vec<PathBuf>,
) -> anyhow::Result<()> {
let mut config = default_config(); let mut config = default_config();
let items = [ let items = [
@ -395,7 +422,7 @@ fn gen_backend_qt(root_dir: &Path, include_dir: &Path) -> anyhow::Result<()> {
let mut crate_dir = root_dir.to_owned(); let mut crate_dir = root_dir.to_owned();
crate_dir.extend(["sixtyfps_runtime", "rendering_backends", "qt"].iter()); crate_dir.extend(["sixtyfps_runtime", "rendering_backends", "qt"].iter());
ensure_cargo_rerun_for_crate(&crate_dir)?; ensure_cargo_rerun_for_crate(&crate_dir, dependencies)?;
cbindgen::Builder::new() cbindgen::Builder::new()
.with_config(config) .with_config(config)
@ -409,12 +436,16 @@ fn gen_backend_qt(root_dir: &Path, include_dir: &Path) -> anyhow::Result<()> {
Ok(()) Ok(())
} }
fn gen_backend(root_dir: &Path, include_dir: &Path) -> anyhow::Result<()> { fn gen_backend(
root_dir: &Path,
include_dir: &Path,
dependencies: &mut Vec<PathBuf>,
) -> anyhow::Result<()> {
let config = default_config(); let config = default_config();
let mut crate_dir = root_dir.to_owned(); let mut crate_dir = root_dir.to_owned();
crate_dir.extend(["api", "sixtyfps-cpp"].iter()); crate_dir.extend(["api", "sixtyfps-cpp"].iter());
ensure_cargo_rerun_for_crate(&crate_dir)?; ensure_cargo_rerun_for_crate(&crate_dir, dependencies)?;
cbindgen::Builder::new() cbindgen::Builder::new()
.with_config(config) .with_config(config)
@ -427,14 +458,18 @@ fn gen_backend(root_dir: &Path, include_dir: &Path) -> anyhow::Result<()> {
Ok(()) Ok(())
} }
fn gen_interpreter(root_dir: &Path, include_dir: &Path) -> anyhow::Result<()> { fn gen_interpreter(
root_dir: &Path,
include_dir: &Path,
dependencies: &mut Vec<PathBuf>,
) -> anyhow::Result<()> {
let mut config = default_config(); let mut config = default_config();
// Avoid Value, just export ValueOpaque. // Avoid Value, just export ValueOpaque.
config.export.exclude.push("Value".into()); config.export.exclude.push("Value".into());
let mut crate_dir = root_dir.to_owned(); let mut crate_dir = root_dir.to_owned();
crate_dir.extend(["sixtyfps_runtime", "interpreter"].iter()); crate_dir.extend(["sixtyfps_runtime", "interpreter"].iter());
ensure_cargo_rerun_for_crate(&crate_dir)?; ensure_cargo_rerun_for_crate(&crate_dir, dependencies)?;
cbindgen::Builder::new() cbindgen::Builder::new()
.with_config(config) .with_config(config)
@ -451,12 +486,15 @@ fn gen_interpreter(root_dir: &Path, include_dir: &Path) -> anyhow::Result<()> {
/// Generate the headers. /// Generate the headers.
/// `root_dir` is the root directory of the sixtyfps git repo /// `root_dir` is the root directory of the sixtyfps git repo
/// `include_dir` is the output directory /// `include_dir` is the output directory
pub fn gen_all(root_dir: &Path, include_dir: &Path) -> anyhow::Result<()> { /// Returns the list of all paths that contain dependencies to the generated output. If you call this
/// function from build.rs, feed each entry to stdout prefixed with `cargo:rerun-if-changed=`.
pub fn gen_all(root_dir: &Path, include_dir: &Path) -> anyhow::Result<Vec<PathBuf>> {
proc_macro2::fallback::force(); // avoid a abort if panic=abort is set proc_macro2::fallback::force(); // avoid a abort if panic=abort is set
std::fs::create_dir_all(include_dir).context("Could not create the include directory")?; std::fs::create_dir_all(include_dir).context("Could not create the include directory")?;
gen_corelib(root_dir, include_dir)?; let mut deps = Vec::new();
gen_backend_qt(root_dir, include_dir)?; gen_corelib(root_dir, include_dir, &mut deps)?;
gen_backend(root_dir, include_dir)?; gen_backend_qt(root_dir, include_dir, &mut deps)?;
gen_interpreter(root_dir, include_dir)?; gen_backend(root_dir, include_dir, &mut deps)?;
Ok(()) gen_interpreter(root_dir, include_dir, &mut deps)?;
Ok(deps)
} }

View file

@ -56,8 +56,8 @@ exhale_args = {
"doxygenStripFromPath": "..", "doxygenStripFromPath": "..",
"createTreeView": True, "createTreeView": True,
"exhaleExecutesDoxygen": True, "exhaleExecutesDoxygen": True,
"exhaleDoxygenStdin": """INPUT = ../../api/sixtyfps-cpp/include "exhaleDoxygenStdin": """INPUT = ../../api/sixtyfps-cpp/include generated_include
EXCLUDE_SYMBOLS = sixtyfps::cbindgen_private* sixtyfps::private_api* vtable* EXCLUDE_SYMBOLS = sixtyfps::cbindgen_private* sixtyfps::private_api* vtable* SIXTYFPS_DECL_ITEM
EXCLUDE = ../../api/sixtyfps-cpp/include/vtable.h ../../api/sixtyfps-cpp/include/sixtyfps_testing.h EXCLUDE = ../../api/sixtyfps-cpp/include/vtable.h ../../api/sixtyfps-cpp/include/sixtyfps_testing.h
ENABLE_PREPROCESSING = YES ENABLE_PREPROCESSING = YES
PREDEFINED += DOXYGEN PREDEFINED += DOXYGEN

View file

@ -337,20 +337,6 @@ private:
private_api::WindowRc inner; private_api::WindowRc inner;
}; };
#if !defined(DOXYGEN)
using cbindgen_private::TimerMode;
#else
/// The TimerMode specifies what should happen after the timer fired.
///
/// Used by the sixtyfps::Timer::start() function.
enum class TimerMode {
/// A SingleShot timer is fired only once.
SingleShot,
/// A Repeated timer is fired repeatedly until it is stopped or dropped.
Repeated,
};
#endif
/// A Timer that can call a callback at repeated interval /// A Timer that can call a callback at repeated interval
/// ///
/// Use the static single_shot function to make a single shot timer /// Use the static single_shot function to make a single shot timer
@ -367,7 +353,7 @@ struct Timer
template<typename F> template<typename F>
Timer(std::chrono::milliseconds interval, F callback) Timer(std::chrono::milliseconds interval, F callback)
: id(cbindgen_private::sixtyfps_timer_start( : id(cbindgen_private::sixtyfps_timer_start(
-1, cbindgen_private::TimerMode::Repeated, interval.count(), -1, TimerMode::Repeated, interval.count(),
[](void *data) { (*reinterpret_cast<F *>(data))(); }, new F(std::move(callback)), [](void *data) { (*reinterpret_cast<F *>(data))(); }, new F(std::move(callback)),
[](void *data) { delete reinterpret_cast<F *>(data); })) [](void *data) { delete reinterpret_cast<F *>(data); }))
{ {

View file

@ -16,3 +16,5 @@ regex = "1.4"
toml_edit = "0.6.0" toml_edit = "0.6.0"
xshell = "0.1.6" xshell = "0.1.6"
serde_json = "1.0" serde_json = "1.0"
cbindgen = "0.20"
proc-macro2 = "1.0.11"

View file

@ -7,6 +7,9 @@ use anyhow::{Context, Result};
use std::ffi::OsString; use std::ffi::OsString;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
#[path = "../../api/sixtyfps-cpp/cbindgen.rs"]
mod cbindgen;
fn symlink_file<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Result<()> { fn symlink_file<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Result<()> {
if dst.as_ref().exists() { if dst.as_ref().exists() {
std::fs::remove_file(dst.as_ref()).context("Error removing old symlink")?; std::fs::remove_file(dst.as_ref()).context("Error removing old symlink")?;
@ -80,6 +83,9 @@ pub fn generate(show_warnings: bool) -> Result<(), Box<dyn std::error::Error>> {
docs_build_dir.join("README.md"), docs_build_dir.join("README.md"),
)?; )?;
let generated_headers_dir = docs_build_dir.join("generated_include");
cbindgen::gen_all(&root, &generated_headers_dir)?;
let pip_env = vec![(OsString::from("PIPENV_PIPFILE"), docs_source_dir.join("docs/Pipfile"))]; let pip_env = vec![(OsString::from("PIPENV_PIPFILE"), docs_source_dir.join("docs/Pipfile"))];
println!("Running pipenv install"); println!("Running pipenv install");