blob: 02ba877dee5e9e25a8dfe922ad6b943f0d90560b [file] [log] [blame] [edit]
//! Wayland scanner crate
//!
//!
//! This crate is a rust equivalent of the wayland-scanner tool from the
//! official wayland C library.
//!
//! You can use in your build script to generate the rust code for any
//! wayland protocol file, to use alongside the `wayland_client` and
//! `wayland_server` crate to build your applications.
//!
//! ## How to use this crate
//!
//! This crate is to be used in a build script. It provides the function `generate_code`.
//! It'll allow you to generate the code to use with the `wayland_client` or
//! `wayland_server` crates for any XML wayland protocol file (NB: you don't
//! need to do it for the core protocol, which is already included in both crates).
//!
//! First, have the XML files you want to use in your project, somewhere the build script
//! will be able to read them.
//!
//! Then, you'll need to invoke `generate_code` for each of these files.
//!
//! A sample build script:
//!
//! ```no_run
//! extern crate wayland_scanner;
//!
//! use std::env::var;
//! use std::path::Path;
//!
//! use wayland_scanner::{Side, generate_code};
//!
//! // Location of the xml file, relative to the `Cargo.toml`
//! let protocol_file = "./my_protocol.xml";
//!
//! // Target directory for the generate files
//! let out_dir_str = var("OUT_DIR").unwrap();
//! let out_dir = Path::new(&out_dir_str);
//!
//! generate_code(
//! protocol_file,
//! out_dir.join("my_protocol_api.rs"),
//! Side::Client, // Replace by `Side::Server` for server-side code
//! );
//! ```
//!
//! The above example will output a `.rs` file in the `OUT_DIR` defined by
//! cargo. Then, you'll need to include this generated file (using the
//! macro of the same name) to make this code available in your crate.
//!
//! ```ignore
//! // The generated code will import stuff from wayland_commons
//! extern crate wayland_commons;
//! extern crate wayland_client;
//!
//! // Re-export only the actual code, and then only use this re-export
//! // The `generated` module below is just some boilerplate to properly isolate stuff
//! // and avoid exposing internal details.
//! //
//! // You can use all the types from my_protocol as if they went from `wayland_client::protocol`.
//! pub use generated::client as my_protocol;
//!
//! mod generated {
//! // The generated code tends to trigger a lot of warnings
//! // so we isolate it into a very permissive module
//! #![allow(dead_code,non_camel_case_types,unused_unsafe,unused_variables)]
//! #![allow(non_upper_case_globals,non_snake_case,unused_imports)]
//!
//! pub mod client {
//! // These imports are used by the generated code
//! pub(crate) use wayland_client::{Main, Attached, Proxy, ProxyMap, AnonymousObject};
//! pub(crate) use wayland_commons::map::{Object, ObjectMetadata};
//! pub(crate) use wayland_commons::{Interface, MessageGroup};
//! pub(crate) use wayland_commons::wire::{Argument, MessageDesc, ArgumentType, Message};
//! pub(crate) use wayland_commons::smallvec;
//! pub(crate) use wayland_client::protocol::{$($import),*};
//! pub(crate) use wayland_client::sys;
//! // If you protocol interacts with objects from other protocols, you'll need to import
//! // their modules, like so:
//! pub(crate) use wayland_client::protocol::{wl_surface, wl_region};
//! include!(concat!(env!("OUT_DIR"), "/my_protocol_code.rs"));
//! }
//! }
//! ```
#![warn(missing_docs)]
// disable clippy lints that are not compatible with rust 1.41
#![allow(clippy::match_like_matches_macro)]
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::path::Path;
use std::process::Command;
mod c_code_gen;
mod c_interface_gen;
mod common_gen;
mod parse;
mod protocol;
mod side;
mod util;
pub use side::Side;
fn load_xml<P: AsRef<Path>>(prot: P) -> protocol::Protocol {
let pfile = File::open(prot.as_ref()).unwrap_or_else(|_| {
panic!(
"Unable to open protocol file `{}`.",
prot.as_ref().display()
)
});
parse::parse_stream(pfile)
}
/// Generate the code for a protocol
///
/// See this crate toplevel documentation for details.
///
/// Args:
///
/// - `protocol`: a path to the XML file describing the protocol, absolute or relative to
/// the build script using this function.
/// - `target`: the path of the file to store the code in.
/// - `side`: the side (client or server) to generate code for.
pub fn generate_code<P1: AsRef<Path>, P2: AsRef<Path>>(prot: P1, target: P2, side: Side) {
generate_code_with_destructor_events(prot, target, side, &[]);
}
/// Generate the code for a protocol with aditionnal destructor events
///
/// Same as `generate_code`, but allows you to additionnaly specify some events
/// (in the format `("interface_name", "event_name")`) as being destructor, as this
/// information is not encoded in the protocol files but instead written in the
/// protocol documentation.
pub fn generate_code_with_destructor_events<P1: AsRef<Path>, P2: AsRef<Path>>(
prot: P1,
target: P2,
side: Side,
events: &[(&str, &str)],
) {
let mut protocol = load_xml(prot);
for interface in &mut protocol.interfaces {
for event in &mut interface.events {
if events.contains(&(&interface.name, &event.name)) {
event.typ = Some(crate::protocol::Type::Destructor);
}
}
}
{
let mut out = OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(&target)
.unwrap();
let output = match side {
Side::Client => c_code_gen::generate_protocol_client(protocol),
Side::Server => c_code_gen::generate_protocol_server(protocol),
};
write!(&mut out, "{}", output).unwrap();
}
let _ = Command::new("rustfmt").arg(target.as_ref()).status();
}
/// Generate the code for a protocol from/to IO streams
///
/// Like `generate_code`, but takes IO Streams directly rather than filenames
///
/// Args:
///
/// - `protocol`: an object `Read`-able containing the XML protocol file
/// - `target`: a `Write`-able object to which the generated code will be outputted to
/// - `side`: the side (client or server) to generate code for.
pub fn generate_code_streams<P1: Read, P2: Write>(protocol: P1, target: &mut P2, side: Side) {
generate_code_streams_with_destructor_events(protocol, target, side, &[])
}
/// Generate the code for a protocol from/to IO streams with aditionnal destructor events
///
/// Same as `generate_code_streams`, but allows you to additionnaly specify some events
/// (in the format `("interface_name", "event_name")`) as being destructor, as this
/// information is not encoded in the protocol files but instead written in the documentation
/// of the protocol.
pub fn generate_code_streams_with_destructor_events<P1: Read, P2: Write>(
protocol: P1,
target: &mut P2,
side: Side,
events: &[(&str, &str)],
) {
let mut protocol = parse::parse_stream(protocol);
for interface in &mut protocol.interfaces {
for event in &mut interface.events {
if events.contains(&(&interface.name, &event.name)) {
event.typ = Some(crate::protocol::Type::Destructor);
}
}
}
let output = match side {
Side::Client => c_code_gen::generate_protocol_client(protocol),
Side::Server => c_code_gen::generate_protocol_server(protocol),
};
write!(target, "{}", output).unwrap();
}