build.sh (12569B)
1 #!/bin/sh 2 3 # Build system for nissy, run './build.sh 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.sh 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.sh 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 portable_nproc() { 47 OS="$(uname -s)" 48 case "$OS" in 49 "Linux") 50 nproc 51 ;; 52 "Darwin" | "*BSD") 53 sysctl -n hw.ncpu 54 ;; 55 *) 56 echo "32" # A safe, large default 57 ;; 58 esac 59 } 60 61 detectthreads() { 62 echo "$(portable_nproc)" 63 } 64 65 [ -z "$THREADS" ] && THREADS="$(detectthreads)" 66 67 # You can use this variable to build for a different architecture, for example 68 # if you want to cross-compile or to use the portable version. 69 # The value must be one of "AVX2", "NEON", "PORTABLE". If the value is not 70 # specified, the script will automatically detect the best architecture for the 71 # system. The architecture "PORTABLE" will work on any system. 72 ARCH=${ARCH-${NISSY_BUILD_ARCH}} 73 74 greparch() { 75 $CC -march=native -dM -E - </dev/null 2>/dev/null | grep "$1" 76 } 77 78 detectarch() { 79 [ -n "$(greparch __AVX2__)" ] && detected="AVX2" 80 [ -n "$(greparch __ARM_NEON)" ] && detected="NEON" 81 [ -z "$detected" ] && detected="PORTABLE" 82 echo "$detected" 83 } 84 85 [ -z "$ARCH" ] && ARCH="$(detectarch)" 86 87 # SANITIZE="option1,option2,..." adds the options "-fsanitize=option1", 88 # "-fsanitize=option2", ... to the C compiler command in debug mode. 89 # By default none is used because these options are not available on all 90 # systems. It is advisable to set the NISSY_BUILD_SANITIZE options in your 91 # shell's configuration file if your system does support them. 92 SANITIZE=${SANITIZE-${NISSY_BUILD_SANITIZE:-""}} 93 94 # Optimization flags. 95 OPTIMIZE=${OPTIMIZE-${NISSY_BUILD_OPTIMIZE:-"-O3"}} 96 97 validate_command() { 98 command="$1" 99 if ! (command -v "$command" >/dev/null 2>&1) ; then 100 echo "Error: command '$command' not found" 101 exit 1 102 fi 103 } 104 105 validate_threads() { 106 threads="$1" 107 if [ "$threads" -le 0 ] || [ "$threads" -gt 128 ]; then 108 echo "Error: number of threads must be between 1 and 128" 109 exit 1 110 fi 111 } 112 113 validate_arch() { 114 arch="$1" 115 case "$arch" in 116 AVX2|NEON|PORTABLE) 117 ;; 118 *) 119 echo "Error: architecture '$arch' not supported" 120 echo "Supported architectures: AVX2, NEON, PORTABLE" 121 exit 1 122 ;; 123 esac 124 } 125 126 parsesanitize() { 127 # Use the user-specified comma-separated sanitizers 128 for opt in $(echo "$1" | tr ',' '\n'); do 129 printf -- '-fsanitize=%s ' "$opt" 130 done 131 } 132 133 maybe_pthread() { 134 if [ "$THREADS" -gt 1 ]; then 135 echo "-pthread" 136 else 137 echo "" 138 fi 139 } 140 141 # Build flags 142 CFLAGS="-std=c11 -fPIC -D_POSIX_C_SOURCE=199309L $(maybe_pthread)" 143 PYCFLAGS="-std=c11 -fPIC $(maybe_pthread)" 144 [ "$ARCH" = "AVX2" ] && CFLAGS="$CFLAGS -mavx2" 145 WFLAGS="-pedantic -Wall -Wextra -Wno-unused-parameter -Wno-unused-function" 146 OFLAGS="$OPTIMIZE" 147 DFLAGS="-DDEBUG -g3 $(parsesanitize "$SANITIZE")" 148 MFLAGS="-DTHREADS=$THREADS -D$ARCH" 149 CPPFLAGS="-std=c++20 $(maybe_pthread)" 150 151 # TODO: 152 # MEMORY64 is supported on Firefox (from version 134) and Chrome (from 133), 153 # but not on Safari (nor on e.g. Firefox 128 ESR, current default on Debian). 154 # See also https://webassembly.org/features 155 # When it becomes widely available, we can support it by adding -sMEMORY64 156 # to the WASMCFLAGS, WASMCPPFLAGS and WASMLINKFLAGS variables below, and 157 # -sMAXIMUM_MEMORY=10737418240 to WASMLINKFLAGS. This way we can enable 158 # solvers larger than h48h6 in the web version. 159 160 # TODO: 161 # The options below have to be adjusted when native WASM_SIMD is implemented. 162 163 # Build flags for emscripten (WASM target) 164 WASMCFLAGS="-std=c11 -fPIC -D_POSIX_C_SOURCE=199309L $(maybe_pthread)" 165 WASMCPPFLAGS="-std=c++20 $(maybe_pthread)" 166 WASMDBGFLAGS="-sASSERTIONS" 167 WASMMFLAGS="-DTHREADS=$THREADS -DPORTABLE" 168 WASMLINKFLAGS="--no-entry -sEXPORT_NAME='Nissy' -sMODULARIZE 169 -sEXPORTED_RUNTIME_METHODS=addFunction,UTF8ToString 170 -sALLOW_TABLE_GROWTH 171 -sALLOW_MEMORY_GROWTH -sSTACK_SIZE=5MB -sPTHREAD_POOL_SIZE=$THREADS 172 -sFETCH -sASYNCIFY -sLINKABLE -sEXPORT_ALL" 173 174 if (command -v "python3-config" >/dev/null 2>&1) ; then 175 PYTHON3_INCLUDES="$(python3-config --includes)" 176 PYTHON3="version $(echo "$PYTHON3_INCLUDES" | sed 's/.*3\./3./')" 177 else 178 PYTHON3_INCLUDES="" 179 PYTHON3="Not found, Python shell won't be available" 180 fi 181 182 build_help() { 183 echo "Build system for nissy. Usage:" 184 echo "" 185 echo "$0 [-d] [TARGET]" 186 echo "" 187 echo "Possible values for TARGET (defaults to 'nissy' if unspecified):" 188 echo "" 189 echo "nissy Build the nissy.o object file." 190 echo "lib Build the static library libnissy.a." 191 echo "sharedlib Build the shared library libnissy.so." 192 echo "python Build the Python module for nissy." 193 echo "shell Build a basic nissy shell (./run)." 194 echo "web Build the WebAssembly module for nissy." 195 echo "cpp FILES Build and run the given C++ FILES." 196 echo "test [EXPR] Build and run unit tests. If EXPR is provided," 197 echo " only the tests whose name matches EXPR are run." 198 echo " The -d option is always implied." 199 echo "webtest [EXPR] Same as test, but for WebAssembly build." 200 echo "tool EXPR Run the 'tool' matching the given EXPR." 201 echo "solvetest Build nissy and run a collection of tools for" 202 echo " testing various solvers." 203 echo "help Show this help message." 204 echo "config Show build configuration and exit." 205 echo "clean Remove all build files." 206 echo "" 207 echo "The -d option activates debug mode (slower, used for testing)." 208 echo "Tests are automatically built in debug mode even without -d." 209 echo "For more on build configurations, see the comments in ./build.sh" 210 } 211 212 build_config() { 213 echo "Compiler: $CC" 214 echo "Architecture: $ARCH" 215 echo "Max threads: $THREADS" 216 echo "Optimization options: $OFLAGS" 217 echo "Debug flags: $DFLAGS" 218 echo "" 219 220 echo "Optional features:" 221 222 printf '%s' "WASM compiler: " 223 if (validate_command "$EMCC" >/dev/null); then 224 echo "$EMCC" 225 else 226 echo "Not found, web target won't be available" 227 fi 228 229 printf '%s' "nodejs executable: " 230 if (validate_command "$NODE" >/dev/null); then 231 echo "$NODE" 232 else 233 echo "Not found, unit tests won't be available for web target" 234 fi 235 236 echo "Python bindings: $PYTHON3" 237 } 238 239 run() { 240 echo "$@" 241 $@ 242 } 243 244 odflags() { 245 if [ "$debug" = "yes" ]; then 246 echo "$DFLAGS" 247 else 248 echo "$OFLAGS" 249 fi 250 } 251 252 build_clean() { 253 run rm -rf -- *.o *.so *.a run runtest runtool runcpp \ 254 web/nissy_web_module.* web/http/nissy_web_module.* 255 } 256 257 build_nissy() { 258 validate_command "$CC" 259 validate_threads "$THREADS" 260 validate_arch "$ARCH" 261 262 run $CC $CFLAGS $WFLAGS $MFLAGS $(odflags) -c -o nissy.o src/nissy.c 263 } 264 265 build_lib() { 266 build_nissy 267 run ar rcs libnissy.a nissy.o 268 } 269 270 build_sharedlib() { 271 validate_command "$CC" 272 validate_threads "$THREADS" 273 validate_arch "$ARCH" 274 275 run $CC $CFLAGS $WFLAGS $MFLAGS $(odflags) -shared -c -o nissy.o \ 276 src/nissy.c 277 } 278 279 build_shell() { 280 build_nissy || exit 1 281 run $CC $CFLAGS $WFLAGS $(odflags) -o run nissy.o shell/shell.c 282 } 283 284 build_python() { 285 if [ -z "$PYTHON3_INCLUDES" ]; then 286 echo "Python3 development headers could not be located" 287 echo "Cannot build python module" 288 exit 1 289 fi 290 if [ "$debug" = "yes" ]; then 291 echo "Cannot build Python library in debug mode" 292 exit 1 293 fi 294 build_nissy || exit 1 295 run $CC $PYCFLAGS $WFLAGS $PYTHON3_INCLUDES $OFLAGS -shared \ 296 -o python/nissy.so nissy.o python/nissy_module.c 297 } 298 299 build_cpp() { 300 validate_command "$CXX" 301 if [ -z "$@" ]; then 302 echo "Please provide one or more valid C++ source files" 303 echo "usage: ./build.sh cpp FILES" 304 fi 305 306 build_nissy || exit 1 307 run $CXX $(odflags) -std=c++20 -o runcpp cpp/nissy.cpp nissy.o $@ \ 308 || exit 1 309 run ./runcpp 310 } 311 312 build_web() { 313 validate_command "$EMCC" 314 validate_threads "$THREADS" 315 validate_arch "$ARCH" 316 317 obj="nissy_web_module" 318 319 if [ "$debug" = "yes" ]; then 320 ODFLAGS="$DFLAGS -sASSERTIONS" 321 else 322 ODFLAGS="$OFLAGS" 323 fi 324 325 run $EMCC $WASMCFLAGS $WFLAGS $WASMMFLAGS $ODFLAGS -c \ 326 -o nissy.o src/nissy.c || exit 1 327 run $EMCC -lembind -lidbfs.js \ 328 $WASMCPPFLAGS $ODFLAGS $WASMLINKFLAGS -o web/"$obj".mjs \ 329 cpp/nissy.cpp web/storage.cpp web/adapter.cpp nissy.o || exit 1 330 cp web/"$obj".mjs web/http/ 331 cp web/"$obj".wasm web/http/ 332 } 333 334 dotest() { 335 testout="test/last.out" 336 testerr="test/last.err" 337 338 # Verify that $t is a directory and it starts with three digits 339 if [ ! -d "$t" ] || ! (basename "$t" | grep -Eq '^[0-9]{3}'); then 340 return 0 341 fi 342 343 # Verify that the test is selected by the given pattern 344 if [ -n "$pattern" ] && ! (echo "$t" | grep -q "$pattern"); then 345 return 0 346 fi 347 348 $testbuild "$t"/*.c nissy.o || exit 1 349 for cin in "$t"/*.in; do 350 c="$(echo "$cin" | sed 's/\.in//')" 351 cout="$c.out" 352 printf '%s: ' "$c" 353 $testrun < "$cin" > "$testout" 2> "$testerr" 354 if diff "$cout" "$testout"; then 355 printf "OK\n" 356 else 357 printf "Test failed! stderr:\n" 358 cat "$testerr" 359 exit 1 360 fi 361 done 362 } 363 364 build_test_generic() { 365 if [ -n "$1" ]; then 366 pattern="$1" 367 shift 368 fi 369 debug="yes" 370 build_$obj || exit 1 371 for t in test/*; do 372 dotest || exit 1 373 done 374 echo "All tests passed!" 375 } 376 377 build_test() { 378 obj="nissy" 379 testobj="runtest" 380 testbuild="$CC $CFLAGS $WFLAGS $DFLAGS $MFLAGS -o $testobj" 381 testrun="./$testobj" 382 build_test_generic $@ 383 rm -f runtest 384 } 385 386 build_webtest() { 387 obj="web" 388 testobj="runtest.js" 389 testbuild="$EMCC $WASMCFLAGS $WFLAGS $WASMMFLAGS $DFLAGS \ 390 -sSTACK_SIZE=5MB -o $testobj" 391 testrun="$NODE $testobj" 392 build_test_generic $@ 393 rm -f runtest.js runtest.wasm 394 } 395 396 run_single_tool() { 397 results="tools/results" 398 last="$results/last.out" 399 date="$(date +'%Y-%m-%d-%H-%M-%S')" 400 file="$results/$toolname-$date.txt" 401 failed="tools/failed" 402 403 $CC $CFLAGS $WFLAGS $MFLAGS $(odflags) -o runtool "$t"/*.c nissy.o \ 404 || exit 1 405 406 ( 407 date +'%Y-%m-%d %H:%M' 408 echo "" 409 echo "=========== Running tool ===========" 410 echo "tool name: $toolname" 411 echo "" 412 echo "======== nissy build command ========" 413 echo "$CC $CFLAGS $WFLAGS $MFLAGS $(odflags)" 414 echo "" 415 echo "======== tool build command ========" 416 echo "$CC $CFLAGS $WFLAGS $MFLAGS $(odflags)" 417 echo "" 418 echo "============ tool output ============" 419 ./runtool $@ || touch "$failed" 420 ) | tee "$file" "$last" 421 422 rm -f runtool 423 if [ -f "$failed" ]; then 424 rm "$failed" 425 exit 1 426 fi 427 } 428 429 build_tool() { 430 pattern="$1" 431 432 if [ -z "$pattern" ]; then 433 echo "Please provide a valid PATTERN to select a tool" 434 echo "usage: ./build.sh tool PATTERN" 435 exit 1 436 fi 437 shift 438 439 # Select a single tool matched by the given pattern 440 for t in tools/*; do 441 if [ -d "$t" ] && (echo "$t" | grep -q "$pattern"); then 442 toolname="$(basename "$t" .c)" 443 break 444 fi 445 done 446 if [ -z "$toolname" ]; then 447 echo "pattern '$pattern' does not match any tool" 448 exit 1 449 fi 450 451 build_nissy || exit 1 452 run_single_tool $@ 453 } 454 455 build_solvetest() { 456 build_nissy || exit 1 457 for t in tools/*; do 458 if [ -d "$t" ] && (echo "$t" | grep -q "solvetest"); then 459 toolname="$(basename "$t" .c)" 460 run_single_tool || exit 1 461 fi 462 done 463 } 464 465 if [ "$1" = "-d" ]; then 466 debug="yes" 467 shift 468 fi 469 470 target="$1" 471 if [ -z "$target" ]; then 472 target="nissy" 473 else 474 shift 475 fi 476 477 case "$target" in 478 help|config|clean|\ 479 nissy|lib|sharedlib|shell|python|cpp|web|test|webtest|tool|solvetest) 480 mkdir -p tables tools/results 481 (build_"$target" $@) || exit 1 482 exit 0 483 ;; 484 *) 485 echo "Target '$target' unavailable, run '$0 help' for info" 486 exit 1 487 ;; 488 esac