build (12234B)
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 PYCFLAGS="-std=c11 -fPIC -pthread" 123 [ "$ARCH" = "AVX2" ] && CFLAGS="$CFLAGS -mavx2" 124 WFLAGS="-pedantic -Wall -Wextra -Wno-unused-parameter -Wno-unused-function" 125 OFLAGS="$OPTIMIZE" 126 DFLAGS="-DDEBUG -g3 $(parsesanitize "$SANITIZE")" 127 MFLAGS="-DTHREADS=$THREADS -D$ARCH" 128 CPPFLAGS="-std=c++20 -pthread" 129 130 # TODO: 131 # MEMORY64 is supported on Firefox (from version 134) and Chrome (from 133), 132 # but not on Safari (nor on e.g. Firefox 128 ESR, current default on Debian). 133 # See also https://webassembly.org/features 134 # When it becomes widely available, we can support it by adding -sMEMORY64 135 # to the WASMCFLAGS, WASMCPPFLAGS and WASMLINKFLAGS variables below, and 136 # -sMAXIMUM_MEMORY=10737418240 to WASMLINKFLAGS. This way we can enable 137 # solvers larger than h48h6k2 in the web version. 138 139 # TODO: 140 # The options below have to be adjusted when native WASM_SIMD is implemented. 141 142 # Build flags for emscripten (WASM target) 143 WASMCFLAGS="-std=c11 -fPIC -D_POSIX_C_SOURCE=199309L -pthread 144 -mfpu=neon -mrelaxed-simd" 145 WASMCPPFLAGS="-std=c++20 -pthread" 146 WASMDBGFLAGS="-sASSERTIONS" 147 WASMMFLAGS="-DTHREADS=$THREADS -DNEON" 148 WASMLINKFLAGS="--no-entry -sEXPORT_NAME='Nissy' -sMODULARIZE 149 -sEXPORTED_RUNTIME_METHODS=addFunction,UTF8ToString 150 -sALLOW_TABLE_GROWTH 151 -sALLOW_MEMORY_GROWTH -sSTACK_SIZE=5MB -sPTHREAD_POOL_SIZE=$THREADS 152 -sFETCH -sASYNCIFY -sLINKABLE -sEXPORT_ALL" 153 154 if (command -v "python3-config" >/dev/null 2>&1) ; then 155 PYTHON3_INCLUDES="$(python3-config --includes)" 156 PYTHON3="version $(echo "$PYTHON3_INCLUDES" | sed 's/.*3\./3./')" 157 else 158 PYTHON3_INCLUDES="" 159 PYTHON3="Not found, Python shell won't be available" 160 fi 161 162 build_help() { 163 echo "Build system for nissy. Usage:" 164 echo "" 165 echo "$0 [-d] TARGET # build the given TARGET" 166 echo "$0 [-d] # same as '$0 nissy [-d]'" 167 echo "$0 test [PATTERN] # run unit tests (matching PATTERN)" 168 echo "$0 webtest [PATTERN] # same as test, but for WASM build" 169 echo "$0 [-d] tool PATTERN # run the tool matching PATTERN" 170 echo "" 171 echo "targets:" 172 echo "" 173 echo "nissy Build the nissy.o object file" 174 echo "lib Build the static library libnissy.a" 175 echo "sharedlib Build the shared library libnissy.so" 176 echo "python Build the Python module for nissy" 177 echo "shell Build a basic nissy shell (./run)" 178 echo "web Build the WebAssembly / JavaScript module for nissy" 179 echo "cpp FILES Build and run the given FILES including cpp/nissy.h" 180 echo "solvetest Build nissy and run the tests for solvers (tools)" 181 echo "" 182 echo "help Show this help message" 183 echo "config Show build configuration and exit" 184 echo "clean Remove all build files" 185 echo "" 186 echo "The -d option activates debug mode (slower, used for testing)." 187 echo "Tests are automatically built in debug mode even without -d." 188 echo "For more on build configurations, see the comments in ./build" 189 } 190 191 build_config() { 192 echo "Compiler: $CC" 193 echo "Architecture: $ARCH" 194 echo "Max threads: $THREADS" 195 echo "Optimization options: $OFLAGS" 196 echo "Debug flags: $DFLAGS" 197 echo "" 198 199 echo "Optional features:" 200 201 printf '%s' "WASM compiler: " 202 if (validate_command "$EMCC" >/dev/null); then 203 echo "$EMCC" 204 else 205 echo "Not found, web target won't be available" 206 fi 207 208 printf '%s' "nodejs executable: " 209 if (validate_command "$NODE" >/dev/null); then 210 echo "$NODE" 211 else 212 echo "Not found, unit tests won't be available for web target" 213 fi 214 215 echo "Python bindings: $PYTHON3" 216 } 217 218 run() { 219 echo "$@" 220 $@ 221 } 222 223 odflags() { 224 if [ "$debug" = "yes" ]; then 225 echo "$DFLAGS" 226 else 227 echo "$OFLAGS" 228 fi 229 } 230 231 build_clean() { 232 run rm -rf -- *.o *.so *.a run runtest runtool runcpp \ 233 web/nissy_web_module.* web/http/nissy_web_module.* 234 } 235 236 build_nissy() { 237 validate_command "$CC" 238 validate_threads "$THREADS" 239 validate_arch "$ARCH" 240 241 run $CC $CFLAGS $WFLAGS $MFLAGS $(odflags) -c -o nissy.o src/nissy.c 242 } 243 244 build_lib() { 245 build_nissy 246 run ar rcs libnissy.a nissy.o 247 } 248 249 build_sharedlib() { 250 validate_command "$CC" 251 validate_threads "$THREADS" 252 validate_arch "$ARCH" 253 254 run $CC $CFLAGS $WFLAGS $MFLAGS $(odflags) -shared -c -o nissy.o \ 255 src/nissy.c 256 } 257 258 build_shell() { 259 build_nissy || exit 1 260 run $CC $CFLAGS $WFLAGS $(odflags) -o run nissy.o shell/shell.c 261 } 262 263 build_python() { 264 if [ -z "$PYTHON3_INCLUDES" ]; then 265 echo "Python3 development headers could not be located" 266 echo "Cannot build python module" 267 exit 1 268 fi 269 if [ "$debug" = "yes" ]; then 270 echo "Cannot build Python library in debug mode" 271 exit 1 272 fi 273 build_nissy || exit 1 274 run $CC $PYCFLAGS $WFLAGS $PYTHON3_INCLUDES $OFLAGS -shared \ 275 -o nissy_python_module.so nissy.o python/nissy_module.c 276 } 277 278 build_cpp() { 279 validate_command "$CXX" 280 if [ -z "$@" ]; then 281 echo "Please provide one or more valid C++ source files" 282 echo "usage: ./build cpp FILES" 283 fi 284 285 build_nissy || exit 1 286 run $CXX $(odflags) -std=c++20 -o runcpp cpp/nissy.cpp nissy.o $@ \ 287 || exit 1 288 run ./runcpp 289 } 290 291 build_web() { 292 validate_command "$EMCC" 293 validate_threads "$THREADS" 294 validate_arch "$ARCH" 295 296 obj="nissy_web_module" 297 298 if [ "$debug" = "yes" ]; then 299 ODFLAGS="$DFLAGS -sASSERTIONS" 300 else 301 ODFLAGS="$OFLAGS" 302 fi 303 304 run $EMCC $WASMCFLAGS $WFLAGS $WASMMFLAGS $ODFLAGS -c \ 305 -o nissy.o src/nissy.c || exit 1 306 run $EMCC -lembind -lidbfs.js \ 307 $WASMCPPFLAGS $ODFLAGS $WASMLINKFLAGS -o web/"$obj".mjs \ 308 cpp/nissy.cpp web/storage.cpp web/adapter.cpp nissy.o || exit 1 309 cp web/"$obj".mjs web/http/ 310 cp web/"$obj".wasm web/http/ 311 } 312 313 dotest() { 314 testout="test/last.out" 315 testerr="test/last.err" 316 317 # Verify that $t is a directory and it starts with three digits 318 if [ ! -d "$t" ] || ! (basename "$t" | grep -Eq '^[0-9]{3}'); then 319 return 0 320 fi 321 322 # Verify that the test is selected by the given pattern 323 if [ -n "$pattern" ] && ! (echo "$t" | grep -q "$pattern"); then 324 return 0 325 fi 326 327 $testbuild "$t"/*.c nissy.o || exit 1 328 for cin in "$t"/*.in; do 329 c="$(echo "$cin" | sed 's/\.in//')" 330 cout="$c.out" 331 printf '%s: ' "$c" 332 $testrun < "$cin" > "$testout" 2> "$testerr" 333 if diff "$cout" "$testout"; then 334 printf "OK\n" 335 else 336 printf "Test failed! stderr:\n" 337 cat "$testerr" 338 exit 1 339 fi 340 done 341 } 342 343 build_test_generic() { 344 if [ -n "$1" ]; then 345 pattern="$1" 346 shift 347 fi 348 debug="yes" 349 build_$obj || exit 1 350 for t in test/*; do 351 dotest || exit 1 352 done 353 echo "All tests passed!" 354 } 355 356 build_test() { 357 obj="nissy" 358 testobj="runtest" 359 testbuild="$CC $CFLAGS $WFLAGS $DFLAGS $MFLAGS -o $testobj" 360 testrun="./$testobj" 361 build_test_generic $@ 362 rm -f runtest 363 } 364 365 build_webtest() { 366 obj="web" 367 testobj="runtest.js" 368 testbuild="$EMCC $WASMCFLAGS $WFLAGS $WASMMFLAGS $DFLAGS \ 369 -sSTACK_SIZE=5MB -o $testobj" 370 testrun="$NODE $testobj" 371 build_test_generic $@ 372 rm -f runtest.js runtest.wasm 373 } 374 375 run_single_tool() { 376 results="tools/results" 377 last="$results/last.out" 378 date="$(date +'%Y-%m-%d-%H-%M-%S')" 379 file="$results/$toolname-$date.txt" 380 failed="tools/failed" 381 382 $CC $CFLAGS $WFLAGS $MFLAGS $(odflags) -o runtool "$t"/*.c nissy.o \ 383 || exit 1 384 385 ( 386 date +'%Y-%m-%d %H:%M' 387 echo "" 388 echo "=========== Running tool ===========" 389 echo "tool name: $toolname" 390 echo "" 391 echo "======== nissy build command ========" 392 echo "$CC $CFLAGS $WFLAGS $MFLAGS $(odflags)" 393 echo "" 394 echo "======== tool build command ========" 395 echo "$CC $CFLAGS $WFLAGS $MFLAGS $(odflags)" 396 echo "" 397 echo "============ tool output ============" 398 ./runtool $@ || touch "$failed" 399 ) | tee "$file" "$last" 400 401 rm -f runtool 402 if [ -f "$failed" ]; then 403 rm "$failed" 404 exit 1 405 fi 406 } 407 408 build_tool() { 409 pattern="$1" 410 411 if [ -z "$pattern" ]; then 412 echo "Please provide a valid PATTERN to select a tool" 413 echo "usage: ./build tool PATTERN" 414 exit 1 415 fi 416 shift 417 418 # Select a single tool matched by the given pattern 419 for t in tools/*; do 420 if [ -d "$t" ] && (echo "$t" | grep -q "$pattern"); then 421 toolname="$(basename "$t" .c)" 422 break 423 fi 424 done 425 if [ -z "$toolname" ]; then 426 echo "pattern '$pattern' does not match any tool" 427 exit 1 428 fi 429 430 build_nissy || exit 1 431 run_single_tool 432 } 433 434 build_solvetest() { 435 build_nissy || exit 1 436 for t in tools/*; do 437 if [ -d "$t" ] && (echo "$t" | grep -q "solvetest"); then 438 toolname="$(basename "$t" .c)" 439 run_single_tool || exit 1 440 fi 441 done 442 } 443 444 if [ "$1" = "-d" ]; then 445 debug="yes" 446 shift 447 fi 448 449 target="$1" 450 if [ -z "$target" ]; then 451 target="nissy" 452 else 453 shift 454 fi 455 456 case "$target" in 457 help|config|clean|\ 458 nissy|lib|sharedlib|shell|python|cpp|web|test|webtest|tool|solvetest) 459 mkdir -p tables tools/results 460 (build_"$target" $@) || exit 1 461 exit 0 462 ;; 463 *) 464 echo "Target '$target' unavailable, run '$0 help' for info" 465 exit 1 466 ;; 467 esac