nissy-core

The "engine" of nissy, including the H48 optimal solver.
git clone https://git.tronto.net/nissy-core
Download | Log | Files | Refs | README | LICENSE

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