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 (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