build (11378B)
1 #!/bin/sh 2 3 # Build system for nissy, run './build help' for info on how to use this. 4 5 # The variables below can be used to personalize the build system. For example, 6 # to compile with clang instead of the default cc one can use: 7 # 8 # CC=clang ./build 9 # 10 # Each variable also has a counterpart that starts with NISSY_BUILD_*. These 11 # other variables can be set, for example, in your shell configuration file 12 # to change the default build options. They are still overwritten by the values 13 # of the non-NISSY_BUILD_* variables, if set. 14 # For example, on a system that supports the addressed and undefined behavior 15 # sanitizers, one can set 16 # 17 # export NISSY_BUILD_SANITIZE="address,undefined" 18 # 19 # so that these sanitizers will be enabled when building nissy in debug mode. 20 # If later the same user wants to build a debug version without sanitizers, 21 # they may use 22 # 23 # SANITIZE="" ./build debug 24 # 25 # And the empty string value will take precedence. 26 27 # Specify the C compiler to use. 28 CC=${CC-${NISSY_BUILD_CC:-"cc"}} 29 30 # Specify the C++ compiler for the C++ examples. 31 CXX=${CXX-${NISSY_BUILD_CXX:-"c++"}} 32 33 # Specify the compiler to use when building for WASM. 34 EMCC=${EMCC-${NISSY_BUILD_EMCC:-"emcc"}} 35 36 # Specify the nodejs command for running tests for WASM. 37 NODE=${NODE-${NISSY_BUILD_NODE:-"node"}} 38 39 # The maximum number of threads to use for multi-threaded operations. 40 # This is also used as default value in case an operation allows 41 # specifying how many threads to use. 42 # The number n must be between 1 and 128. 43 # The default value is 16. 44 THREADS=${THREADS-${NISSY_BUILD_THREADS}} 45 46 detectthreads() { 47 # TODO: detect based on system 48 # Is 'getconf NPROCESSORD_ONLN' portable? Is it threads or cores? 49 echo "16" 50 } 51 52 [ -z "$THREADS" ] && THREADS="$(detectthreads)" 53 54 # You can use this variable to build for a different architecture, for example 55 # if you want to cross-compile or to use the portable version. 56 # The value must be one of "AVX2", "NEON", "PORTABLE". If the value is not 57 # specified, the script will automatically detect the best architecture for the 58 # system. The architecture "PORTABLE" will work on any system. 59 ARCH=${ARCH-${NISSY_BUILD_ARCH}} 60 61 greparch() { 62 $CC -march=native -dM -E - </dev/null 2>/dev/null | grep "$1" 63 } 64 65 detectarch() { 66 [ -n "$(greparch __AVX2__)" ] && detected="AVX2" 67 [ -n "$(greparch __ARM_NEON)" ] && detected="NEON" 68 [ -z "$detected" ] && detected="PORTABLE" 69 echo "$detected" 70 } 71 72 [ -z "$ARCH" ] && ARCH="$(detectarch)" 73 74 # SANITIZE="option1,option2,..." adds the options "-fsanitize=option1", 75 # "-fsanitize=option2", ... to the C compiler command in debug mode. 76 # By default none is used because these options are not available on all 77 # systems. It is advisable to set the NISSY_BUILD_SANITIZE options in your 78 # shell's configuration file if your system does support them. 79 SANITIZE=${SANITIZE-${NISSY_BUILD_SANITIZE:-""}} 80 81 # Optimization flags. 82 OPTIMIZE=${OPTIMIZE-${NISSY_BUILD_OPTIMIZE:-"-O3"}} 83 84 validate_command() { 85 command="$1" 86 if ! (command -v "$command" >/dev/null 2>&1) ; then 87 echo "Error: command '$command' not found" 88 exit 1 89 fi 90 } 91 92 validate_threads() { 93 threads="$1" 94 if [ "$threads" -le 0 ] || [ "$threads" -gt 128 ]; then 95 echo "Error: number of threads must be between 1 and 128" 96 exit 1 97 fi 98 } 99 100 validate_arch() { 101 arch="$1" 102 case "$arch" in 103 AVX2|NEON|PORTABLE) 104 ;; 105 *) 106 echo "Error: architecture '$arch' not supported" 107 echo "Supported architectures: AVX2, NEON, PORTABLE" 108 exit 1 109 ;; 110 esac 111 } 112 113 parsesanitize() { 114 # Use the user-specified comma-separated sanitizers 115 for opt in $(echo "$1" | tr ',' '\n'); do 116 printf -- '-fsanitize=%s ' "$opt" 117 done 118 } 119 120 # Build flags 121 CFLAGS="-std=c11 -fPIC -D_POSIX_C_SOURCE=199309L -pthread" 122 [ "$ARCH" = "AVX2" ] && CFLAGS="$CFLAGS -mavx2" 123 WFLAGS="-pedantic -Wall -Wextra -Wno-unused-parameter -Wno-unused-function" 124 OFLAGS="$OPTIMIZE" 125 DFLAGS="-DDEBUG -g3 $(parsesanitize "$SANITIZE")" 126 MFLAGS="-DTHREADS=$THREADS -D$ARCH" 127 CPPFLAGS="-std=c++20 -pthread" 128 129 # TODO: 130 # MEMORY64 is supported on Firefox (from version 134) and Chrome (from 133), 131 # but not on Safari (nor on e.g. Firefox 128 ESR, current default on Debian). 132 # See also https://webassembly.org/features 133 # When it becomes widely available, we can support it by adding -sMEMORY64 134 # to the WASMCFLAGS, WASMCPPFLAGS and WASMLINKFLAGS variables below, and 135 # -sMAXIMUM_MEMORY=10737418240 to WASMLINKFLAGS. This way we can enable 136 # solvers larger than h48h6k2 in the web version. 137 138 # TODO: 139 # The options below have to be adjusted when native WASM_SIMD is implemented. 140 141 # Build flags for emscripten (WASM target) 142 WASMCFLAGS="-std=c11 -fPIC -D_POSIX_C_SOURCE=199309L -pthread 143 -mfpu=neon -mrelaxed-simd" 144 WASMCPPFLAGS="-std=c++20 -pthread" 145 WASMDBGFLAGS="-sASSERTIONS" 146 WASMMFLAGS="-DTHREADS=$THREADS -DNEON" 147 WASMLINKFLAGS="--no-entry -sEXPORT_NAME='Nissy' -sMODULARIZE 148 -sEXPORTED_RUNTIME_METHODS=addFunction,UTF8ToString 149 -sALLOW_TABLE_GROWTH 150 -sALLOW_MEMORY_GROWTH -sSTACK_SIZE=5MB -sPTHREAD_POOL_SIZE=$THREADS 151 -sFETCH -sASYNCIFY -sLINKABLE -sEXPORT_ALL" 152 153 if (command -v "python3-config" >/dev/null 2>&1) ; then 154 PYTHON3_INCLUDES="$(python3-config --includes)" 155 PYTHON3="version $(echo "$PYTHON3_INCLUDES" | sed 's/.*3\./3./')" 156 else 157 PYTHON3_INCLUDES="" 158 PYTHON3="Not found, Python shell won't be available" 159 fi 160 161 build_help() { 162 echo "Build system for nissy. Usage:" 163 echo "" 164 echo "$0 TARGET # build the given TARGET" 165 echo "$0 # same as '$0 nissy'" 166 echo "$0 test [PATTERN] # run unit tests (matching PATTERN)" 167 echo "$0 webtest [PATTERN] # same as test, but for WASM build" 168 echo "$0 tool PATTERN # run the tool matching PATTERN" 169 echo "" 170 echo "targets:" 171 echo "" 172 echo "nissy Build the nissy.o object file" 173 echo "lib Build the static library libnissy.a" 174 echo "sharedlib Build the shared library libnissy.so" 175 echo "python Build the Python module for nissy" 176 echo "shell Build a basic nissy shell (./run)" 177 echo "web Build the WebAssembly / JavaScript module for nissy" 178 echo "cpp FILES Build and run the given FILES including cpp/nissy.h" 179 echo "" 180 echo "help Show this help message" 181 echo "config Show build configuration and exit" 182 echo "clean Remove all build files" 183 } 184 185 build_config() { 186 echo "Compiler: $CC" 187 echo "Architecture: $ARCH" 188 echo "Max threads: $THREADS" 189 echo "Optimization options: $OFLAGS" 190 echo "Debug flags: $DFLAGS" 191 echo "" 192 193 echo "Optional features:" 194 195 printf '%s' "WASM compiler: " 196 if (validate_command "$EMCC" >/dev/null); then 197 echo "$EMCC" 198 else 199 echo "Not found, web target won't be available" 200 fi 201 202 printf '%s' "nodejs executable: " 203 if (validate_command "$NODE" >/dev/null); then 204 echo "$NODE" 205 else 206 echo "Not found, unit tests won't be available for web target" 207 fi 208 209 echo "Python bindings: $PYTHON3" 210 } 211 212 run() { 213 echo "$@" 214 $@ 215 } 216 217 odflags() { 218 if [ "$debug" = "yes" ]; then 219 echo "$DFLAGS" 220 else 221 echo "$OFLAGS" 222 fi 223 } 224 225 build_clean() { 226 run rm -rf -- *.o *.so *.a run runtest runtool runcpp \ 227 web/nissy_web_module.* web/http/nissy_web_module.* 228 } 229 230 build_nissy() { 231 validate_command "$CC" 232 validate_threads "$THREADS" 233 validate_arch "$ARCH" 234 235 run $CC $CFLAGS $WFLAGS $MFLAGS $(odflags) -c -o nissy.o src/nissy.c 236 } 237 238 build_lib() { 239 build_nissy 240 run ar rcs libnissy.a nissy.o 241 } 242 243 build_sharedlib() { 244 validate_command "$CC" 245 validate_threads "$THREADS" 246 validate_arch "$ARCH" 247 248 run $CC $CFLAGS $WFLAGS $MFLAGS $(odflags) -shared -c -o nissy.o \ 249 src/nissy.c 250 } 251 252 build_shell() { 253 build_nissy || exit 1 254 run $CC $CFLAGS $WFLAGS $(odflags) -o run nissy.o shell/shell.c 255 } 256 257 build_python() { 258 if [ -z "$PYTHON3_INCLUDES" ]; then 259 echo "Python3 development headers could not be located" 260 echo "Cannot build python module" 261 exit 1 262 fi 263 build_nissy || exit 1 264 run $CC $CFLAGS $WFLAGS $PYTHON3_INCLUDES $(odflags) -shared \ 265 -o nissy_python_module.so nissy.o python/nissy_module.c 266 } 267 268 build_cpp() { 269 validate_command "$CXX" 270 if [ -z "$@" ]; then 271 echo "Please provide one or more valid C++ source files" 272 echo "usage: ./build cpp FILES" 273 fi 274 275 build_nissy || exit 1 276 run $CXX -std=c++20 -o runcpp cpp/nissy.cpp nissy.o $@ 277 run ./runcpp 278 } 279 280 build_web() { 281 validate_command "$EMCC" 282 validate_threads "$THREADS" 283 validate_arch "$ARCH" 284 285 obj="nissy_web_module" 286 287 if [ "$debug" = "yes" ]; then 288 ODFLAGS="$DFLAGS -sASSERTIONS" 289 else 290 ODFLAGS="$OFLAGS" 291 fi 292 293 run $EMCC $WASMCFLAGS $WFLAGS $WASMMFLAGS $ODFLAGS -c \ 294 -o nissy.o src/nissy.c || exit 1 295 run $EMCC -lembind -lidbfs.js \ 296 $WASMCPPFLAGS $ODFLAGS $WASMLINKFLAGS -o web/"$obj".mjs \ 297 cpp/nissy.cpp web/storage.cpp web/adapter.cpp nissy.o || exit 1 298 cp web/"$obj".mjs web/http/ 299 cp web/"$obj".wasm web/http/ 300 } 301 302 dotest() { 303 testout="test/last.out" 304 testerr="test/last.err" 305 306 # Verify that $t is a directory and it starts with three digits 307 if [ ! -d "$t" ] || ! (basename "$t" | grep -Eq '^[0-9]{3}'); then 308 return 0 309 fi 310 311 # Verify that the test is selected by the given pattern 312 if [ -n "$pattern" ] && ! (echo "$t" | grep -q "$pattern"); then 313 return 0 314 fi 315 316 $testbuild "$t"/*.c nissy.o || exit 1 317 for cin in "$t"/*.in; do 318 c="$(echo "$cin" | sed 's/\.in//')" 319 cout="$c.out" 320 printf '%s: ' "$c" 321 $testrun < "$cin" > "$testout" 2> "$testerr" 322 if diff "$cout" "$testout"; then 323 printf "OK\n" 324 else 325 printf "Test failed! stderr:\n" 326 cat "$testerr" 327 exit 1 328 fi 329 done 330 } 331 332 build_test_generic() { 333 if [ -n "$1" ]; then 334 pattern="$1" 335 shift 336 fi 337 debug="yes" 338 build_$obj || exit 1 339 for t in test/*; do 340 dotest || exit 1 341 done 342 echo "All tests passed!" 343 } 344 345 build_test() { 346 obj="nissy" 347 testobj="runtest" 348 testbuild="$CC $CFLAGS $WFLAGS $DFLAGS $MFLAGS -o $testobj" 349 testrun="./$testobj" 350 build_test_generic $@ 351 rm -f runtest 352 } 353 354 build_webtest() { 355 obj="web" 356 testobj="runtest.js" 357 testbuild="$EMCC $WASMCFLAGS $WFLAGS $WASMMFLAGS $DFLAGS -o $testobj" 358 testrun="$NODE $testobj" 359 build_test_generic $@ 360 rm -f runtest.js runtest.wasm 361 } 362 363 build_tool() { 364 pattern="$1" 365 366 if [ -z "$pattern" ]; then 367 echo "Please provide a valid PATTERN to select a tool" 368 echo "usage: ./build tool PATTERN" 369 exit 1 370 fi 371 shift 372 373 # Select a single tool matched by the given pattern 374 for t in tools/*; do 375 if [ -d "$t" ] && (echo "$t" | grep -q "$pattern"); then 376 toolname="$(basename "$t" .c)" 377 break 378 fi 379 done 380 if [ -z "$toolname" ]; then 381 echo "pattern '$pattern' does not match any tool" 382 exit 1 383 fi 384 385 build_nissy 386 387 results="tools/results" 388 last="$results/last.out" 389 date="$(date +'%Y-%m-%d-%H-%M-%S')" 390 file="$results/$toolname-$date.txt" 391 392 $CC $CFLAGS $WFLAGS $MFLAGS $(odflags) -o runtool "$t"/*.c nissy.o \ 393 || exit 1 394 395 ( 396 date +'%Y-%m-%d %H:%M' 397 echo "" 398 echo "=========== Running tool ===========" 399 echo "tool name: $toolname" 400 echo "" 401 echo "======== nissy build command ========" 402 echo "$CC $CFLAGS $WFLAGS $MFLAGS $(odflags)" 403 echo "" 404 echo "======== tool build command ========" 405 echo "$CC $CFLAGS $WFLAGS $MFLAGS $(odflags)" 406 echo "" 407 echo "============ tool output ============" 408 ./runtool $@ 409 ) | tee "$file" "$last" 410 rm -f runtool 411 } 412 413 if [ "$1" = "-d" ]; then 414 debug="yes" 415 shift 416 fi 417 418 target="$1" 419 if [ -z "$target" ]; then 420 target="nissy" 421 else 422 shift 423 fi 424 425 case "$target" in 426 help|config|clean|\ 427 nissy|lib|sharedlib|shell|python|cpp|web|test|webtest|tool) 428 mkdir -p tables tools/results 429 (build_"$target" $@) || exit 1 430 exit 0 431 ;; 432 *) 433 echo "Target '$target' unavailable, run '$0 help' for info" 434 exit 1 435 ;; 436 esac