build (10396B)
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 # Build flags for emscripten (WASM target) 130 WASMCFLAGS="-std=c11 -fPIC -D_POSIX_C_SOURCE=199309L -pthread" 131 WASMMFLAGS="-DTHREADS=$THREADS -DWASMSIMD" 132 WASMLINKFLAGS="--no-entry -sEXPORT_NAME='Nissy' -sMODULARIZE \ 133 -sALLOW_MEMORY_GROWTH -sSTACK_SIZE=5MB -sPTHREAD_POOL_SIZE=$THREADS \ 134 -sLINKABLE -sEXPORT_ALL" 135 136 if (command -v "python3-config" >/dev/null 2>&1) ; then 137 PYTHON3_INCLUDES="$(python3-config --includes)" 138 PYTHON3="version $(echo "$PYTHON3_INCLUDES" | sed 's/.*3\./3./')" 139 else 140 PYTHON3_INCLUDES="" 141 PYTHON3="Not found, Python shell won't be available" 142 fi 143 144 build_help() { 145 echo "Build system for nissy. Usage:" 146 echo "" 147 echo "$0 TARGET # build the given TARGET" 148 echo "$0 # same as '$0 nissy'" 149 echo "$0 test [PATTERN] # run unit tests (matching PATTERN)" 150 echo "$0 webtest [PATTERN] # same as test, but for WASM build" 151 echo "$0 tool PATTERN # run the tool matching PATTERN" 152 echo "" 153 echo "targets:" 154 echo "" 155 echo "nissy Build the nissy.o object file" 156 echo "lib Build the static library libnissy.a" 157 echo "sharedlib Build the shared library libnissy.so" 158 echo "python Build the Python module for nissy" 159 echo "shell Build a basic nissy shell (./run)" 160 echo "web Build the WebAssembly / JavaScript module for nissy" 161 echo "cpp FILES Build and run the given FILES including cpp/nissy.h" 162 echo "" 163 echo "help Show this help message" 164 echo "config Show build configuration and exit" 165 echo "clean Remove all build files" 166 } 167 168 build_config() { 169 echo "Compiler: $CC" 170 echo "Architecture: $ARCH" 171 echo "Max threads: $THREADS" 172 echo "Optimization options: $OFLAGS" 173 echo "Debug flags: $DFLAGS" 174 echo "" 175 176 echo "Optional features:" 177 178 printf '%s' "WASM compiler: " 179 if (validate_command "$EMCC" >/dev/null); then 180 echo "$EMCC" 181 else 182 echo "Not found, web target won't be available" 183 fi 184 185 printf '%s' "nodejs executable: " 186 if (validate_command "$NODE" >/dev/null); then 187 echo "$NODE" 188 else 189 echo "Not found, unit tests won't be available for web target" 190 fi 191 192 echo "Python bindings: $PYTHON3" 193 } 194 195 run() { 196 echo "$@" 197 $@ 198 } 199 200 odflags() { 201 if [ "$debug" = "yes" ]; then 202 echo "$DFLAGS" 203 else 204 echo "$OFLAGS" 205 fi 206 } 207 208 build_clean() { 209 run rm -rf -- *.o *.so *.a run runtest runtool runcpp \ 210 web/nissy_web_module.* 211 } 212 213 build_nissy() { 214 validate_command "$CC" 215 validate_threads "$THREADS" 216 validate_arch "$ARCH" 217 218 run $CC $CFLAGS $WFLAGS $MFLAGS $(odflags) -c -o nissy.o src/nissy.c 219 } 220 221 build_lib() { 222 build_nissy 223 run ar rcs libnissy.a nissy.o 224 } 225 226 build_sharedlib() { 227 validate_command "$CC" 228 validate_threads "$THREADS" 229 validate_arch "$ARCH" 230 231 run $CC $CFLAGS $WFLAGS $MFLAGS $(odflags) -shared -c -o nissy.o \ 232 src/nissy.c 233 } 234 235 build_shell() { 236 build_nissy 237 run $CC $CFLAGS $WFLAGS $(odflags) -o run nissy.o shell/shell.c 238 } 239 240 build_python() { 241 if [ -z "$PYTHON3_INCLUDES" ]; then 242 echo "Python3 development headers could not be located" 243 echo "Cannot build python module" 244 exit 1 245 fi 246 build_nissy 247 run $CC $CFLAGS $WFLAGS $PYTHON3_INCLUDES $(odflags) -shared \ 248 -o nissy_python_module.so nissy.o python/nissy_module.c 249 } 250 251 build_cpp() { 252 validate_command "$CXX" 253 if [ -z "$@" ]; then 254 echo "Please provide one or more valid C++ source files" 255 echo "usage: ./build cpp FILES" 256 fi 257 258 build_nissy 259 run $CXX -std=c++20 -o runcpp cpp/nissy.cpp nissy.o $@ 260 run ./runcpp 261 } 262 263 build_web() { 264 validate_command "$EMCC" 265 validate_threads "$THREADS" 266 validate_arch "$ARCH" 267 268 run $EMCC $WASMCFLAGS $WFLAGS $WASMMFLAGS $(odflags) -c \ 269 -o nissy.o src/nissy.c 270 run $EMCC -lembind -lidbfs.js -lnodefs.js \ 271 $CPPFLAGS $(odflags) $WASMLINKFLAGS \ 272 --js-library web/callback.js \ 273 -o web/nissy_web_module.mjs \ 274 cpp/nissy.cpp web/adapter.cpp nissy.o 275 } 276 277 dotest() { 278 testout="test/last.out" 279 testerr="test/last.err" 280 281 # Verify that $t is a directory and it starts with three digits 282 if [ ! -d "$t" ] || ! (basename "$t" | grep -Eq '^[0-9]{3}'); then 283 return 0 284 fi 285 286 # Verify that the test is selected by the given pattern 287 if [ -n "$pattern" ] && ! (echo "$t" | grep -q "$pattern"); then 288 return 0 289 fi 290 291 $testbuild "$t"/*.c nissy.o || exit 1 292 for cin in "$t"/*.in; do 293 c="$(echo "$cin" | sed 's/\.in//')" 294 cout="$c.out" 295 printf '%s: ' "$c" 296 $testrun < "$cin" > "$testout" 2> "$testerr" 297 if diff "$cout" "$testout"; then 298 printf "OK\n" 299 else 300 printf "Test failed! stderr:\n" 301 cat "$testerr" 302 exit 1 303 fi 304 done 305 } 306 307 build_test_generic() { 308 if [ -n "$1" ]; then 309 pattern="$1" 310 shift 311 fi 312 debug="yes" 313 build_$obj 314 for t in test/*; do 315 dotest || exit 1 316 done 317 echo "All tests passed!" 318 } 319 320 build_test() { 321 obj="nissy" 322 testobj="runtest" 323 testbuild="$CC $CFLAGS $WFLAGS $DFLAGS $MFLAGS -o $testobj" 324 testrun="./$testobj" 325 build_test_generic $@ 326 rm runtest 327 } 328 329 build_webtest() { 330 obj="web" 331 testobj="runtest.js" 332 testbuild="$EMCC $WASMCFLAGS $WFLAGS $WASMMFLAGS $DFLAGS -o $testobj" 333 testrun="$NODE $testobj" 334 build_test_generic $@ 335 rm -f runtest.js runtest.wasm 336 } 337 338 build_tool() { 339 pattern="$1" 340 341 if [ -z "$pattern" ]; then 342 echo "Please provide a valid PATTERN to select a tool" 343 echo "usage: ./build tool PATTERN" 344 exit 1 345 fi 346 shift 347 348 # Select a single tool matched by the given pattern 349 for t in tools/*; do 350 if [ -d "$t" ] && (echo "$t" | grep -q "$pattern"); then 351 toolname="$(basename "$t" .c)" 352 break 353 fi 354 done 355 if [ -z "$toolname" ]; then 356 echo "pattern '$pattern' does not match any tool" 357 exit 1 358 fi 359 360 build_nissy 361 362 results="tools/results" 363 last="$results/last.out" 364 date="$(date +'%Y-%m-%d-%H-%M-%S')" 365 file="$results/$toolname-$date.txt" 366 367 $CC $CFLAGS $WFLAGS $MFLAGS $(odflags) -o runtool "$t"/*.c nissy.o \ 368 || exit 1 369 370 ( 371 date +'%Y-%m-%d %H:%M' 372 echo "" 373 echo "=========== Running tool ===========" 374 echo "tool name: $toolname" 375 echo "" 376 echo "======== nissy build command ========" 377 echo "$CC $CFLAGS $WFLAGS $MFLAGS $(odflags)" 378 echo "" 379 echo "======== tool build command ========" 380 echo "$CC $CFLAGS $WFLAGS $MFLAGS $(odflags)" 381 echo "" 382 echo "============ tool output ============" 383 ./runtool $@ 384 ) | tee "$file" "$last" 385 rm -f runtool 386 } 387 388 if [ "$1" = "-d" ]; then 389 debug="yes" 390 shift 391 fi 392 393 target="$1" 394 if [ -z "$target" ]; then 395 target="nissy" 396 else 397 shift 398 fi 399 400 case "$target" in 401 help|config|clean|\ 402 nissy|lib|sharedlib|shell|python|cpp|web|test|webtest|tool) 403 mkdir -p tables tools/results 404 (build_"$target" $@) || exit 1 405 exit 0 406 ;; 407 *) 408 echo "Target '$target' unavailable, run '$0 help' for info" 409 exit 1 410 ;; 411 esac