Skip to content

Commit

Permalink
feat: c++ bindings (#41)
Browse files Browse the repository at this point in the history
* feat: c++ bindings for resolvo

* fix: redefinition errors
  • Loading branch information
baszalmstra authored Jun 10, 2024
1 parent c41d0df commit 10d75e9
Show file tree
Hide file tree
Showing 27 changed files with 5,419 additions and 11 deletions.
5 changes: 5 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
BasedOnStyle: Google
IndentWidth: 4
IndentAccessModifiers: false
AccessModifierOffset: -4
ColumnLimit: 100
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# GitHub syntax highlighting
pixi.lock linguist-language=YAML

47 changes: 47 additions & 0 deletions .github/workflows/cpp-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
on:
push:
branches:
- "main"
pull_request:

name: C++

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
format:
name: Format and Lint
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@v4

- uses: prefix-dev/setup-pixi@v0.8.1
with:
environments: format-cpp

- name: Ensure code is properly formatted
run: |
pixi run format-cpp
git diff --exit-code
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04, macOS-latest, macOS-14, windows-latest ]
runs-on: ${{ matrix.os }}
needs: [ format ]
steps:
- name: Checkout source code
uses: actions/checkout@v4

- uses: prefix-dev/setup-pixi@v0.8.1
with:
environments: test-cpp

- name: Run the tests
run: |
pixi run test-cpp
2 changes: 1 addition & 1 deletion .github/workflows/rust-compile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
run: cargo clippy

build:
name: ubuntu-latest
name: Test
runs-on: ubuntu-latest
needs: [ format_and_lint ]
steps:
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,9 @@ Cargo.lock
*.pdb

**/.DS_Store

build/
# pixi environments
.pixi
*.egg-info

34 changes: 34 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
cmake_minimum_required(VERSION 3.21)

project(resolvo LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include(FeatureSummary)

option(RESOLVO_BUILD_TESTING "Build tests" OFF)
add_feature_info(RESOLVO_BUILD_TESTING RESOLVO_BUILD_TESTING "configure whether to build the test suite")
include(CTest)

set(RESOLVO_IS_TOPLEVEL_BUILD TRUE)

# Place all compiled examples into the same bin directory
# on Windows, where we'll also put the dll
if (WIN32)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin/debug)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin/release)
elseif(APPLE)
# On macOS, the resolvo_cpp.dylib's install_name uses @rpath. CMake doesn't
# set BUILD_RPATH for imported targets though, so include the directory here
# by hand in the rpath used to build binaries in the build tree (such as our
# examples or tests).
set(CMAKE_BUILD_RPATH ${CMAKE_BINARY_DIR}/cpp)
endif()

add_subdirectory(cpp/)

feature_summary(WHAT ENABLED_FEATURES DESCRIPTION "Enabled features:")
feature_summary(WHAT DISABLED_FEATURES DESCRIPTION "Disabled features:")
24 changes: 18 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
[workspace]
members = ["tools/solve-snapshot"]
members = ["cpp", "tools/*"]
resolver = "2"

[package]
[workspace.package]
name = "resolvo"
version = "0.6.1"
authors = ["Adolfo Ochagavía <github@adolfo.ochagavia.nl>", "Bas Zalmstra <zalmstra.bas@gmail.com>", "Tim de Jager <tdejager89@gmail.com>"]
description = "Fast package resolver written in Rust (CDCL based SAT solving)"
keywords = ["dependency", "solver", "version"]
categories = ["algorithms"]
homepage = "https://github.com/mamba-org/resolvo"
repository = "https://github.com/mamba-org/resolvo"
license = "BSD-3-Clause"
edition = "2021"
readme = "README.md"
resolver = "2"
keywords = ["dependency", "solver", "version"]
categories= ["algorithms"]

[package]
name = "resolvo"
version.workspace = true
authors.workspace = true
description= "Fast package resolver written in Rust (CDCL based SAT solving)"
keywords.workspace = true
categories.workspace = true
homepage.workspace = true
repository.workspace = true
license.workspace = true
edition.workspace = true
readme.workspace = true

[dependencies]
ahash = "0.8.11"
Expand Down
86 changes: 86 additions & 0 deletions cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
cmake_minimum_required(VERSION 3.21)

# Select C++ and C as languages, as Corrosion needs ${CMAKE_C_COMPILER}
# for linking
project(Resolvo LANGUAGES C CXX VERSION 0.1.0)

# Add the Corrosion dependency (used to build Rust code)
include(FetchContent)
FetchContent_Declare(
Corrosion
GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git
GIT_TAG v0.4.9
)
FetchContent_MakeAvailable(Corrosion)

# Add the Corrosion CMake module path to the list of paths to search for modules
list(PREPEND CMAKE_MODULE_PATH ${Corrosion_SOURCE_DIR}/cmake)
find_package(Rust 1.75 REQUIRED MODULE)

option(BUILD_SHARED_LIBS "Build Resolvo as shared library" ON)

set(RESOLVO_LIBRARY_CARGO_FLAGS "" CACHE STRING
"Flags to pass to cargo when building the Resolvo runtime library")

if(BUILD_SHARED_LIBS)
set(rustc_lib_type "cdylib")
set(resolvo_cpp_impl "resolvo_cpp-shared")
set(cmake_lib_type "SHARED")
else()
set(rustc_lib_type "staticlib")
set(resolvo_cpp_impl "resolvo_cpp-static")
set(cmake_lib_type "STATIC")
endif()

corrosion_import_crate(MANIFEST_PATH "${CMAKE_CURRENT_SOURCE_DIR}/Cargo.toml"
CRATES resolvo_cpp CRATE_TYPES bin ${rustc_lib_type})

add_library(Resolvo INTERFACE)
add_library(Resolvo::Resolvo ALIAS Resolvo)
target_link_libraries(Resolvo INTERFACE resolvo_cpp)

set_property(
TARGET resolvo_cpp
APPEND
PROPERTY CORROSION_ENVIRONMENT_VARIABLES
"RESOLVO_GENERATED_INCLUDE_DIR=${CMAKE_CURRENT_BINARY_DIR}/generated_include/"
)

file(GLOB api_headers RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/include/"
"${CMAKE_CURRENT_SOURCE_DIR}/include/*.h")

foreach(header IN LISTS api_headers)
set_property(TARGET Resolvo APPEND PROPERTY PUBLIC_HEADER include/${header})
endforeach()

set(generated_headers
${CMAKE_CURRENT_BINARY_DIR}/generated_include/resolvo_vector_internal.h
${CMAKE_CURRENT_BINARY_DIR}/generated_include/resolvo_string_internal.h
${CMAKE_CURRENT_BINARY_DIR}/generated_include/resolvo_internal.h
)

foreach(header IN LISTS generated_headers)
set_property(TARGET Resolvo APPEND PROPERTY PUBLIC_HEADER ${header})
endforeach()

target_include_directories(Resolvo INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/generated_include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include/resolvo>
)

export(TARGETS Resolvo resolvo_cpp
NAMESPACE Resolvo:: FILE "${CMAKE_BINARY_DIR}/lib/cmake/Resolvo/ResolvoTargets.cmake")
install(EXPORT ResolvoTargets NAMESPACE Resolvo:: DESTINATION lib/cmake/Resolvo)
install(TARGETS Resolvo resolvo_cpp
EXPORT ResolvoTargets LIBRARY DESTINATION lib PUBLIC_HEADER DESTINATION include/resolvo)

install(FILES $<TARGET_FILE:${resolvo_cpp_impl}> TYPE LIB)

if(WIN32)
install(FILES $<TARGET_LINKER_FILE:${resolvo_cpp_impl}> TYPE LIB)
endif()

if(RESOLVO_BUILD_TESTING)
add_subdirectory(tests)
endif()
24 changes: 24 additions & 0 deletions cpp/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "resolvo_cpp"
description = "Resolvo C++ integration"
version.workspace = true
authors.workspace = true
keywords.workspace = true
categories.workspace = true
homepage.workspace = true
repository.workspace = true
license.workspace = true
edition.workspace = true
readme.workspace = true
publish = false

[lib]
crate-type = ["lib", "cdylib", "staticlib"]

[dependencies]
resolvo = { path = "../" }

[build-dependencies]
anyhow = "1"
cbindgen = "0.26.0"

92 changes: 92 additions & 0 deletions cpp/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use std::path::{Path, PathBuf};

use anyhow::Context;

fn main() -> anyhow::Result<()> {
let manifest_dir = PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap());

println!("cargo:rerun-if-env-changed=RESOLVO_GENERATED_INCLUDE_DIR");
let output_dir = std::env::var_os("RESOLVO_GENERATED_INCLUDE_DIR").unwrap_or_else(|| {
Path::new(&std::env::var_os("OUT_DIR").unwrap())
.join("generated_include")
.into()
});
let output_dir = Path::new(&output_dir);

println!("cargo:GENERATED_INCLUDE_DIR={}", output_dir.display());

std::fs::create_dir_all(output_dir).context("Could not create the include directory")?;

let mut default_config = cbindgen::Config::default();
default_config.macro_expansion.bitflags = true;
default_config.pragma_once = true;
default_config.include_version = true;
default_config.namespaces = Some(vec!["resolvo".into(), "cbindgen_private".into()]);
default_config.line_length = 100;
default_config.tab_width = 4;
default_config.language = cbindgen::Language::Cxx;
default_config.cpp_compat = true;
default_config.documentation = true;
default_config.documentation_style = cbindgen::DocumentationStyle::Doxy;
default_config.structure.associated_constants_in_body = true;
default_config.constant.allow_constexpr = true;
default_config.export.exclude = vec!["Slice".into()];

cbindgen::Builder::new()
.with_config(default_config.clone())
.with_src(manifest_dir.join("src/vector.rs"))
.generate()
.context("Unable to generate bindings for resolvo_vector_internal.h")?
.write_to_file(output_dir.join("resolvo_vector_internal.h"));

let mut string_config = default_config.clone();
string_config.export.exclude = vec!["String".into()];

cbindgen::Builder::new()
.with_config(string_config.clone())
.with_src(manifest_dir.join("src/string.rs"))
.with_after_include("namespace resolvo { struct String; }")
.generate()
.context("Unable to generate bindings for resolvo_string_internal.h")?
.write_to_file(output_dir.join("resolvo_string_internal.h"));

let mut config = default_config.clone();
config.export.exclude.extend(vec![
"Vector".into(),
"resolvo_vector_free".into(),
"resolvo_vector_allocate".into(),
"resolvo_vector_empty".into(),
"String".into(),
"resolvo_string_bytes".into(),
"resolvo_string_drop".into(),
"resolvo_string_clone".into(),
"resolvo_string_from_bytes".into(),
]);
config.export.body.insert(
"Slice".to_owned(),
r"
const T &operator[](int i) const { return ptr[i]; }
/// Note: this doesn't initialize Slice properly, but we need to keep the struct as compatible with C
constexpr Slice() = default;
/// Rust uses a NonNull, so even empty slices shouldn't use nullptr
constexpr Slice(const T *ptr, uintptr_t len) : ptr(ptr ? const_cast<T*>(ptr) : reinterpret_cast<T*>(sizeof(T))), len(len) {}"
.to_owned(),
);

cbindgen::Builder::new()
.with_config(config.clone())
.with_src(manifest_dir.join("src/lib.rs"))
.with_include("resolvo_slice.h")
.with_include("resolvo_vector.h")
.with_include("resolvo_string.h")
.generate()
.context("Unable to generate bindings for resolvo_internal.h")?
.write_to_file(output_dir.join("resolvo_internal.h"));

println!("cargo:rerun-if-changed=src/lib.rs");
println!("cargo:rerun-if-changed=src/slice.rs");
println!("cargo:rerun-if-changed=src/string.rs");
println!("cargo:rerun-if-changed=src/vector.rs");

Ok(())
}
37 changes: 37 additions & 0 deletions cpp/include/resolvo.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#pragma once

#include "resolvo_dependency_provider.h"
#include "resolvo_internal.h"

namespace resolvo {

/**
* Called to solve a package problem.
*
* If the solve was successful, an empty string is returned and selected solvable ids will be
* stored in `result`. If the solve was unsuccesfull an error describing the reason is returned and
* the result vector will be empty.
*/
inline String solve(DependencyProvider &provider, Slice<VersionSetId> requirements,
Slice<VersionSetId> constraints, Vector<SolvableId> &result) {
cbindgen_private::DependencyProvider bridge{
static_cast<void *>(&provider),
private_api::bridge_display_solvable,
private_api::bridge_display_solvable_name,
private_api::bridge_display_merged_solvables,
private_api::bridge_display_name,
private_api::bridge_display_version_set,
private_api::bridge_display_string,
private_api::bridge_version_set_name,
private_api::bridge_solvable_name,
private_api::bridge_get_candidates,
private_api::bridge_sort_candidates,
private_api::bridge_filter_candidates,
private_api::bridge_get_dependencies,
};

String error;
cbindgen_private::resolvo_solve(&bridge, requirements, constraints, &error, &result);
return error;
}
} // namespace resolvo
Loading

0 comments on commit 10d75e9

Please sign in to comment.