Merge "regtool: add a crate that can be used in build.rs to run regtool.py"
diff --git a/util/regtool/Cargo.toml b/util/regtool/Cargo.toml
new file mode 100644
index 0000000..ed409cc
--- /dev/null
+++ b/util/regtool/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "regtool"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies]
diff --git a/util/regtool/src/lib.rs b/util/regtool/src/lib.rs
new file mode 100644
index 0000000..3584abc
--- /dev/null
+++ b/util/regtool/src/lib.rs
@@ -0,0 +1,124 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#![allow(clippy::needless_doctest_main)]
+
+//! A library for build scripts to generate `.rs` files using `regtool.py`.
+//!
+//! This library is intended to be used as a `build-dependencies` entry in
+//! `Cargo.toml`:
+//!
+//! ```toml
+//! [build-dependencies]
+//! regtool = { path = "path/to/regtool" }
+//! ```
+//!
+//! By default, the library expects the environment variable `REGTOOL` to point
+//! to `regtool.py`. Either set the variable before running cargo, or use
+//! `Build::regtool(...)` from `build.rs` to set the path to the script
+//! explicitly.
+//!
+//! # Examples
+//!
+//! Use the `Build` struct to process `hw/ip/uart/data/uart.hjson`:
+//!
+//! build.rs:
+//!
+//! ```no_run
+//! fn main() {
+//!     regtool::Build::new()
+//!         .in_file_path("hw/ip/uart/data/uart.hjson")
+//!         .generate("uart.rs");
+//! }
+//! ```
+//!
+//! src/main.rs:
+//!
+//! ```no_run
+//! include!(concat!(env!("OUT_DIR"), "/uart.rs"));
+//! ```
+
+use std::env;
+use std::path::Path;
+use std::path::PathBuf;
+use std::process::Command;
+
+#[derive(Clone, Debug)]
+pub struct Build {
+    in_file_path: Option<PathBuf>, // Must be set; no default
+    out_dir: Option<PathBuf>,      // default: $OUT_DIR
+
+    python: Option<PathBuf>,
+    regtool: Option<PathBuf>, // Default: $REGTOOL
+}
+
+macro_rules! option_path_setter {
+    ($name:ident) => {
+        pub fn $name<P: AsRef<Path>>(&mut self, $name: P) -> &mut Build {
+            self.$name = Some($name.as_ref().to_owned());
+            self
+        }
+    };
+}
+
+impl Build {
+    pub fn new() -> Self {
+        Self {
+            in_file_path: None,
+            out_dir: None,
+
+            python: None,
+            regtool: None,
+        }
+    }
+
+    // Generate setter functions for Option<PathBuf> fields:
+    option_path_setter!(in_file_path);
+    option_path_setter!(out_dir);
+    option_path_setter!(python);
+    option_path_setter!(regtool);
+
+    // Run regtool. If out_file is an absolute path, write output to out_file,
+    // otherwise write output to out_dir/out_file.
+    pub fn generate(&self, out_file: &str) {
+        let regtool = if let Some(regtool) = &self.regtool {
+            regtool.to_owned()
+        } else {
+            println!("cargo:rerun-if-env-changed=REGTOOL");
+            PathBuf::from(env::var("REGTOOL").expect("missing environment variable 'REGTOOL'"))
+        };
+        println!("cargo:rerun-if-changed={}", regtool.display());
+
+        let mut out_file_path = if let Some(out_dir) = &self.out_dir {
+            out_dir.to_owned()
+        } else {
+            PathBuf::from(env::var("OUT_DIR").unwrap())
+        };
+        // NB: If out_file is absolute, it replaces the current path.
+        out_file_path.push(out_file);
+
+        let in_file_path: &Path = self
+            .in_file_path
+            .as_ref()
+            .expect("'in_file_path' is not set");
+        println!("cargo:rerun-if-changed={}", in_file_path.display());
+
+        let mut cmd;
+        if let Some(python) = &self.python {
+            cmd = Command::new(python);
+            cmd.arg(regtool);
+        } else {
+            cmd = Command::new(regtool);
+        }
+        cmd.arg("-R").arg("-o").arg(out_file_path).arg(in_file_path);
+        println!("Running: {:?}", cmd);
+        assert!(cmd.status().unwrap().success());
+    }
+}
+
+impl Default for Build {
+    fn default() -> Self {
+        Self::new()
+    }
+}