Repository: realtime-sanitizer/rtsan-libs
Pull request #1: Initial commit to add build actions for libraries
Author: login: cjappl
Created: 2025-03-29T13:48:57Z | Merged: 2025-04-02T17:19:28Z

This adds a simple makefile and a github action to run through that makefile.


What this does not do:
1. Do any verification on the binaries
2. Sign or attest the binaries
3. Release the binaries where the standalones can consume them

Just trying to break things into smaller pieces so they can be reviewed effectively.

Feedback I'm looking for:
1. Name of this repo is `rtsan-libs` is that ok?
2. Any feedback on the makefile or yml?
3. Any top level thoughts on if I'm going about this the right or wrong way.


Proof that this action/makefile does work to build **something**
https://github.com/cjappl/rtsan-libs/actions/runs/14146229798



Diff:
diff --git a/.github/workflows/build_rtsan_libs.yml b/.github/workflows/build_rtsan_libs.yml
new file mode 100644
index 0000000..6356d99
--- /dev/null
+++ b/.github/workflows/build_rtsan_libs.yml
@@ -0,0 +1,27 @@
+name: Build RTSan Libs
+
+on:
+  push:
+    branches: [ "main" ]
+  pull_request:
+    branches: [ "main" ]
+
+
+jobs:
+  build:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        os: [ubuntu-latest, macos-latest]
+
+    steps:
+    - uses: actions/checkout@v4
+
+    - name: Download and Extract
+      run: make init
+
+    - name: Configure
+      run: make configure
+
+    - name: Build
+      run: make build
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a6c929f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+llvm-project/
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..0a824af
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,50 @@
+LLVM_VERSION := 20.1.1
+LLVM_PROJECT_DIR := llvm-project
+LLVM_PROJECT_TAR := llvm-project-$(LLVM_VERSION).src.tar.xz
+LLVM_PROJECT_URL := https://github.com/llvm/llvm-project/releases/download/llvmorg-$(LLVM_VERSION)/$(LLVM_PROJECT_TAR)
+
+BUILD_DIR := $(LLVM_PROJECT_DIR)/build
+TARGET_OS := $(shell uname | tr '[:upper:]' '[:lower:]')
+TARGET_ARCH := $(shell uname -m)
+NUM_CORES := $(shell nproc 2>/dev/null || sysctl -n hw.ncpu)
+
+.PHONY: all download extract init configure build clean show-lib
+
+all: init configure build show-lib
+
+download:
+	if [ ! -f "$(LLVM_PROJECT_TAR)" ]; then \
+		curl -LO $(LLVM_PROJECT_URL); \
+	fi
+
+extract: download
+	if [ ! -d "$(LLVM_PROJECT_DIR)/.extracted" ]; then \
+		mkdir -p $(LLVM_PROJECT_DIR) && \
+		tar -xf $(LLVM_PROJECT_TAR) --strip-components=1 -C $(LLVM_PROJECT_DIR); \
+		touch $(LLVM_PROJECT_DIR)/.extracted; \
+	fi
+
+init: extract
+	@echo "LLVM project extracted to $(LLVM_PROJECT_DIR)"
+
+configure:
+	@mkdir -p $(BUILD_DIR)
+	cd $(BUILD_DIR) && cmake -G "Unix Makefiles" \
+		-DBUILD_SHARED_LIBS=OFF \
+		-DCMAKE_BUILD_TYPE=Release \
+		-DCOMPILER_RT_BUILD_SANITIZERS=ON \
+		../compiler-rt
+
+build:
+	$(MAKE) -C $(BUILD_DIR) -j$(NUM_CORES) rtsan
+
+clean:
+	rm -rf $(BUILD_DIR)
+
+show-lib:
+	@echo "Built library:"
+ifeq ($(TARGET_OS),linux)
+	@echo "$(BUILD_DIR)/lib/linux/libclang_rt.rtsan-$(TARGET_ARCH).a"
+else
+	@echo "$(BUILD_DIR)/lib/darwin/libclang_rt.rtsan_osx_dynamic.dylib"
+endif


Reviews:
user: login: jcavar
body: Thanks for working on this, I've left a couple of comments. I am not experienced in CMake, but on a basic level, this is looking good to me.

> Name of this repo is rtsan-libs is that ok?

I think I would prefer `rtsan-binaries`, but I am ok with this too.

> Any top level thoughts on if I'm going about this the right or wrong way.

I am wondering if we are reinventing the wheel to some extent with this? LLVM [actions repo](https://github.com/llvm/actions) contains most (all?) of the code required to do this already (e.g. you can check out my release action which uses them).
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-30T15:37:27Z
user: login: cjappl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-30T17:37:04Z
user: login: cjappl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-30T18:06:20Z
user: login: jcavar
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-31T14:49:36Z
user: login: jcavar
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-31T14:52:27Z
user: login: cjappl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-31T17:31:37Z
user: login: cjappl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T21:19:37Z
user: login: jcavar
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-02T05:21:04Z
user: login: davidtrevelyan
body: Looks great! Thanks for your efforts on this, looking forward to seeing where it's going next. 

I agree with @jcavar that `rtsan-binaries` is a slightly more preferable name for the repo, but also I really don't feel strongly about it - `rtsan-libs` is great too. 
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-05T09:49:40Z
user: login: cjappl
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-05T13:55:14Z

Review comments:
diff_hunk: @@ -0,0 +1,27 @@
+name: Build RTSan Libs
path: .github/workflows/makefile.yml
user: login: jcavar
body: Maybe name the file `build_rtsan_libs.yml`
created_at: 2025-03-30T15:19:58Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,27 @@
+name: Build RTSan Libs
+
+on:
+  push:
+    branches: [ "main" ]
+  pull_request:
+    branches: [ "main" ]
path: .github/workflows/makefile.yml
user: login: jcavar
body: Is there a way to specify manual trigger? It seems like that is what we want here.
created_at: 2025-03-30T15:20:22Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,45 @@
+LLVM_PROJECT_DIR := llvm-project
+BUILD_DIR := $(LLVM_PROJECT_DIR)/build
+TARGET_OS := $(shell uname | tr '[:upper:]' '[:lower:]')
+TARGET_ARCH := $(shell uname -m)
+NUM_CORES := $(shell nproc 2>/dev/null || sysctl -n hw.ncpu)
+
+.PHONY: all clone init checkout configure build show-lib clean
+
+all: init checkout configure build show-lib
+
+clone:
path: Makefile
user: login: jcavar
body: Instead of cloning the repo, we can download tagged sources. This is much faster (but also less flexible)
created_at: 2025-03-30T15:33:10Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,27 @@
+name: Build RTSan Libs
+
+on:
+  push:
+    branches: [ "main" ]
+  pull_request:
+    branches: [ "main" ]
path: .github/workflows/makefile.yml
user: login: cjappl
body: My plan was to make this trigger on a specific version tag - but that is after we do the wrapping and publishing of the artifacts.

In the meantime you can always do manual running of actions from the "actions" tab in github
created_at: 2025-03-30T17:37:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,45 @@
+LLVM_PROJECT_DIR := llvm-project
+BUILD_DIR := $(LLVM_PROJECT_DIR)/build
+TARGET_OS := $(shell uname | tr '[:upper:]' '[:lower:]')
+TARGET_ARCH := $(shell uname -m)
+NUM_CORES := $(shell nproc 2>/dev/null || sysctl -n hw.ncpu)
+
+.PHONY: all clone init checkout configure build show-lib clean
+
+all: init checkout configure build show-lib
+
+clone:
path: Makefile
user: login: cjappl
body: I gave this a try, and it seems like it is a bit slower on my machine at least.

The main issue is that when you download the sources from the release, you have to download the entire repo. This is because the CMake expects there at least to be the top level `cmake` directories available from the stuff we don't use (`clang`, `llvm` etc).

The git method selectively downloads just the `cmake` directories, and `compiler-rt`, instead of everything, which makes the downloading and extracting faster.

Do you have an idea of how we could do this while maintaining the speed?

To check out what I did, see this branch:

https://github.com/cjappl/rtsan-libs/tree/try_download
created_at: 2025-03-30T18:06:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,45 @@
+LLVM_PROJECT_DIR := llvm-project
+BUILD_DIR := $(LLVM_PROJECT_DIR)/build
+TARGET_OS := $(shell uname | tr '[:upper:]' '[:lower:]')
+TARGET_ARCH := $(shell uname -m)
+NUM_CORES := $(shell nproc 2>/dev/null || sysctl -n hw.ncpu)
+
+.PHONY: all clone init checkout configure build show-lib clean
+
+all: init checkout configure build show-lib
+
+clone:
path: Makefile
user: login: jcavar
body: Interesting. Maybe there is something else at play here, but my action using download takes a minute:

https://github.com/realtime-sanitizer/RTSanStandaloneSwift/actions/runs/14124620231/job/39570957031

while clone takes 3 minutes.
created_at: 2025-03-31T14:49:35Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,45 @@
+LLVM_PROJECT_DIR := llvm-project
+BUILD_DIR := $(LLVM_PROJECT_DIR)/build
+TARGET_OS := $(shell uname | tr '[:upper:]' '[:lower:]')
+TARGET_ARCH := $(shell uname -m)
+NUM_CORES := $(shell nproc 2>/dev/null || sysctl -n hw.ncpu)
+
+.PHONY: all clone init checkout configure build show-lib clean
+
+all: init checkout configure build show-lib
+
+clone:
path: Makefile
user: login: cjappl
body: It is entirely possible this is my crappy internet. I will run it on the build bot to compare apples to apples before dismissing it entirely.

I do agree that using the official released sources is nice if the time is in the ballpark, the `git` part of this makefile is definitely more complex than I'd like.

Will report back
created_at: 2025-03-31T17:31:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,45 @@
+LLVM_PROJECT_DIR := llvm-project
+BUILD_DIR := $(LLVM_PROJECT_DIR)/build
+TARGET_OS := $(shell uname | tr '[:upper:]' '[:lower:]')
+TARGET_ARCH := $(shell uname -m)
+NUM_CORES := $(shell nproc 2>/dev/null || sysctl -n hw.ncpu)
+
+.PHONY: all clone init checkout configure build show-lib clean
+
+all: init checkout configure build show-lib
+
+clone:
path: Makefile
user: login: cjappl
body: @jcavar - oh yeah, downloading the sources is WAY faster.

See for yourself here: 

Git clone method - https://github.com/cjappl/rtsan-libs/actions/runs/14206157744
ubuntu - 5 min
macos - 5.5 min

Download method - https://github.com/cjappl/rtsan-libs/actions/runs/14206157744
ubuntu - 0.5 min
macos - 2.5 min

Switched this review to downloading and extracting the sources. Would you please take another look to make sure I didn't mess anything up? Going to re-request a review from you


created_at: 2025-04-01T21:19:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,50 @@
+LLVM_VERSION := 20.1.1
+LLVM_PROJECT_DIR := llvm-project
+LLVM_PROJECT_TAR := llvm-project-$(LLVM_VERSION).src.tar.xz
+LLVM_PROJECT_URL := https://github.com/llvm/llvm-project/releases/download/llvmorg-$(LLVM_VERSION)/$(LLVM_PROJECT_TAR)
+
+BUILD_DIR := $(LLVM_PROJECT_DIR)/build
+TARGET_OS := $(shell uname | tr '[:upper:]' '[:lower:]')
+TARGET_ARCH := $(shell uname -m)
+NUM_CORES := $(shell nproc 2>/dev/null || sysctl -n hw.ncpu)
+
+.PHONY: all download extract init configure build clean show-lib
+
+all: init configure build show-lib
+
+download:
+	if [ ! -f "$(LLVM_PROJECT_TAR)" ]; then \
+		curl -LO $(LLVM_PROJECT_URL); \
+	fi
+
+extract: download
+	if [ ! -d "$(LLVM_PROJECT_DIR)/.extracted" ]; then \
+		mkdir -p $(LLVM_PROJECT_DIR) && \
+		tar -xf $(LLVM_PROJECT_TAR) --strip-components=1 -C $(LLVM_PROJECT_DIR); \
+		touch $(LLVM_PROJECT_DIR)/.extracted; \
+	fi
+
+init: extract
+	@echo "LLVM project extracted to $(LLVM_PROJECT_DIR)"
+
+configure:
+	@mkdir -p $(BUILD_DIR)
+	cd $(BUILD_DIR) && cmake -G "Unix Makefiles" \
+		-DBUILD_SHARED_LIBS=OFF \
+		-DCMAKE_BUILD_TYPE=Release \
+		-DCOMPILER_RT_BUILD_SANITIZERS=ON \
+		../compiler-rt
+
+build:
+	$(MAKE) -C $(BUILD_DIR) -j$(NUM_CORES) rtsan
+
+clean:
+	rm -rf $(BUILD_DIR)
+
+show-lib:
path: Makefile
user: login: davidtrevelyan
body: Extremely minor nit for future use: this is more of an "expected" built library location, rather than a verification of where the library was actually built - I'm guessing this target might not be needed going forwards but thought I'd flag just in case
created_at: 2025-04-05T09:47:43Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,50 @@
+LLVM_VERSION := 20.1.1
+LLVM_PROJECT_DIR := llvm-project
+LLVM_PROJECT_TAR := llvm-project-$(LLVM_VERSION).src.tar.xz
+LLVM_PROJECT_URL := https://github.com/llvm/llvm-project/releases/download/llvmorg-$(LLVM_VERSION)/$(LLVM_PROJECT_TAR)
+
+BUILD_DIR := $(LLVM_PROJECT_DIR)/build
+TARGET_OS := $(shell uname | tr '[:upper:]' '[:lower:]')
+TARGET_ARCH := $(shell uname -m)
+NUM_CORES := $(shell nproc 2>/dev/null || sysctl -n hw.ncpu)
+
+.PHONY: all download extract init configure build clean show-lib
+
+all: init configure build show-lib
+
+download:
+	if [ ! -f "$(LLVM_PROJECT_TAR)" ]; then \
+		curl -LO $(LLVM_PROJECT_URL); \
+	fi
+
+extract: download
+	if [ ! -d "$(LLVM_PROJECT_DIR)/.extracted" ]; then \
+		mkdir -p $(LLVM_PROJECT_DIR) && \
+		tar -xf $(LLVM_PROJECT_TAR) --strip-components=1 -C $(LLVM_PROJECT_DIR); \
+		touch $(LLVM_PROJECT_DIR)/.extracted; \
+	fi
+
+init: extract
+	@echo "LLVM project extracted to $(LLVM_PROJECT_DIR)"
+
+configure:
+	@mkdir -p $(BUILD_DIR)
+	cd $(BUILD_DIR) && cmake -G "Unix Makefiles" \
+		-DBUILD_SHARED_LIBS=OFF \
+		-DCMAKE_BUILD_TYPE=Release \
+		-DCOMPILER_RT_BUILD_SANITIZERS=ON \
+		../compiler-rt
+
+build:
+	$(MAKE) -C $(BUILD_DIR) -j$(NUM_CORES) rtsan
+
+clean:
+	rm -rf $(BUILD_DIR)
+
+show-lib:
path: Makefile
user: login: cjappl
body: Yep! Working on a PR currently that actually tests that these exist and does other small verification steps. This was just a sanity/helper for now and will be removed
created_at: 2025-04-05T13:55:14Z
author_association: CONTRIBUTOR

PR comments:
user: login: cjappl
created_at: 2025-03-29T13:51:47Z
body: @steckes @jcavar

Pinging you manually for the review until you accept the maintainer invite I just sent over.
author_association: CONTRIBUTOR
user: login: cjappl
created_at: 2025-03-30T18:11:12Z
body: > I am wondering if we are reinventing the wheel to some extent with this? LLVM [actions repo](https://github.com/llvm/actions) contains most (all?) of the code required to do this already (e.g. you can check out my release action which uses them).

I poked around this for a bit and thought that re-inventing the wheel was OK here for a couple reasons. I am completely open to conversation/debate about if this is right.

1. We only want to download and build one small subsection of the project
2. We want to have the ability to to test this out locally and make tweaks. 

On the second point, what we need to do is so simple that it was not overly laborious to build this infrastructure. This allows us to test things locally, only build our own stuff etc. In the past I have had a hard time debugging when things go wrong with a third party action. Sometimes they are necessary, but I try to avoid them where I can to ensure I can debug/reproduce/have the knobs to tweak when we need to.

author_association: CONTRIBUTOR
user: login: jcavar
created_at: 2025-03-31T14:51:10Z
body: > I poked around this for a bit and thought that re-inventing the wheel was OK here for a couple reasons. I am completely open to conversation/debate about if this is right.

> We only want to download and build one small subsection of the project
> We want to have the ability to to test this out locally and make tweaks.
> On the second point, what we need to do is so simple that it was not overly laborious to build this infrastructure. This allows us to test things locally, only build our own stuff etc. In the past I have had a hard time debugging when things go wrong with a third party action. Sometimes they are necessary, but I try to avoid them where I can to ensure I can debug/reproduce/have the knobs to tweak when we need to.

That makes a lot of sense, I agree with you.
author_association: COLLABORATOR
user: login: steckes
created_at: 2025-04-02T07:43:54Z
body: Great, can't wait to integrate it into the Rust library as an option!
Did you try to git clone only the relevant source code, like I did in the build.rs file?

```rust
// Clone llvm-project into the temporary directory
run_command(
    "git",
    &[
        "clone",
        "-n",
        "--depth=1",
        "--filter=tree:0",
        "--branch",
        "llvmorg-20.1.0",
        "https://github.com/llvm/llvm-project.git",
        llvm_project_dir.to_str().unwrap(),
    ],
    ".",
);

// Perform sparse checkout
run_command(
    "git",
    &[
        "sparse-checkout",
        "set",
        "--no-cone",
        "compiler-rt",
        "cmake",
    ],
    llvm_project_dir.to_str().unwrap(),
);
run_command("git", &["checkout"], llvm_project_dir.to_str().unwrap());
```
author_association: COLLABORATOR
user: login: steckes
created_at: 2025-04-02T08:29:14Z
body: Yeah sorry I found the old Makefile now and see that you did exactly this!
author_association: COLLABORATOR
user: login: cjappl
created_at: 2025-04-02T11:35:46Z
body: > Yeah sorry I found the old Makefile now and see that you did exactly this!

Yep! Thanks for that template, the original version of this PR was directly copied from your code and make-ified. 

Discovered it was significantly faster to download the released source so I went in that direction.
author_association: CONTRIBUTOR
user: login: cjappl
created_at: 2025-04-02T17:19:16Z
body: I'm going to submit as we have general support here. In the coming days I'll put up some more PRs to round out this process and hopefully get some official binaries up
author_association: CONTRIBUTOR
user: login: cjappl
created_at: 2025-04-05T13:54:18Z
body: I prefer libs or librarys as the suffix, but I'm happy to be overruled! 

Reasoning below:

To me what this repo contains is libraries (aka what you would find in /lib of a unix system). These are things that you would link into an executable binary but do not have the ability to run on their own.

This is in contrast with binaries (/bin in unix) which contain a main function and can be executed on their own!

I agree that in theory these both contain binary machine code etc, but in my experience the executable-ness is always what has differentiated a library from a binary in parlance. (E.g. i would expect someone to call libc++ a library, and clang a binary)

Do we expect this repo to grow to contain executable binaries as well as libraries? Or other pieces of data that are not specifically libraries but some other collection of bits?

Also possible this is a failure of the American education system and I have learned it all wrong ;)


author_association: CONTRIBUTOR
user: login: davidtrevelyan
created_at: 2025-04-05T18:44:31Z
body: > I prefer libs or librarys as the suffix, but I'm happy to be overruled!
> 
> Reasoning below:
> 
> To me what this repo contains is libraries (aka what you would find in /lib of a unix system). These are things that you would link into an executable binary but do not have the ability to run on their own.
> 
> This is in contrast with binaries (/bin in unix) which contain a main function and can be executed on their own!
> 
> I agree that in theory these both contain binary machine code etc, but in my experience the executable-ness is always what has differentiated a library from a binary in parlance. (E.g. i would expect someone to call libc++ a library, and clang a binary)
> 
> Do we expect this repo to grow to contain executable binaries as well as libraries? Or other pieces of data that are not specifically libraries but some other collection of bits?
> 
> Also possible this is a failure of the American education system and I have learned it all wrong ;)

That's all good from my perspective, I'm totally happy with `rtsan-libs`. I think there is ambiguity in the both the word "binary" and in the word "library", which can mean something like "code" - and can only mean code in the case of a header-only "library". It doesn't matter to me though - and I don't expect the repo to grow to contain executable binaries - so let's stick with `rtsan-libs` - this is a great name already. 
author_association: COLLABORATOR