Skip to content

Commit

Permalink
Add WIP on musl C++ support
Browse files Browse the repository at this point in the history
  • Loading branch information
autarch committed Oct 5, 2024
1 parent e021eb0 commit c2bae19
Show file tree
Hide file tree
Showing 16 changed files with 334 additions and 4 deletions.
66 changes: 62 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ on:
pull_request:

jobs:
test:
name: Test
test-pure-rust:
name: Test a pure Rust project
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -223,8 +223,8 @@ jobs:
- name: Copy test project to root
shell: bash
run: |
cp -a test-project/* .
rm -fr test-project
cp -a test-projects/pure-rust/* .
rm -fr test-projects
- name: Run both commands
uses: ./
with:
Expand Down Expand Up @@ -271,3 +271,61 @@ jobs:
--expect-cross-version "${{ matrix.platform.expect_cross_version }}" \
${{ matrix.platform.expect_cross }} \
${{ matrix.platform.expect_stripped }}
test-cxx:
name: Test a project with C++
strategy:
fail-fast: false
matrix:
os:
- ubuntu-20.04
- ubuntu-22.04
- ubuntu-24.04
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Copy test project to root
shell: bash
run: |
cp -a test-projects/with-cxx/* .
rm -fr test-projects
- name: Run both commands
uses: ./
with:
command: both
cache-cross-binary: true
target: x86_64-unknown-linux-musl
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run test command
uses: ./
with:
command: test
cache-cross-binary: true
target: x86_64-unknown-linux-musl
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run build command
uses: ./
with:
command: build
cache-cross-binary: true
target: x86_64-unknown-linux-musl
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
strip: true
- name: Run build command for subdir
uses: ./
with:
command: build
cache-cross-binary: true
working-directory: subcrate
target: x86_64-unknown-linux-musl
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
strip: true
- name: Check binary and cross on ${{ matrix.os }}
shell: bash
run: |
tests/check-binary.pl \
--target "x86_64-unknown-linux-musl" \
--expect-file-re "ELF.+x86-64" \
--no-expect-cross \
--expect-stripped
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ runs:
shell: bash
run: sudo apt-get update --yes && sudo apt-get install --yes musl-tools
if: steps.determine-cross-compile.outputs.needs-cross != 'true' && contains(inputs.target, 'musl')
- name: Set up system for C++ with musl
shell: bash
run: musl-symlink.sh
if: steps.determine-cross-compile.outputs.needs-cross != 'true' && contains(inputs.target, 'musl')
- name: Set build command
id: set-build-command
shell: bash
Expand Down
86 changes: 86 additions & 0 deletions musl-symlink.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/usr/bin/env bash

# This script was copied from the https://github.com/cross-rs/cross project at commit
# ac4c11cedc97cd7c27faed36e55377a90e6ed618. The license for that project in its `Cargo.toml` file is
# "MIT OR Apache-2.0". The copyright in the MIT license says:
#
# Copyright (c) 2017-2022 by the respective authors
# Copyright (c) 2016 Jorge Aparicio
#
# The script was adjusted to get rid of the sysroot and arch parameters in favor of hardcoding the
# paths for Ubuntu 22.04 and the x86-64 arch.

# Create necessary symlinks for musl images to run
# dynamically-linked binaries.
# Just to be careful, we need this in a few locations,
# relative to the musl sysroot.
# /lib/ld-musl-armhf.so
# /lib/ld-musl-armhf.so.1
# /usr/lib/ld.so
# /usr/lib/ld.so.1
# /usr/lib/libc.so
# /usr/lib/libc.so.1

set -x
set -euo pipefail

main() {
apt-get --yes install libboost-dev

local src
local dst
local dsts
local libstdcpp_path
local libstdcpp_basename

# ignore any failures here
src="/usr/lib/x86_64-linux-musl/libc.so"
dsts=(
"/usr/lib/ld-musl-x86_64.so"
"/usr/lib/ld-musl-x86_64.so.1"
"/usr/lib/ld.so"
"/usr/lib/ld.so.6"
"/usr/lib/libc.so"
"/usr/lib/libc.so.6"
)
for dst in "${dsts[@]}"; do
# force a link if the dst does not exist or is broken
if [[ -L ${dst} ]] && [[ ! -e ${dst} ]]; then
ln -sf "${src}" "${dst}"
elif [[ ! -f ${dst} ]]; then
ln -s "${src}" "${dst}"
fi
done

libstdcpp_path=$(find /usr/lib/x86_64-linux-gnu/ -name 'libstdc++.so.6.0.*')
libstdcpp_basename=$(basename "$libstdcpp_path")

# ensure we statically link libstdc++, so avoid segfaults with c++
# https://github.com/cross-rs/cross/issues/902
find /usr -name 'libstdc++.so*' -exec rm -f {} \;

# now, we create a linker script that adds all the required dependencies
# because we link to a static libstdc++ to avoid runtime issues and
# with the shared libstdc++, we can have missing symbols that are referenced
# in libstdc++, such as those from libc like `setlocale` and `__cxa_atexit`,
# as well as those from libgcc, like `__extendsftf2`. all musl targets
# can require symbols from libc, however, only the following are known
# to require symbols from libgcc:
# - aarch64-unknown-linux-musl
# - mips64-unknown-linux-muslabi64
# - mips64el-unknown-linux-muslabi64
echo '/* cross-rs linker script
* this allows us to statically link libstdc++ to avoid segfaults
* https://github.com/cross-rs/cross/issues/902
*/
GROUP ( libstdc++.a AS_NEEDED( -lgcc -lc -lm ) )
' >"$libstdcpp_path"
ln -s "$libstdcpp_basename" /usr/lib/x86_64-linux-gnu/libstdc++.so.6
ln -s "$libstdcpp_basename" /usr/lib/x86_64-linux-gnu/libstdc++.so

echo /usr/lib/x86_64-linux-gnu >>/etc/ld-musl-x86_64.path

ln -s /usr/bin/musl-gcc /usr/bin/musl-g++
}

main
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
15 changes: 15 additions & 0 deletions test-projects/with-cxx/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "demo"
version = "0.0.0"
authors = ["David Tolnay <dtolnay@gmail.com>"]
description = "Toy project from https://github.com/dtolnay/cxx"
edition = "2021"
license = "MIT OR Apache-2.0"
publish = false
repository = "https://github.com/dtolnay/cxx"

[dependencies]
cxx = "1.0"

[build-dependencies]
cxx-build = "1.0"
1 change: 1 addition & 0 deletions test-projects/with-cxx/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This was copied from https://github.com/dtolnay/cxx/tree/master/demo
10 changes: 10 additions & 0 deletions test-projects/with-cxx/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
fn main() {
cxx_build::bridge("src/main.rs")
.file("src/blobstore.cc")
.std("c++14")
.compile("cxxbridge-demo");

println!("cargo:rerun-if-changed=src/main.rs");
println!("cargo:rerun-if-changed=src/blobstore.cc");
println!("cargo:rerun-if-changed=include/blobstore.h");
}
26 changes: 26 additions & 0 deletions test-projects/with-cxx/include/blobstore.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#pragma once
#include "rust/cxx.h"
#include <memory>

namespace org {
namespace blobstore {

struct MultiBuf;
struct BlobMetadata;

class BlobstoreClient {
public:
BlobstoreClient();
uint64_t put(MultiBuf &buf) const;
void tag(uint64_t blobid, rust::Str tag) const;
BlobMetadata metadata(uint64_t blobid) const;

private:
class impl;
std::shared_ptr<impl> impl;
};

std::unique_ptr<BlobstoreClient> new_blobstore_client();

} // namespace blobstore
} // namespace org
71 changes: 71 additions & 0 deletions test-projects/with-cxx/src/blobstore.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#include "demo/include/blobstore.h"
#include "demo/src/main.rs.h"
#include <algorithm>
#include <functional>
#include <set>
#include <string>
#include <unordered_map>

namespace org {
namespace blobstore {

// Toy implementation of an in-memory blobstore.
//
// In reality the implementation of BlobstoreClient could be a large complex C++
// library.
class BlobstoreClient::impl {
friend BlobstoreClient;
using Blob = struct {
std::string data;
std::set<std::string> tags;
};
std::unordered_map<uint64_t, Blob> blobs;
};

BlobstoreClient::BlobstoreClient() : impl(new class BlobstoreClient::impl) {}

// Upload a new blob and return a blobid that serves as a handle to the blob.
uint64_t BlobstoreClient::put(MultiBuf &buf) const {
std::string contents;

// Traverse the caller's chunk iterator.
//
// In reality there might be sophisticated batching of chunks and/or parallel
// upload implemented by the blobstore's C++ client.
while (true) {
auto chunk = next_chunk(buf);
if (chunk.size() == 0) {
break;
}
contents.append(reinterpret_cast<const char *>(chunk.data()), chunk.size());
}

// Insert into map and provide caller the handle.
auto blobid = std::hash<std::string>{}(contents);
impl->blobs[blobid] = {std::move(contents), {}};
return blobid;
}

// Add tag to an existing blob.
void BlobstoreClient::tag(uint64_t blobid, rust::Str tag) const {
impl->blobs[blobid].tags.emplace(tag);
}

// Retrieve metadata about a blob.
BlobMetadata BlobstoreClient::metadata(uint64_t blobid) const {
BlobMetadata metadata{};
auto blob = impl->blobs.find(blobid);
if (blob != impl->blobs.end()) {
metadata.size = blob->second.data.size();
std::for_each(blob->second.tags.cbegin(), blob->second.tags.cend(),
[&](auto &t) { metadata.tags.emplace_back(t); });
}
return metadata;
}

std::unique_ptr<BlobstoreClient> new_blobstore_client() {
return std::make_unique<BlobstoreClient>();
}

} // namespace blobstore
} // namespace org
59 changes: 59 additions & 0 deletions test-projects/with-cxx/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#[cxx::bridge(namespace = "org::blobstore")]
mod ffi {
// Shared structs with fields visible to both languages.
struct BlobMetadata {
size: usize,
tags: Vec<String>,
}

// Rust types and signatures exposed to C++.
extern "Rust" {
type MultiBuf;

fn next_chunk(buf: &mut MultiBuf) -> &[u8];
}

// C++ types and signatures exposed to Rust.
unsafe extern "C++" {
include!("demo/include/blobstore.h");

type BlobstoreClient;

fn new_blobstore_client() -> UniquePtr<BlobstoreClient>;
fn put(&self, parts: &mut MultiBuf) -> u64;
fn tag(&self, blobid: u64, tag: &str);
fn metadata(&self, blobid: u64) -> BlobMetadata;
}
}

// An iterator over contiguous chunks of a discontiguous file object.
//
// Toy implementation uses a Vec<Vec<u8>> but in reality this might be iterating
// over some more complex Rust data structure like a rope, or maybe loading
// chunks lazily from somewhere.
pub struct MultiBuf {
chunks: Vec<Vec<u8>>,
pos: usize,
}
pub fn next_chunk(buf: &mut MultiBuf) -> &[u8] {
let next = buf.chunks.get(buf.pos);
buf.pos += 1;
next.map_or(&[], Vec::as_slice)
}

fn main() {
let client = ffi::new_blobstore_client();

// Upload a blob.
let chunks = vec![b"fearless".to_vec(), b"concurrency".to_vec()];
let mut buf = MultiBuf { chunks, pos: 0 };
let blobid = client.put(&mut buf);
println!("blobid = {}", blobid);

// Add a tag.
client.tag(blobid, "rust");

// Read back the tags.
let metadata = client.metadata(blobid);
println!("tags = {:?}", metadata.tags);
}

0 comments on commit c2bae19

Please sign in to comment.