1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
|
{
apple-sdk,
cling,
cmake,
fetchFromGitHub,
gcc-unwrapped,
lib,
libffi,
libxml2,
llvmPackages_21,
ncurses,
ninja,
python3,
zlib,
zstd,
# tests
gtest,
# Which interpreter backend to build against. CppInterOp can use either
# clang-repl (from upstream LLVM/Clang) or Cling. They are mutually exclusive.
backend ? "clang-repl", # "clang-repl" | "cling"
}:
let
llvmPackages = llvmPackages_21;
inherit (llvmPackages) stdenv;
useCling = backend == "cling";
llvm = llvmPackages.llvm;
clang = llvmPackages.clang-unwrapped;
# For the cling backend we build against the LLVM/Clang/Cling that ship inside
# `cling` itself (its LLVM 20 fork), so the ABI matches libcling. The CMake
# config packages (LLVM, Clang, Cling) all live under cling.unwrapped.
clingRoot = cling.unwrapped;
# The Clang resource dir and standard-library include flags the JIT interpreter
# needs, since there is no system compiler to probe in the Nix sandbox. Both
# this package's own tests and xeus-cpp (via passthru) feed these to CppInterOp
# through CPPINTEROP_EXTRA_INTERPRETER_ARGS. The resource dir must match the
# Clang that CppInterOp was built against: the cling fork for the cling backend,
# upstream LLVM otherwise. -nostdinc(++) makes the search hermetic: only the
# -isystem paths below are used, never any stray host include dirs.
resourceDir =
if useCling then
"${clingRoot}/lib/clang/20"
else
"${lib.getLib clang}/lib/clang/${lib.versions.major llvm.version}";
# These must precede the resource dir, because libc++ ships its own <stddef.h>
# that include_next's Clang's and errors out if reached second.
cxxIncludeArgs =
if stdenv.hostPlatform.isDarwin then
[
"-isystem"
"${lib.getDev llvmPackages.libcxx}/include/c++/v1"
]
else
[
"-isystem"
"${gcc-unwrapped}/include/c++/${gcc-unwrapped.version}"
"-isystem"
"${gcc-unwrapped}/include/c++/${gcc-unwrapped.version}/${stdenv.hostPlatform.config}"
];
libcIncludeArgs =
if stdenv.hostPlatform.isDarwin then
[
"-isystem"
"${apple-sdk.sdkroot}/usr/include"
"-iframework"
"${apple-sdk.sdkroot}/System/Library/Frameworks"
]
else
[
"-isystem"
"${lib.getDev stdenv.cc.libc}/include"
];
interpreterArgs = [
"-nostdinc"
"-nostdinc++"
"-resource-dir"
resourceDir
]
++ cxxIncludeArgs
++ [
"-isystem"
"${resourceDir}/include"
]
++ libcIncludeArgs;
in
assert lib.assertOneOf "backend" backend [
"clang-repl"
"cling"
];
stdenv.mkDerivation (finalAttrs: {
pname = "cpp-interop-${backend}";
version = "1.9.0";
src = fetchFromGitHub {
owner = "compiler-research";
repo = "CppInterOp";
tag = "v${finalAttrs.version}";
hash = "sha256-am2WObER9dlNQU/VMTY2ScMe/w8c4N8m/DVyNwHiBnw=";
};
strictDeps = true;
__structuredAttrs = true;
nativeBuildInputs = [
cmake
ninja
python3
];
buildInputs = [
libffi
libxml2
ncurses
zlib
zstd
]
++ (
if useCling then
[ clingRoot ]
else
[
llvm
clang
]
);
# Upstream's unittests/CMakeLists.txt only fetches GoogleTest over the network
# (forbidden in the sandbox) when no gtest target exists; point it at the
# nixpkgs gtest instead so the tests can build offline.
postPatch = ''
substituteInPlace unittests/CMakeLists.txt \
--replace-fail "include(GoogleTest)" "find_package(GTest REQUIRED)"
'';
cmakeFlags = [
(lib.cmakeBool "BUILD_SHARED_LIBS" true)
(lib.cmakeBool "CPPINTEROP_USE_CLING" useCling)
(lib.cmakeBool "CPPINTEROP_USE_REPL" (!useCling))
(lib.cmakeBool "CPPINTEROP_ENABLE_TESTING" finalAttrs.finalPackage.doCheck)
]
++ (
if useCling then
[
"-DCling_DIR=${clingRoot}/lib/cmake/cling"
"-DLLVM_DIR=${clingRoot}/lib/cmake/llvm"
"-DClang_DIR=${clingRoot}/lib/cmake/clang"
]
else
[
"-DLLVM_DIR=${llvm.dev}/lib/cmake/llvm"
"-DClang_DIR=${clang.dev}/lib/cmake/clang"
]
);
# Run the upstream GoogleTest suite. Only the clang-repl backend is exercised;
# the Cling backend skips many of these tests upstream.
doCheck = !useCling;
checkInputs = [ gtest ];
checkPhase = ''
runHook preCheck
export CPPINTEROP_EXTRA_INTERPRETER_ARGS="${lib.concatStringsSep " " interpreterArgs}"
# Upstream registers the tests only in the unittests subdir; its
# check-cppinterop target builds them and runs ctest from the right place.
ninja check-cppinterop
runHook postCheck
'';
# Smoke test: drive the backend to JIT-compile and run a function, proving
# the installed library, headers and runtime linking all work together.
doInstallCheck = !useCling;
installCheckPhase = ''
runHook preInstallCheck
cat > smoke.cpp <<'EOF'
#include "CppInterOp/CppInterOp.h"
#include <cstdio>
int main() {
Cpp::CreateInterpreter();
if (Cpp::Declare("int square(int x) { return x * x; }") != 0) return 1;
bool hadError = false;
intptr_t result = Cpp::Evaluate("square(7)", &hadError);
if (hadError) return 2;
if (result != 49) { std::printf("expected 49, got %ld\n", (long)result); return 3; }
if (Cpp::GetNamed("square") == nullptr) return 4;
return 0;
}
EOF
$CXX -std=c++17 smoke.cpp -I$out/include -L$out/lib -lclangCppInterOp -o smoke
LD_LIBRARY_PATH=$out/lib ./smoke
runHook postInstallCheck
'';
passthru = {
inherit backend resourceDir interpreterArgs;
};
meta = {
description = "Clang-based C++ interoperability library (${backend} backend)";
homepage = "https://github.com/compiler-research/CppInterOp";
changelog = "https://github.com/compiler-research/CppInterOp/releases/tag/v${finalAttrs.version}";
license = with lib.licenses; [
asl20
llvm-exception
];
maintainers = with lib.maintainers; [ thomasjm ];
platforms = lib.platforms.unix;
};
})
|