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