Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions c_api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,26 @@ containing MQLib's random-forest model files, usually the upstream `hhdata`
directory. If no model files are found, `mqlib_solve_qubo` returns
`MQLIB_STATUS_HYPERHEURISTIC_DATA_NOT_FOUND`.

## Build Sketch
## BinaryBuilder Recipe

The exact build recipe belongs in `MQLib_jll`, but a local syntax check against
an upstream MQLib checkout looks like:
The `MQLib_jll` build recipe in `jll/build_tarballs.jl` builds the existing
`MQLib` executable product and a shared library product named
`libmqlib_c_api`. The library compiles `c_api/src/mqlib_c_api.cpp` with the
upstream MQLib implementation sources, excluding the upstream executable entry
point (`src/main.cpp`), and installs this header as `mqlib_c_api.h`.

Downstream Julia code should call the library product exported by `MQLib_jll`,
for example:

```julia
ccall((:mqlib_c_abi_version, libmqlib_c_api), Cint, ())
```

The public ABI is versioned by `MQLIB_C_ABI_VERSION`. Any incompatible change
to the structs, status codes, or exported functions must bump that value and be
released through a new `MQLib_jll` build.

A local syntax check against an upstream MQLib checkout looks like:

```sh
c++ -std=c++11 \
Expand All @@ -52,11 +68,6 @@ c++ -std=c++11 \
c_api/src/mqlib_c_api.cpp
```

When building the shared library, compile `c_api/src/mqlib_c_api.cpp` together
with the upstream MQLib implementation sources needed by the heuristics. The
upstream executable entry point (`src/main.cpp`) should not be part of the
shared-library target.

The small native example in `c_api/examples/solve_qubo.c` demonstrates both an
explicit QUBO heuristic and the hyperheuristic call shape:

Expand Down
100 changes: 100 additions & 0 deletions jll/build_tarballs.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Note that this script can accept some limited command-line arguments. Run
# `julia build_tarballs.jl --help` to see a usage message.
using BinaryBuilder, Pkg

name = "MQLib"
version = v"0.1.1"

# Collection of sources required to complete build.
sources = [
GitSource(
"https://github.com/MQLib/MQLib.git",
"585496274af5abb0849d0d47e135496b4688680b",
),
# Provides the C ABI wrapper that is packaged as libmqlib_c_api.
GitSource(
"https://github.com/JuliaQUBO/MQLib.jl.git",
"8b491876ef44b663e8fd04a48d262d72e73faff7",
),
]

# Bash recipe for building across all platforms.
script = raw"""
cd "${WORKSPACE}/srcdir"

MQLIB_SRC="${WORKSPACE}/srcdir/MQLib"
MQLIB_C_HEADER="$(find "${WORKSPACE}/srcdir" -path '*/c_api/include/mqlib_c_api.h' -print -quit)"
MQLIB_JL_SRC="${MQLIB_C_HEADER%/c_api/include/mqlib_c_api.h}"

if [[ ! -f "${MQLIB_JL_SRC}/c_api/src/mqlib_c_api.cpp" ]]; then
echo "Could not find MQLib.jl C ABI sources" >&2
exit 1
fi

cd "${MQLIB_SRC}"
make -j${nproc}
install -Dvm 0755 bin/MQLib "${bindir}/MQLib${exeext}"
install -Dvm 0644 \
"${MQLIB_JL_SRC}/c_api/include/mqlib_c_api.h" \
"${includedir}/mqlib_c_api.h"

mapfile -t MQLIB_LIBRARY_SOURCES < <(find src -name '*.cpp' ! -name main.cpp | sort)

if [[ "${target}" == *-mingw* ]]; then
MQLIB_C_API_LIBRARY="${bindir}/libmqlib_c_api.${dlext}"
MQLIB_C_API_SHARED_FLAGS=(
-shared
-Wl,--out-implib,"${libdir}/libmqlib_c_api.dll.a"
)
elif [[ "${target}" == *-apple-darwin* ]]; then
MQLIB_C_API_LIBRARY="${libdir}/libmqlib_c_api.${dlext}"
MQLIB_C_API_SHARED_FLAGS=(
-dynamiclib
-Wl,-install_name,@rpath/libmqlib_c_api.${dlext}
)
else
MQLIB_C_API_LIBRARY="${libdir}/libmqlib_c_api.${dlext}"
MQLIB_C_API_SHARED_FLAGS=(-shared)
fi

"${CXX}" \
-std=c++11 \
-O2 \
-fPIC \
-DMQLIB_C_BUILD_SHARED \
-Iinclude \
-I"${MQLIB_JL_SRC}/c_api/include" \
"${MQLIB_LIBRARY_SOURCES[@]}" \
"${MQLIB_JL_SRC}/c_api/src/mqlib_c_api.cpp" \
"${MQLIB_C_API_SHARED_FLAGS[@]}" \
-o "${MQLIB_C_API_LIBRARY}" \
-lm
"""

# These are the platforms we will build for by default, unless further
# platforms are passed in on the command line.
platforms = expand_cxxstring_abis(supported_platforms())

# The products that we will ensure are always built.
products = [
ExecutableProduct("MQLib", :MQLib),
LibraryProduct("libmqlib_c_api", :libmqlib_c_api),
]

# Dependencies that must be installed before this package can be built.
dependencies = [
Dependency("CompilerSupportLibraries_jll"; platforms = filter(!Sys.isapple, platforms)),
]

# Build the tarballs, and possibly a `build.jl` as well.
build_tarballs(
ARGS,
name,
version,
sources,
script,
platforms,
products,
dependencies;
julia_compat = "1.6",
)
15 changes: 15 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ Test.@testset "C ABI contract" begin
Test.@test isfile(header)
Test.@test isfile(source)

recipe = joinpath(root, "jll", "build_tarballs.jl")
Test.@test isfile(recipe)

header_text = read(header, String)
for symbol in (
"MQLIB_C_ABI_VERSION",
Expand Down Expand Up @@ -80,6 +83,18 @@ Test.@testset "C ABI contract" begin
end
end

recipe_text = read(recipe, String)
for snippet in (
"ExecutableProduct(\"MQLib\", :MQLib)",
"LibraryProduct(\"libmqlib_c_api\", :libmqlib_c_api)",
"c_api/include/mqlib_c_api.h",
"c_api/src/mqlib_c_api.cpp",
"! -name main.cpp",
"-DMQLIB_C_BUILD_SHARED",
)
Test.@test occursin(snippet, recipe_text)
end

upstream_dir = get(ENV, "MQLIB_UPSTREAM_DIR", "")
cxx = first_available_tool(["c++", "g++", "clang++"])
if isempty(upstream_dir) || !isdir(joinpath(upstream_dir, "include"))
Expand Down
Loading