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