Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] add submodule for C bindings, library compilation with PackageCompiler #88

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@ benchmark/*
/profile/*
/statprof/*
/debug/*

# Compiled packages
JetReconstructionCompiled
12 changes: 12 additions & 0 deletions compile/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[deps]
ArgParse = "c7e460c6-2fb9-53a9-8c5b-16f535851c63"
JetReconstruction = "44e8cb2c-dfab-4825-9c70-d4808a591196"
PackageCompiler = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d"

[sources]
JetReconstruction = {path = ".."}

[compat]
JetReconstruction = "0.4"
PackageCompiler = "2"
julia = "1.11"
87 changes: 87 additions & 0 deletions compile/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Compiling JetReconstruction.jl to a C-library

Minimal C bindings for JetReconstruction.jl

- [C-header](include/JetReconstruction.h)
- shared library compiled with [PackageCompiler.jl](https://github.com/JuliaLang/PackageCompiler.jl)

## Building library

To build the library, run the following command from the package root directory:

```sh
julia --project=compile compile/build.jl
```

## Usage example

### Example source file

Example usage of C bindings in an application:

```C
#include "JetReconstruction.h"
#include "julia_init.h" /*Should be automatically generated by PackageCompiler.jl and distributed together with the "JetReconstruction.h" header file*/

int main(int argc, char *argv[]) {
init_julia(argc, argv); /*initialization of julia runtime*/

/*Prepare array of pseudo jets*/
size_t particles_len;
jetreconstruction_PseudoJet* particles;
/*Initialize with desired values*/

/*Call jet reconstruction*/
jetreconstruction_JetAlgorithm algorithm = JETRECONSTRUCTION_JETALGORITHM_CA;
double R = 3.0;
jetreconstruction_RecoStrategy strategy = JETRECONSTRUCTION_RECOSTRATEGY_BEST;

jetreconstruction_ClusterSequence cluster_seq;
int ret = jetreconstruction_jet_reconstruct(particles, len, algorithm, R, strategy,
&cluster_seq);
if (ret != JETRECONSTRUCTION_STATUSCODE_OK){
/*An error occurred check the value or stderr for more information*/
return 1;
}

/*Use the cluster sequence in your application
then free memory allocations done by library*/
jetreconstruction_ClusterSequence_free_members(&cluster_seq);
shutdown_julia(0); /*teardown of julia runtime*/
return 0;
}

```

### Example compilation

To build an example application run the following command:

```shell
cc -o jetreconstruction_test compile/test/jetreconstruction_test.c -IJetReconstructionCompiled/include -LJetReconstructionCompiled/lib -ljetreconstruction -ljulia
```

In case the compiled library resides in non-standard location, add its location to `LD_LIBRARY_PATH` when running example application:

```shell
LD_LIBRARY_PATH=JetReconstructionCompiled/lib/:${LD_LIBRARY_PATH} ./jetreconstruction_test
```

### Compilation with CMake

The JetReconstruction library comes with a CMake target `JetReconstruction::JetReconstruction`. Example usage in CMake file:

```cmake
find_package(JetReconstruction REQUIRED)

target_link_libraries(myTarget PUBLIC JetReconstruction::JetReconstruction)
```

## Limitations

Currently it's not possible to create libraries for different platforms - no cross-compilation!

The library is relocatable given the whole installation tree is moved, including libraries in the `lib/julia/` directory.

It's advised to install the library in a separate directory to avoid possible conflicts.
The library must not be installed in the same directory as another Julia package compiled with `PackageCompiler.jl` as they would overwrite the package specific files in `share/julia`.
65 changes: 65 additions & 0 deletions compile/build.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using PackageCompiler
import ArgParse
import JetReconstruction

function parse_args(args)
s = ArgParse.ArgParseSettings()
ArgParse.@add_arg_table s begin
"--source-dir"
help = "Directory containing the source files"
arg_type = String
default = splitdir(@__DIR__) |> first

"--output-dir", "-o"
help = "Directory to save the compiled library"
arg_type = String
default = joinpath(splitdir(@__DIR__) |> first, "JetReconstructionCompiled")
end

return ArgParse.parse_args(args, s)
end

function configure_file(template_path::String, output_path::String,
replacements::Dict{String, String})
template = read(template_path, String)
for (key, value) in replacements
template = replace(template, "@$(key)@" => value)
end
open(output_path, "w") do io
write(io, template)
end
end

function (@main)(args)
parsed_args = parse_args(args)
source_dir = parsed_args["source-dir"]
output_dir = parsed_args["output-dir"]

@info "Compiling package from $source_dir"
@info "Creating library in $output_dir"
PackageCompiler.create_library(source_dir, output_dir;
lib_name = "jetreconstruction",
header_files = [joinpath(@__DIR__, "include",
"JetReconstruction.h")],
precompile_execution_file = [joinpath(@__DIR__,
"precompile_execution.jl")],
incremental = false,
filter_stdlibs = true,
force = true)

cmake_input = joinpath(@__DIR__, "cmake", "JetReconstruction")
cmake_output = joinpath(output_dir, "lib", "cmake", "JetReconstruction")

version = pkgversion(JetReconstruction)
cmake_project_version = "$(version.major).$(version.minor).$(version.patch)"

@info "Copying CMake file to $cmake_output"
mkpath(cmake_output)
cp_file(input_dir, basename, output_dir) = cp(joinpath(input_dir, basename),
joinpath(output_dir, basename))
cp_file(cmake_input, "JetReconstructionConfig.cmake", cmake_output)
cp_file(cmake_input, "JetReconstructionTargets.cmake", cmake_output)
configure_file(joinpath(cmake_input, "JetReconstructionConfigVersion.cmake.in"),
joinpath(cmake_output, "JetReconstructionConfigVersion.cmake"),
Dict("PROJECT_VERSION" => cmake_project_version))
end
39 changes: 39 additions & 0 deletions compile/cmake/JetReconstruction/JetReconstructionConfig.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Config file for the JetReconstruction.jl package
# Manualy adjusted from standard cmake generated config file
get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE)

macro(set_and_check _var _file)
set(${_var} "${_file}")
if(NOT EXISTS "${_file}")
message(FATAL_ERROR "File or directory ${_file} referenced by variable ${_var} does not exist !")
endif()
endmacro()

macro(check_required_components _NAME)
foreach(comp ${${_NAME}_FIND_COMPONENTS})
if(NOT ${_NAME}_${comp}_FOUND)
if(${_NAME}_FIND_REQUIRED_${comp})
set(${_NAME}_FOUND FALSE)
endif()
endif()
endforeach()
endmacro()

####################################################################################

# - Create relocatable paths to headers.
# NOTE: Do not strictly need paths as all usage requirements are encoded in
# the imported targets created later.
set_and_check(JetReconstruction_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include")

# - Create path to installed read-only data files (e.g. yaml files)
set_and_check(JetReconstruction_DATA_DIR "${PACKAGE_PREFIX_DIR}/share/julia")

# - Include the targets file to create the imported targets that a client can
# link to (libraries) or execute (programs)
include("${CMAKE_CURRENT_LIST_DIR}/JetReconstructionTargets.cmake")

# print the default "Found:" message and check library location
include(FindPackageHandleStandardArgs)
get_property(TEST_JETRECONSTRUCTION_LIBRARY TARGET JetReconstruction::JetReconstruction PROPERTY LOCATION)
find_package_handle_standard_args(JetReconstruction DEFAULT_MSG CMAKE_CURRENT_LIST_FILE TEST_JETRECONSTRUCTION_LIBRARY)
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# This is a basic version file for the Config-mode of find_package().
# It is used by write_basic_package_version_file() as input file for configure_file()
# to create a version-file which can be installed along a config.cmake file.
#
# The created file sets PACKAGE_VERSION_EXACT if the current version string and
# the requested version string are exactly the same and it sets
# PACKAGE_VERSION_COMPATIBLE if the current version is >= requested version,
# but only if the requested major version is the same as the current one.
# The variable CVF_VERSION must be set before calling configure_file().


set(PACKAGE_VERSION "@PROJECT_VERSION@")

if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION)
set(PACKAGE_VERSION_COMPATIBLE FALSE)
else()

if(PACKAGE_VERSION MATCHES "^([0-9]+)\\.")
set(CVF_VERSION_MAJOR "${CMAKE_MATCH_1}")
if(NOT CVF_VERSION_MAJOR VERSION_EQUAL 0)
string(REGEX REPLACE "^0+" "" CVF_VERSION_MAJOR "${CVF_VERSION_MAJOR}")
endif()
else()
set(CVF_VERSION_MAJOR ${PACKAGE_VERSION})
endif()

if(PACKAGE_FIND_VERSION_RANGE)
# both endpoints of the range must have the expected major version
math (EXPR CVF_VERSION_MAJOR_NEXT "${CVF_VERSION_MAJOR} + 1")
if (NOT PACKAGE_FIND_VERSION_MIN_MAJOR STREQUAL CVF_VERSION_MAJOR
OR ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND NOT PACKAGE_FIND_VERSION_MAX_MAJOR STREQUAL CVF_VERSION_MAJOR)
OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND NOT PACKAGE_FIND_VERSION_MAX VERSION_LESS_EQUAL CVF_VERSION_MAJOR_NEXT)))
set(PACKAGE_VERSION_COMPATIBLE FALSE)
elseif(PACKAGE_FIND_VERSION_MIN_MAJOR STREQUAL CVF_VERSION_MAJOR
AND ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_LESS_EQUAL PACKAGE_FIND_VERSION_MAX)
OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION_MAX)))
set(PACKAGE_VERSION_COMPATIBLE TRUE)
else()
set(PACKAGE_VERSION_COMPATIBLE FALSE)
endif()
else()
if(PACKAGE_FIND_VERSION_MAJOR STREQUAL CVF_VERSION_MAJOR)
set(PACKAGE_VERSION_COMPATIBLE TRUE)
else()
set(PACKAGE_VERSION_COMPATIBLE FALSE)
endif()

if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION)
set(PACKAGE_VERSION_EXACT TRUE)
endif()
endif()
endif()


# if the installed or the using project don't have CMAKE_SIZEOF_VOID_P set, ignore it:
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "" OR "8" STREQUAL "")
return()
endif()

# check that the installed version has the same 32/64bit-ness as the one which is currently searching:
if(NOT CMAKE_SIZEOF_VOID_P STREQUAL "8")
math(EXPR installedBits "8 * 8")
set(PACKAGE_VERSION "${PACKAGE_VERSION} (${installedBits}bit)")
set(PACKAGE_VERSION_UNSUITABLE TRUE)
endif()
96 changes: 96 additions & 0 deletions compile/cmake/JetReconstruction/JetReconstructionTargets.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Generated by CMake

if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.8)
message(FATAL_ERROR "CMake >= 2.8.0 required")
endif()
if(CMAKE_VERSION VERSION_LESS "3.0.0")
message(FATAL_ERROR "CMake >= 3.0.0 required")
endif()
cmake_policy(PUSH)
cmake_policy(VERSION 3.0.0...3.28)

# Commands may need to know the format version.
set(CMAKE_IMPORT_FILE_VERSION 1)

# Protect against multiple inclusion, which would fail when already imported targets are added once more.
set(_cmake_targets_defined "")
set(_cmake_targets_not_defined "")
set(_cmake_expected_targets "")
foreach(_cmake_expected_target IN ITEMS JetReconstruction::JetReconstruction)
list(APPEND _cmake_expected_targets "${_cmake_expected_target}")
if(TARGET "${_cmake_expected_target}")
list(APPEND _cmake_targets_defined "${_cmake_expected_target}")
else()
list(APPEND _cmake_targets_not_defined "${_cmake_expected_target}")
endif()
endforeach()
unset(_cmake_expected_target)
if(_cmake_targets_defined STREQUAL _cmake_expected_targets)
unset(_cmake_targets_defined)
unset(_cmake_targets_not_defined)
unset(_cmake_expected_targets)
unset(CMAKE_IMPORT_FILE_VERSION)
cmake_policy(POP)
return()
endif()
if(NOT _cmake_targets_defined STREQUAL "")
string(REPLACE ";" ", " _cmake_targets_defined_text "${_cmake_targets_defined}")
string(REPLACE ";" ", " _cmake_targets_not_defined_text "${_cmake_targets_not_defined}")
message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_cmake_targets_defined_text}\nTargets not yet defined: ${_cmake_targets_not_defined_text}\n")
endif()
unset(_cmake_targets_defined)
unset(_cmake_targets_not_defined)
unset(_cmake_expected_targets)


# Compute the installation prefix relative to this file.
get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH)
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
if(_IMPORT_PREFIX STREQUAL "/")
set(_IMPORT_PREFIX "")
endif()

# Create imported target JetReconstruction::JetReconstruction
add_library(JetReconstruction::JetReconstruction SHARED IMPORTED)
set_target_properties(JetReconstruction::JetReconstruction PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
IMPORTED_LOCATION "${_IMPORT_PREFIX}/lib/libjetreconstruction.so"
IMPORTED_SONAME "libjetreconstruction.so"
)

# Cleanup temporary variables.
set(_IMPORT_PREFIX)

# Loop over all imported files and verify that they actually exist
foreach(_cmake_target IN LISTS _cmake_import_check_targets)
if(CMAKE_VERSION VERSION_LESS "3.28"
OR NOT DEFINED _cmake_import_check_xcframework_for_${_cmake_target}
OR NOT IS_DIRECTORY "${_cmake_import_check_xcframework_for_${_cmake_target}}")
foreach(_cmake_file IN LISTS "_cmake_import_check_files_for_${_cmake_target}")
if(NOT EXISTS "${_cmake_file}")
message(FATAL_ERROR "The imported target \"${_cmake_target}\" references the file
\"${_cmake_file}\"
but this file does not exist. Possible reasons include:
* The file was deleted, renamed, or moved to another location.
* An install or uninstall procedure did not complete successfully.
* The installation package was faulty and contained
\"${CMAKE_CURRENT_LIST_FILE}\"
but not all the files it references.
")
endif()
endforeach()
endif()
unset(_cmake_file)
unset("_cmake_import_check_files_for_${_cmake_target}")
endforeach()
unset(_cmake_target)
unset(_cmake_import_check_targets)

# This file does not depend on other imported targets which have
# been exported from the same project but in a separate export set.

# Commands beyond this point should not need to know the version.
set(CMAKE_IMPORT_FILE_VERSION)
cmake_policy(POP)
15 changes: 15 additions & 0 deletions compile/downstream/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.12)

project(
JetReconstruction_downstream
LANGUAGES C
VERSION 0.1.0)

find_package(JetReconstruction 0.4.3 REQUIRED)

add_executable(jetreconstruction_test jetreconstruction_test.c)
target_link_libraries(jetreconstruction_test
PUBLIC JetReconstruction::JetReconstruction)

enable_testing()
add_test(NAME JetReconstructionTest COMMAND jetreconstruction_test)
3 changes: 3 additions & 0 deletions compile/downstream/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Downstream project example

Example downstream project using C-bindings of JetReconstruction.jl
Loading
Loading