aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Cargo.toml15
-rw-r--r--build.rs224
-rw-r--r--src/lib.rs28
-rw-r--r--wrapper.h1
5 files changed, 270 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..96ef6c0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/target
+Cargo.lock
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..6251462
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "tcl-sys"
+version = "0.1.0"
+authors = ["Melody Horn <melody@boringcactus.com>"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+libc = "0.2.94"
+
+[build-dependencies]
+bindgen = "0.58.1"
+flate2 = "1.0.20"
+tar = "0.4.33"
diff --git a/build.rs b/build.rs
new file mode 100644
index 0000000..27736cd
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,224 @@
+//! derived from https://github.com/Rust-SDL2/rust-sdl2/blob/master/sdl2-sys/build.rs
+use std::path::{Path, PathBuf};
+use std::process::Command;
+use std::{env, fs};
+
+// means the latest stable version that can be downloaded from Tcl's source
+const LATEST_TCL_VERSION: &str = "8.6.11";
+
+macro_rules! add_msvc_includes_to_bindings {
+ ($bindings:expr) => {
+ $bindings = $bindings.clang_arg(format!(
+ "-IC:/Program Files (x86)/Windows Kits/8.1/Include/shared"
+ ));
+ $bindings = $bindings.clang_arg(format!("-IC:/Program Files/LLVM/lib/clang/5.0.0/include"));
+ $bindings = $bindings.clang_arg(format!(
+ "-IC:/Program Files (x86)/Windows Kits/10/Include/10.0.10240.0/ucrt"
+ ));
+ $bindings = $bindings.clang_arg(format!(
+ "-IC:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include"
+ ));
+ $bindings = $bindings.clang_arg(format!(
+ "-IC:/Program Files (x86)/Windows Kits/8.1/Include/um"
+ ));
+ };
+}
+
+fn run_command(cmd: &str, args: &[&str]) {
+ match Command::new(cmd).args(args).output() {
+ Ok(output) => {
+ if !output.status.success() {
+ let error = std::str::from_utf8(&output.stderr).unwrap();
+ panic!("Command '{}' failed: {}", cmd, error);
+ }
+ }
+ Err(error) => {
+ panic!("Error running command '{}': {:#}", cmd, error);
+ }
+ }
+}
+
+fn download_to(url: &str, dest: &str) {
+ if cfg!(windows) {
+ run_command(
+ "powershell",
+ &[
+ "-NoProfile",
+ "-NonInteractive",
+ "-Command",
+ &format!(
+ "& {{
+ $client = New-Object System.Net.WebClient
+ $client.DownloadFile(\"{0}\", \"{1}\")
+ if (!$?) {{ Exit 1 }}
+ }}",
+ url, dest
+ )
+ .as_str(),
+ ],
+ );
+ } else {
+ run_command("curl", &[url, "-o", dest]);
+ }
+}
+
+// returns the location of the downloaded source
+fn download_tcl() -> PathBuf {
+ let out_dir = env::var("OUT_DIR").unwrap();
+
+ let tcl_archive_name = format!("tcl{}-src.tar.gz", LATEST_TCL_VERSION);
+ let tcl_archive_url = format!(
+ "https://iweb.dl.sourceforge.net/project/tcl/Tcl/{}/{}",
+ LATEST_TCL_VERSION, tcl_archive_name
+ );
+
+ let tcl_archive_path = Path::new(&out_dir).join(tcl_archive_name);
+ let tcl_build_path = Path::new(&out_dir).join(format!("tcl{}", LATEST_TCL_VERSION));
+
+ // avoid re-downloading the archive if it already exists
+ if !tcl_archive_path.exists() {
+ download_to(&tcl_archive_url, tcl_archive_path.to_str().unwrap());
+ }
+
+ let reader = flate2::read::GzDecoder::new(fs::File::open(&tcl_archive_path).unwrap());
+ let mut ar = tar::Archive::new(reader);
+ ar.unpack(&out_dir).unwrap();
+
+ tcl_build_path
+}
+
+// compile a static lib
+fn compile_tcl(tcl_build_path: &Path, target_os: &str) -> PathBuf {
+ let out_dir = env::var("OUT_DIR").unwrap();
+ if target_os == "windows-msvc" {
+ let vswhere =
+ Command::new(r"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe")
+ .args(&[
+ "-products",
+ "*",
+ "-prerelease",
+ "-latest",
+ "-property",
+ "installationPath",
+ ])
+ .output()
+ .unwrap();
+ if !vswhere.status.success() {
+ panic!("vswhere failed??");
+ }
+ let output = String::from_utf8(vswhere.stdout).unwrap();
+ let vs_root = output.trim();
+ if vs_root.lines().count() != 1 {
+ panic!("vswhere found multiple roots??");
+ }
+ let install_dir = Path::new(&out_dir).join("install");
+ // TODO i am going to stab a bitch why is this the easiest way to do this
+ let bat = format!(
+ r#"
+ call "{}\Common7\Tools\VSDevCmd.bat" -arch=amd64 -host_arch=amd64
+ nmake -nologo -f makefile.vc release install OPTS=msvcrt,static,staticpkg "INSTALLDIR={}"
+ "#,
+ vs_root,
+ install_dir.to_str().unwrap(),
+ );
+ let bat_file = Path::new(&out_dir).join("build.bat");
+ fs::write(&bat_file, bat).unwrap();
+ let build = Command::new("cmd.exe")
+ .arg("/c")
+ .arg(bat_file.as_os_str())
+ .current_dir(tcl_build_path.join("win"))
+ .env_remove("OUT_DIR")
+ .status()
+ .unwrap();
+ if !build.success() {
+ panic!("nmake failed??")
+ }
+ install_dir
+ } else {
+ todo!()
+ }
+}
+
+fn link_tcl(target_os: &str) {
+ println!("cargo:rustc-link-lib=static=tcl86tsx");
+ println!("cargo:rustc-link-lib=static=tclstub86");
+
+ // Also linked to any required libraries for each supported platform
+ if target_os.contains("windows") {
+ println!("cargo:rustc-link-lib=user32");
+ println!("cargo:rustc-link-lib=netapi32");
+ } else {
+ // TODO: Add other platform linker options here.
+ }
+}
+
+fn main() {
+ let target = env::var("TARGET").expect("Cargo build scripts always have TARGET");
+ let host = env::var("HOST").expect("Cargo build scripts always have HOST");
+ let target_os = get_os_from_triple(target.as_str()).unwrap();
+
+ let tcl_source_path = download_tcl();
+ let tcl_compiled_path = compile_tcl(tcl_source_path.as_path(), target_os);
+
+ let tcl_include_path = tcl_compiled_path.join("include");
+ let tcl_lib_path = tcl_compiled_path.join("lib");
+
+ println!("cargo:rustc-link-search={}", tcl_lib_path.display());
+
+ let include_paths = vec![String::from(tcl_include_path.to_str().unwrap())];
+ println!("cargo:include={}", include_paths.join(":"));
+ generate_bindings(target.as_str(), host.as_str(), include_paths.as_slice());
+
+ link_tcl(target_os);
+}
+
+// headers_path is a list of directories where the Tcl headers are expected
+// to be found by bindgen
+fn generate_bindings(target: &str, host: &str, headers_paths: &[String]) {
+ let target_os = get_os_from_triple(target).unwrap();
+ let mut bindings = bindgen::Builder::default()
+ // enable no_std-friendly output by only using core definitions
+ .use_core()
+ .default_enum_style(bindgen::EnumVariation::Rust {
+ non_exhaustive: false,
+ })
+ .ctypes_prefix("libc");
+
+ // Set correct target triple for bindgen when cross-compiling
+ if target != host {
+ bindings = bindings.clang_arg("-target");
+ bindings = bindings.clang_arg(target.clone());
+ }
+
+ if headers_paths.len() == 0 {
+ panic!("no headers");
+ } else {
+ // if paths are included, use them for bindgen. Bindgen should use the first one.
+ println!("cargo:include={}", headers_paths.join(":"));
+ for headers_path in headers_paths {
+ bindings = bindings.clang_arg(format!("-I{}", headers_path));
+ }
+ }
+
+ if target_os == "windows-msvc" {
+ add_msvc_includes_to_bindings!(bindings);
+ };
+
+ let bindings = bindings
+ .header("wrapper.h")
+ .allowlist_type("Tcl_.*")
+ .allowlist_var("TCL_.*")
+ .allowlist_function("Tcl_.*")
+ .generate()
+ .expect("Unable to generate bindings!");
+
+ let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
+
+ bindings
+ .write_to_file(out_path.join("bindings.rs"))
+ .expect("Couldn't write bindings!");
+}
+
+fn get_os_from_triple(triple: &str) -> Option<&str> {
+ triple.splitn(3, "-").nth(2)
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..e3c7e47
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,28 @@
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+
+include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::ffi::CString;
+
+ #[test]
+ fn two_plus_two() {
+ unsafe {
+ let interp = Tcl_CreateInterp();
+ let script = CString::new("expr 2 + 2").unwrap();
+ let eval_status = Tcl_Eval(interp, script.as_ptr());
+ assert_eq!(eval_status, TCL_OK as i32);
+ let result = Tcl_GetObjResult(interp);
+ let mut result_value = 0;
+ let get_int_status = Tcl_GetIntFromObj(interp, result, &mut result_value);
+ assert_eq!(get_int_status, TCL_OK as i32);
+ assert_eq!(result_value, 4);
+
+ Tcl_DeleteInterp(interp);
+ }
+ }
+}
diff --git a/wrapper.h b/wrapper.h
new file mode 100644
index 0000000..5364b2e
--- /dev/null
+++ b/wrapper.h
@@ -0,0 +1 @@
+#include <tcl.h>