diff --git a/.hgignore b/.hgignore --- a/.hgignore +++ b/.hgignore @@ -18,3 +18,4 @@ subinclude:cfastmanifest/.hgignore subinclude:linelog/.hgignore +subinclude:examplerust/.hgignore diff --git a/examplerust/.hgignore b/examplerust/.hgignore new file mode 100644 --- /dev/null +++ b/examplerust/.hgignore @@ -0,0 +1,2 @@ +rustexamplerust/Cargo.lock +rustexamplerust/target diff --git a/examplerust/Makefile b/examplerust/Makefile new file mode 100644 --- /dev/null +++ b/examplerust/Makefile @@ -0,0 +1,31 @@ +# Cargo's depfiles use full paths, so we must include the full path +# to the rust library. +DIR := $(dir $(realpath $(firstword $(MAKEFILE_LIST)))) + +ifeq ($(shell uname),Darwin) +# On MacOS, we must configure the linker to allow resolution of +# undefined symbols at runtime. +LIBSUFFIX := .dylib +CARGO_ARGS += -C link-args='-Wl,-undefined,dynamic_lookup' +else +LIBSUFFIX := .so +endif + +RUSTLIB := $(DIR)rustexamplerust/target/release/librustexamplerust$(LIBSUFFIX) + +.PHONY: test +test: examplerust/rustexamplerust.so + hg --config extensions.examplerust=examplerust rust + +examplerust/rustexamplerust.so: $(RUSTLIB) + cp $< $@ + +$(RUSTLIB): + cd rustexamplerust && cargo rustc --release -- $(CARGO_ARGS) + +.PHONY: clean +clean: + $(RM) examplerust/rustexamplerust.so + cd rustexamplerust && cargo clean + +-include rustexamplerust/target/release/librustexamplerust.d diff --git a/examplerust/README.md b/examplerust/README.md new file mode 100644 --- /dev/null +++ b/examplerust/README.md @@ -0,0 +1,34 @@ +examplerust +=========== + +This is an example extension written in [Rust](https://www.rust-lang.org/). +The extension adds a single command: `hg rust`, which prints out a short +text string. Its purpose is to show how integration of extensions written +in Rust might occur. + +Building +======== + +The extension uses [PyO3](https://github.com/PyO3/pyo3) to interface between +Python and Rust, in much the same way as the CPython API is used for extensions +written in C. As a result, it currently requires a **nightly** rust compiler. + +If you are using `rustup`, you can switch to the nightly compiler using +`rustup default nightly`. + +To build, run `make` in this directory. This will call `cargo` to compile +the rust library and then copy it into the `examplerust` extension directory. + +Running +======= + +To use the extension, run `hg --config extensions.examplerust=examplerust` +from this directory. + +Implementation Details +====================== + +As with the extensions written in C, there is a Python extension, `examplerust`, +which imports the Rust library, `rustexamplerust`. The Python extension takes +care of registering methods with the Mercurial registry, and then delegates the +execution of the command to the Rust library. diff --git a/examplerust/examplerust/__init__.py b/examplerust/examplerust/__init__.py new file mode 100644 --- /dev/null +++ b/examplerust/examplerust/__init__.py @@ -0,0 +1,21 @@ +# examplerust.py +# +# Copyright 2017 Facebook, Inc. +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. +"""example extension written in Rust""" + +from . import rustexamplerust + +from mercurial import registrar + +cmdtable = {} +command = registrar.command(cmdtable) + +@command('rust', [ + ('w', 'how', None, 'Exactly how cool is rust?'), +]) +def rust(ui, repo, *args, **kwargs): + """Run something in rust to show how cool it is.""" + return rustexamplerust.rust(ui, repo, *args, **kwargs) diff --git a/examplerust/rustexamplerust/Cargo.toml b/examplerust/rustexamplerust/Cargo.toml new file mode 100644 --- /dev/null +++ b/examplerust/rustexamplerust/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rustexamplerust" +version = "0.1.0" +authors = ["Facebook Source Control Team "] + +[lib] +name = "rustexamplerust" +crate-type = ["cdylib"] + +[dependencies.pyo3] +version = "0.2" +features = ["extension-module", "python2"] diff --git a/examplerust/rustexamplerust/src/lib.rs b/examplerust/rustexamplerust/src/lib.rs new file mode 100644 --- /dev/null +++ b/examplerust/rustexamplerust/src/lib.rs @@ -0,0 +1,42 @@ +// rustexamplerust/src/lib.rs: Rust interface for examplerust +// +// Copyright 2017 Facebook, Inc. +// +// This software may be used and distributed according to the terms of the +// GNU General Public License version 2 or any later version. +// +#![feature(proc_macro, specialization)] + +extern crate pyo3; +use pyo3::prelude::*; + +#[py::modinit(rustexamplerust)] +fn init_mod(py: Python, m: &PyModule) -> PyResult<()> { + + /// Example module function. This function uses the passed-in + /// `ui` object to print a status message. + #[pyfn(m, "rust", _args="*", kwargs="**")] + fn rust( + ui: &PyObjectRef, + _repo: &PyObjectRef, + _args: &PyTuple, + kwargs: Option<&PyDict>, + ) -> PyResult<()> { + + // Check the value of the "how" item in `kwargs`. Accessing + // Python keyword arguments in Rust is a bit cumbersome. It is + // preferable to unpack arguments like this in the Python + // wrapper, and pass them in as primitive types when possible. + // This example is included here for completeness. + if kwargs.and_then(|x| x.get_item("how")) + .and_then(|x| x.cast_as::().ok()) + .map(PyBool::is_true) == Some(true) { + ui.call_method1("status", ("Rust is very cool!\n",))?; + } else { + ui.call_method1("status", ("Rust is cool!\n",))?; + } + Ok(()) + } + + Ok(()) +}