diff --git a/Cargo.lock b/Cargo.lock
index 77fee259..14151072 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -177,26 +177,6 @@ dependencies = [
  "rustc-demangle",
 ]
 
-[[package]]
-name = "bindgen"
-version = "0.68.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078"
-dependencies = [
- "bitflags 2.6.0",
- "cexpr",
- "clang-sys",
- "lazy_static",
- "lazycell",
- "peeking_take_while",
- "proc-macro2",
- "quote",
- "regex",
- "rustc-hash 1.1.0",
- "shlex",
- "syn 2.0.72",
-]
-
 [[package]]
 name = "bitflags"
 version = "1.3.2"
@@ -271,15 +251,6 @@ version = "1.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f"
 
-[[package]]
-name = "cexpr"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
-dependencies = [
- "nom",
-]
-
 [[package]]
 name = "cfg-if"
 version = "1.0.0"
@@ -333,17 +304,6 @@ dependencies = [
  "half",
 ]
 
-[[package]]
-name = "clang-sys"
-version = "1.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
-dependencies = [
- "glob",
- "libc",
- "libloading",
-]
-
 [[package]]
 name = "clap"
 version = "3.2.25"
@@ -794,12 +754,6 @@ version = "0.29.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
 
-[[package]]
-name = "glob"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
-
 [[package]]
 name = "half"
 version = "2.4.1"
@@ -1121,39 +1075,12 @@ version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
 
-[[package]]
-name = "lazycell"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
-
 [[package]]
 name = "libc"
 version = "0.2.155"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
 
-[[package]]
-name = "libloading"
-version = "0.8.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
-dependencies = [
- "cfg-if",
- "windows-targets 0.52.6",
-]
-
-[[package]]
-name = "libuv-sys2"
-version = "1.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6125e1a220a5698a154ce76762d2ef8884baf9f77da7ceb8a3bd8c5ce27df343"
-dependencies = [
- "bindgen",
- "cc",
- "pkg-config",
-]
-
 [[package]]
 name = "linked-hash-map"
 version = "0.5.6"
@@ -1456,12 +1383,6 @@ version = "1.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
 
-[[package]]
-name = "peeking_take_while"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
-
 [[package]]
 name = "percent-encoding"
 version = "2.3.1"
@@ -1541,12 +1462,6 @@ dependencies = [
  "wasm-logger",
 ]
 
-[[package]]
-name = "pkg-config"
-version = "0.3.30"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
-
 [[package]]
 name = "pl_linker"
 version = "0.1.0"
@@ -2322,11 +2237,11 @@ version = "0.1.0"
 dependencies = [
  "backtrace",
  "bytecount",
+ "cc",
  "context",
  "immix",
  "internal_macro",
  "libc",
- "libuv-sys2",
  "log",
  "rand",
  "winapi",
diff --git a/vm/Cargo.toml b/vm/Cargo.toml
index 760f160f..baabab92 100644
--- a/vm/Cargo.toml
+++ b/vm/Cargo.toml
@@ -14,13 +14,15 @@ log = { version = "0.4", features = ["std"] }
 libc = "0.2"
 bytecount = "0.6.3"
 context = "3.0.0"
-libuv-sys2 = "1.48.0"
 [target.'cfg(windows)'.dependencies]
 winapi = { version = "0.3", features = ["winuser", "wincrypt"] }
 
 [dev-dependencies]
 rand = "0.8"
 
+[build-dependencies]
+cc = "1.0"
+
 [lib]
 name = "vm"
 path = "src/lib.rs"
diff --git a/vm/build.rs b/vm/build.rs
index 0e7eb5eb..2c088385 100644
--- a/vm/build.rs
+++ b/vm/build.rs
@@ -1,3 +1,322 @@
+use std::env;
+// use std::error;
+// use std::fmt;
+// use std::fs::OpenOptions;
+// use std::io;
+// use std::io::Write;
+use std::path::{Path, PathBuf};
+use std::process::Command;
+
+// static LIBUV_VERSION: &str = "1.48.0";
+
+// #[derive(Debug)]
+// enum Error {
+//     BindgenError,
+//     PathError(String, io::Error),
+// }
+
+// impl error::Error for Error {
+//     fn source(&self) -> Option<&(dyn error::Error + 'static)> {
+//         match self {
+//             Error::PathError(_, err) => Some(err),
+//             _ => None,
+//         }
+//     }
+// }
+
+// impl fmt::Display for Error {
+//     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+//         match self {
+//             Error::BindgenError => write!(f, "Failed to generate ffi bindings"),
+//             Error::PathError(dir, source) => write!(f, "Path error for `{}`: {}", dir, source),
+//         }
+//     }
+// }
+
+// type Result<T> = std::result::Result<T, Error>;
+
+// fn build_pkgconfig_max_version() -> String {
+//     let dotidx = LIBUV_VERSION.find('.').unwrap();
+//     let dotidx2 = LIBUV_VERSION[(dotidx + 1)..].find('.').unwrap() + dotidx + 1;
+//     let next_minor_version = LIBUV_VERSION[(dotidx + 1)..dotidx2]
+//         .parse::<usize>()
+//         .unwrap()
+//         + 1;
+//     format!("{}.{}.0", &LIBUV_VERSION[..dotidx], next_minor_version)
+// }
+
+
+
+fn build<P: AsRef<Path>>(source_path: &P) {
+    let src_path = source_path.as_ref().join("src");
+    let unix_path = src_path.join("unix");
+
+    let target = env::var("TARGET").unwrap();
+    let android = target.ends_with("-android") || target.ends_with("-androideabi");
+    let apple = target.contains("-apple-");
+    let dragonfly = target.ends_with("-dragonfly");
+    let freebsd = target.ends_with("-freebsd");
+    let linux = target.contains("-linux-");
+    let netbsd = target.ends_with("-netbsd");
+    let openbsd = target.ends_with("-openbsd");
+    let solaris = target.ends_with("-solaris");
+
+    // based on libuv's CMakeLists.txt
+    let mut build = cc::Build::new();
+    let compiler = build.get_compiler();
+    let clang = compiler.is_like_clang();
+    let gnu = compiler.is_like_gnu();
+    let msvc = compiler.is_like_msvc();
+    build
+        .include(source_path.as_ref().join("include"))
+        .include(&src_path);
+
+    if msvc {
+        build
+            .flag("/W4")
+            .flag("/wd4100") // no-unused-parameter
+            .flag("/wd4127") // no-conditional-constant
+            .flag("/wd4201") // no-nonstandard
+            .flag("/wd4206") // no-nonstandard-empty-tu
+            .flag("/wd4210") // no-nonstandard-file-scope
+            .flag("/wd4232") // no-nonstandard-nonstatic-dlimport
+            .flag("/wd4456") // no-hides-local
+            .flag("/wd4457") // no-hides-param
+            .flag("/wd4459") // no-hides-global
+            .flag("/wd4706") // no-conditional-assignment
+            .flag("/wd4996") // no-unsafe
+            .flag("/utf-8"); // utf8
+    } else if apple || clang || gnu {
+        build
+            .flag("-fvisibility=hidden")
+            .flag("--std=gnu89")
+            .flag("-Wall")
+            .flag("-Wextra")
+            .flag("-Wstrict-prototypes")
+            .flag("-Wno-unused-parameter");
+    }
+    if gnu {
+        build.flag("-fno-strict-aliasing");
+    }
+
+    build
+        .file(src_path.join("fs-poll.c"))
+        .file(src_path.join("idna.c"))
+        .file(src_path.join("inet.c"))
+        .file(src_path.join("random.c"))
+        .file(src_path.join("strscpy.c"))
+        .file(src_path.join("strtok.c"))
+        .file(src_path.join("thread-common.c"))
+        .file(src_path.join("threadpool.c"))
+        .file(src_path.join("timer.c"))
+        .file(src_path.join("uv-common.c"))
+        .file(src_path.join("uv-data-getter-setters.c"))
+        .file(src_path.join("version.c"));
+
+    if cfg!(windows) {
+        println!("cargo:rustc-link-lib=shell32");
+        println!("cargo:rustc-link-lib=psapi");
+        println!("cargo:rustc-link-lib=user32");
+        println!("cargo:rustc-link-lib=advapi32");
+        println!("cargo:rustc-link-lib=iphlpapi");
+        println!("cargo:rustc-link-lib=userenv");
+        println!("cargo:rustc-link-lib=ws2_32");
+        println!("cargo:rustc-link-lib=dbghelp");
+        println!("cargo:rustc-link-lib=ole32");
+
+        let win_path = src_path.join("win");
+        build
+            .define("_WIN32_WINNT", "0x0602")
+            .define("WIN32_LEAN_AND_MEAN", None)
+            .define("_CRT_DECLARE_NONSTDC_NAMES", "0")
+            .file(win_path.join("async.c"))
+            .file(win_path.join("core.c"))
+            .file(win_path.join("detect-wakeup.c"))
+            .file(win_path.join("dl.c"))
+            .file(win_path.join("error.c"))
+            .file(win_path.join("fs.c"))
+            .file(win_path.join("fs-event.c"))
+            .file(win_path.join("getaddrinfo.c"))
+            .file(win_path.join("getnameinfo.c"))
+            .file(win_path.join("handle.c"))
+            .file(win_path.join("loop-watcher.c"))
+            .file(win_path.join("pipe.c"))
+            .file(win_path.join("thread.c"))
+            .file(win_path.join("poll.c"))
+            .file(win_path.join("process.c"))
+            .file(win_path.join("process-stdio.c"))
+            .file(win_path.join("signal.c"))
+            .file(win_path.join("snprintf.c"))
+            .file(win_path.join("stream.c"))
+            .file(win_path.join("tcp.c"))
+            .file(win_path.join("tty.c"))
+            .file(win_path.join("udp.c"))
+            .file(win_path.join("util.c"))
+            .file(win_path.join("winapi.c"))
+            .file(win_path.join("winsock.c"));
+    } else {
+        // CMakeLists.txt also checks that it's not OS/390 and not QNX
+        if !android {
+            println!("cargo:rustc-link-lib=pthread");
+        }
+
+        build
+            .define("_FILE_OFFSET_BITS", "64")
+            .define("_LARGEFILE_SOURCE", None)
+            .file(unix_path.join("async.c"))
+            .file(unix_path.join("core.c"))
+            .file(unix_path.join("dl.c"))
+            .file(unix_path.join("fs.c"))
+            .file(unix_path.join("getaddrinfo.c"))
+            .file(unix_path.join("getnameinfo.c"))
+            .file(unix_path.join("loop-watcher.c"))
+            .file(unix_path.join("loop.c"))
+            .file(unix_path.join("pipe.c"))
+            .file(unix_path.join("poll.c"))
+            .file(unix_path.join("process.c"))
+            .file(unix_path.join("random-devurandom.c"))
+            .file(unix_path.join("signal.c"))
+            .file(unix_path.join("stream.c"))
+            .file(unix_path.join("tcp.c"))
+            .file(unix_path.join("thread.c"))
+            .file(unix_path.join("tty.c"))
+            .file(unix_path.join("udp.c"));
+    }
+
+    // CMakeLists.txt has some special additions for AIX here; how do I test for it?
+
+    if android {
+        println!("cargo:rustc-link-lib=dl");
+        build
+            .define("_GNU_SOURCE", None)
+            .file(unix_path.join("linux.c"))
+            .file(unix_path.join("procfs-exepath.c"))
+            .file(unix_path.join("random-getentropy.c"))
+            .file(unix_path.join("random-getrandom.c"))
+            .file(unix_path.join("random-sysctl-linux.c"));
+    }
+
+    if apple || android || linux {
+        build.file(unix_path.join("proctitle.c"));
+    }
+
+    if dragonfly || freebsd {
+        build.file(unix_path.join("freebsd.c"));
+    }
+
+    if dragonfly || freebsd || netbsd || openbsd {
+        build
+            .file(unix_path.join("posix-hrtime.c"))
+            .file(unix_path.join("bsd-proctitle.c"));
+    }
+
+    if apple || dragonfly || freebsd || netbsd || openbsd {
+        build
+            .file(unix_path.join("bsd-ifaddrs.c"))
+            .file(unix_path.join("kqueue.c"));
+    }
+
+    if freebsd {
+        build.file(unix_path.join("random-getrandom.c"));
+    }
+
+    if apple || openbsd {
+        build.file(unix_path.join("random-getentropy.c"));
+    }
+
+    if apple {
+        build
+            .define("_DARWIN_UNLIMITED_SELECT", "1")
+            .define("_DARWIN_USE_64_BIT_INODE", "1")
+            .file(unix_path.join("darwin-proctitle.c"))
+            .file(unix_path.join("darwin.c"))
+            .file(unix_path.join("fsevents.c"));
+    }
+
+    // CMakeLists.txt has a check for GNU here
+
+    if linux {
+        build
+            .define("_GNU_SOURCE", None)
+            .define("_POSIX_C_SOURCE", "200112")
+            .file(unix_path.join("linux.c"))
+            .file(unix_path.join("procfs-exepath.c"))
+            .file(unix_path.join("random-getrandom.c"))
+            .file(unix_path.join("random-sysctl-linux.c"));
+        println!("cargo:rustc-link-lib=dl");
+        println!("cargo:rustc-link-lib=rt");
+    }
+
+    if netbsd {
+        build.file(unix_path.join("netbsd.c"));
+        println!("cargo:rustc-link-lib=kvm");
+    }
+
+    if openbsd {
+        build.file(unix_path.join("openbsd.c"));
+    }
+
+    // CMakeLists.txt has a check for OS/390 and OS/400 here
+
+    if solaris {
+        // CMakeLists.txt has a check for a specific version of Solaris here (v5.10)
+        build
+            .define("__EXTENSIONS__", None)
+            .define("_XOPEN_SOURCE", "500")
+            .define("_REENTRANT", None)
+            .file(unix_path.join("no-proctitle.c"))
+            .file(unix_path.join("sunos.c"));
+        println!("cargo:rustc-link-lib=kstat");
+        println!("cargo:rustc-link-lib=nsl");
+        println!("cargo:rustc-link-lib=sendfile");
+        println!("cargo:rustc-link-lib=socket");
+    }
+
+    // CMakeLists.txt has checks for Haiku, QNX, Cygwin, and MSYS here
+
+    build.compile("uv");
+}
+
+fn run<F>(name: &str, mut configure: F)
+where
+    F: FnMut(&mut Command) -> &mut Command,
+{
+    let mut command = Command::new(name);
+    let configured = configure(&mut command);
+    if !configured.status().is_ok() {
+        let err = configured.status().unwrap_err();
+        panic!("failed to execute {:?}: {}", configured, err);
+    }
+}
+
+const LIBUV_REPO: &str = "https://github.com/libuv/libuv.git";
+const LIBUV_DIR: &str = "libuv";
+
 fn main() {
-    println!("cargo:rustc-link-lib=static:+whole-archive=uv");
+    println!("cargo:rerun-if-changed=build.rs");
+    let out_dir = env::var("OUT_DIR").unwrap();
+    let mut uv_src = PathBuf::from(out_dir);
+    uv_src.push(LIBUV_DIR);
+    if !uv_src.exists() {
+        run("git", |cmd| {
+            cmd.arg("clone").arg(LIBUV_REPO).arg(&uv_src)
+        });
+
+        // run("git", |cmd| {
+        //     cmd.arg("clone")
+        //         .arg(BOEHM_ATOMICS_REPO)
+        //         .current_dir(&boehm_src)
+        // });
+
+        // env::set_current_dir(&boehm_src).unwrap();
+        // run("cmake", |cmd| cmd.arg("."));
+        // run("cmake", |cmd| {
+        //     cmd.args(&["--build", ".", "--config", "Release"])
+        // });
+    }
+
+    build(&uv_src);
+
+
+    // println!("cargo:rustc-link-lib=static:+whole-archive=uv");
 }
diff --git a/vm/src/lib.rs b/vm/src/lib.rs
index 81311bfb..f3857f53 100644
--- a/vm/src/lib.rs
+++ b/vm/src/lib.rs
@@ -14,7 +14,6 @@ pub mod libcwrap;
 pub mod logger;
 pub mod mutex;
 pub mod time;
-pub use libuv_sys2::*;
 
 #[is_runtime]
 fn test_vm_link() -> i64 {