#!/usr/bin/env sh # MIT License, Copyright (c) 2020 Marvin Borner set -e cd "$(dirname "$0")" MELVIX_CONFIG="${MELVIX_CONFIG:-dev}" MAKE=make NPROC=nproc SUDO=sudo TAGS=ctags if [ "$(uname -s)" = "OpenBSD" ]; then NPROC="sysctl -n hw.ncpuonline" SUDO="doas" TAGS="ectags" export MAKE=gmake export CC="egcc" export CXX="eg++" export LDFLAGS=-Wl,-z,notext fi mode="${1}" no_ask="${2}" # TODO: Support q35 chipset ('-machine q35') qemu_with_flags() { network="rtl8139" [ -e /dev/kvm ] && [ -r /dev/kvm ] && [ -w /dev/kvm ] && accel="-enable-kvm" || echo "KVM acceleration not available. Make sure your PC supports it and that you're in the KVM group" qemu-system-i386 -d guest_errors,unimp,pcall -cpu max -no-reboot -vga std -rtc base=localtime -m 256M -netdev user,id=net0,hostfwd=tcp:127.0.0.1:8000-10.0.2.15:8000 -device $network,netdev=net0 -object filter-dump,id=dump,netdev=net0,file=dump.pcap $accel "$@" } make_cross() { if [ ! -d "./cross/" ]; then if [ "$no_ask" != "-y" ]; then echo -n "Do you want to compile a cross compiler (this can take up to 20 minutes)? [yn] " read -r answer if ! [ "$answer" != "${answer#[Yy]}" ]; then echo "The compilation of melvix requires a cross compiler!" exit 1 fi fi # Create directory mkdir -p cross cd cross DIR=$(pwd) # Get sources mkdir "${DIR}/src" && cd "${DIR}/src" echo "Downloading..." curl "https://ftp.gnu.org/gnu/binutils/binutils-2.36.1.tar.gz" >binutils.tar.gz tar xzf binutils.tar.gz curl "https://ftp.gnu.org/gnu/gcc/gcc-11.1.0/gcc-11.1.0.tar.gz" >gcc.tar.gz tar xzf gcc.tar.gz # Prepare compiling mkdir -p "${DIR}/opt/bin" export CFLAGS="-g0 -O2" export PREFIX="${DIR}/opt" export TARGET=i686-elf export PATH="$PREFIX/bin:$PATH" if [ "$(uname -s)" = "OpenBSD" ]; then export with_gmp=/usr/local sed -i 's/-no-pie/-nopie/g' "${DIR}/src/gcc-11.1.0/gcc/configure" fi # Compile binutils mkdir "${DIR}/src/build-binutils" && cd "${DIR}/src/build-binutils" ../binutils-2.36.1/configure --target="$TARGET" --prefix="$PREFIX" --with-sysroot --disable-nls --disable-werror $MAKE -j $($NPROC) $MAKE install # Compile GCC mkdir "${DIR}/src/build-gcc" && cd "${DIR}/src/build-gcc" ../gcc-11.1.0/configure --target="$TARGET" --prefix="$PREFIX" --disable-nls --enable-languages=c --without-headers $MAKE -j $($NPROC) all-gcc all-target-libgcc $MAKE install-gcc install-target-libgcc # Fix things if [ "$(uname -s)" = "OpenBSD" ]; then cd "${DIR}/opt/libexec/gcc/i686-elf/11.1.0/" && ln -sf liblto_plugin.so.0.0 liblto_plugin.so fi # Remove sources rm -rf "${DIR}/src/" cd "${DIR}/.." fi } make_disk() { rm -rf disk && mkdir -p disk/font/ && mkdir -p disk/icons/ && mkdir -p disk/conf/ && mkdir -p disk/boot/ echo "Hallo" >disk/conf/test cp -r res/ disk/ echo "Getting font" cd disk/font/ VERSION="1.9.1" wget -q "https://github.com/fcambus/spleen/releases/download/$VERSION/spleen-$VERSION.tar.gz" tar xzf "spleen-$VERSION.tar.gz" mv spleen-"$VERSION"/*.psfu . rm -rf "spleen-$VERSION"* cd ../../ icons="close chess-bishop chess-king chess-knight chess-pawn chess-queen chess-rook" [ -d icons/ ] || git clone --depth=1 https://github.com/Templarian/MaterialDesign.git icons/ inkscape_version="$(inkscape --version | awk '{print $2}' | cut -c1)" echo "$icons" | while read icon; do echo "Converting $icon" if [ "$inkscape_version" = "0" ]; then inkscape -z -w 18 -h 18 icons/svg/"$icon".svg -e disk/icons/"$icon"-18.png inkscape -z -w 24 -h 24 icons/svg/"$icon".svg -e disk/icons/"$icon"-24.png inkscape -z -w 36 -h 36 icons/svg/"$icon".svg -e disk/icons/"$icon"-36.png inkscape -z -w 48 -h 48 icons/svg/"$icon".svg -e disk/icons/"$icon"-48.png else inkscape -w 18 -h 18 icons/svg/"$icon".svg -o disk/icons/"$icon"-18.png inkscape -w 24 -h 24 icons/svg/"$icon".svg -o disk/icons/"$icon"-24.png inkscape -w 36 -h 36 icons/svg/"$icon".svg -o disk/icons/"$icon"-36.png inkscape -w 48 -h 48 icons/svg/"$icon".svg -o disk/icons/"$icon"-48.png fi done echo "Done!" } make_build() { if ! [ -d "disk/" ]; then echo "Creating disk..." make_disk fi mkdir -p build/ rm -rf build/* printf "\nBuilding...\n" $MAKE -j $($NPROC) CONFIG="$MELVIX_CONFIG" $MELVIX_MAKE # Prepare Grub mkdir -p disk/boot/grub/ cp build/apps/kernel/exec disk/boot/melvix cp boot/grub.cfg disk/boot/grub/ # Create disk image # TODO: Fix build for OpenBSD dd if=/dev/zero of=build/disk.img bs=1k count=32k status=none DEV=$($SUDO losetup --find --partscan --show build/disk.img) PART="p1" $SUDO parted -s "$DEV" mklabel msdos mkpart primary ext2 32k 100% -a minimal set 1 boot on if [ "$(uname -s)" = "OpenBSD" ]; then VND=$($SUDO vnconfig build/disk.img) ( echo "e 0" echo 83 echo n echo 0 echo "*" echo "quit" ) | $SUDO fdisk -e $VND >/dev/null $SUDO mkfs.ext2 -F /dev/${VND}i >/dev/null else $SUDO mke2fs -b 1024 -q "$DEV$PART" fi # Set test app as init if [ "$mode" = "test" ]; then cp build/apps/test/exec build/apps/init/exec fi mkdir -p mnt/ if [ "$(uname -s)" = "OpenBSD" ]; then $SUDO mount -t ext2fs /dev/${VND}i mnt/ else $SUDO mount "$DEV$PART" mnt/ fi $SUDO cp -r disk/* mnt/ $SUDO chmod -R 0 mnt/conf/ $SUDO cp -r build/apps/ mnt/apps/ # Install grub if release if [ "$MELVIX_CONFIG" = "release" ]; then $SUDO grub-install --boot-directory=mnt/boot --target=i386-pc --modules="ext2" "$DEV" fi $SUDO umount mnt/ || (sync && sudo umount mnt/) $SUDO rm -rf mnt/ if [ "$(uname -s)" = "OpenBSD" ]; then $SUDO vnconfig -u $VND else $SUDO losetup -d "$DEV" fi printf "Build finshed successfully!\n\n" } make_test() { if [ "$mode" = "test" ]; then qemu_with_flags -serial file:test.log -nographic -kernel build/apps/kernel/exec -drive file=build/disk.img,format=raw,index=1,media=disk echo grep -E 'PASS|FAIL' test.log if grep -q "All tests passed" test.log; then exit 0; else exit 1; fi else qemu_with_flags -serial stdio -kernel build/apps/kernel/exec -drive file=build/disk.img,format=raw,index=1,media=disk fi } make_debug() { qemu_with_flags -serial stdio -kernel build/apps/kernel/exec -drive file=build/disk.img,format=raw,index=1,media=disk -s -S } make_disasm() { if [ -z "$1" ]; then echo "Usage: './run disasm {kernel, wm, ...} [-S]'" exit 1 fi objdump -drwC "$2" -Mintel build/apps/"$1"/exec | less -R #hexdump -C build/kernel.bin | less -R } make_addr() { printf "Info: Make sure that you've turned the debug build on (e.g. with MELVIX_CONFIG=debug)\n\n" if [ -z "$2" ]; then echo "Usage: './run addr kernel 0x50042'" exit 1 fi addr2line -e build/apps/"$1"/exec -f -p "$2" } make_cloc() { cloc . --exclude-dir=build,iso,disk,res,cross,icons } make_append_commands() { s="" while read -r data; do s="${s} ${data}" done echo "$s" | sed -n "/Compiled $1/,/Compiled $2/p" | grep -wE 'gcc' | grep -w '\-c' | jq -nR '[inputs|{directory:"'"$(pwd)/$3"'/", command:., file: match(" [^ ]+$").string[1:]}]' \ >>compile_commands.json } make_sync() { $TAGS -R --exclude=.git --exclude=build --exclude=disk --exclude=cross --exclude=boot . rm -f compile_commands.json output=$($MAKE --always-make --dry-run) echo "$output" | make_append_commands libc libk libs/libc echo "$output" | make_append_commands libk libgui libs/libgui echo "$output" | make_append_commands libgui libtxt libs/libtxt echo "$output" | make_append_commands libtxt kernel kernel echo "$output" | make_append_commands kernel apps apps tr tmp mv tmp compile_commands.json } make_lint() { format=$({ find . -path ./cross -prune -false -o -iname *.h -o -iname *.c -exec clang-format-11 -n --Werror {} \;; } 2>&1) if [ $(echo -n "$format" | head -c1 | wc -c) -ne 0 ]; then echo "$format" exit 1 fi # TODO: Implement clang-tidy linting # find . -path ./cross -prune -o -iname *.h -o -iname *.c | xargs clang-tidy-11 --checks=-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling echo "No errors!" } make_format() { find . -path ./cross -prune -false -o -iname *.h -o -iname *.c -exec clang-format-11 -i {} \; } make_clean() { #rm -rf build/ $MAKE clean } if [ "${mode}" = "cross" ]; then make_cross elif [ "${mode}" = "build" ]; then make_cross make_clean make_build elif [ "${mode}" = "clean" ]; then make_clean elif [ "${mode}" = "lint" ]; then make_lint elif [ "${mode}" = "format" ]; then make_format elif [ "${mode}" = "again" ]; then make_test elif [ "${mode}" = "disasm" ]; then make_disasm "$2" "$3" elif [ "${mode}" = "addr" ]; then make_addr "$2" "$3" elif [ "${mode}" = "cloc" ]; then make_cloc elif [ "${mode}" = "sync" ]; then make_sync elif [ "${mode}" = "disk" ]; then make_disk elif [ "${mode}" = "debug" ]; then make_cross make_clean make_build make_sync & make_debug elif [ "${mode}" = "test" ] || [ "${mode}" = "" ]; then make_cross make_clean make_build make_sync & make_test make_clean else echo "Usage: ./run {cross | clean | build | test | debug | again | disasm | addr | sync | disk} [-y]" printf "\nDescription of options:\n" printf "cross\t\tBuilds the cross compiler\n" printf "clean\t\tRemoves the compiled files\n" printf "lint\t\tLint the entire project using clang-tidy\n" printf "format\t\tFormat the entire project using clang-format\n" printf "build\t\tBuilds the whole project (cross+clean)\n" printf "test\t\tRuns the Melvix unit tests with QEMU (cross+clean+build)\n" printf "debug\t\tEmulates Melvix with QEMU and debug options (cross+clean+build)\n" printf "again\t\tOpens QEMU again using the previous build\n" printf "disasm\t\tDisassembles a given part of Melvix\n" printf "addr\t\tResolves an address to a line of code\n" printf "cloc\t\tCount the total lines of code\n" printf "sync\t\tSyncs the 'tags' and 'compile_commands.json' file\n" printf "disk\t\tPrepares the userspace disk (e.g. fonts)\n" printf "*\t\tAnything else prints this help\n" printf "\t\tWhen no option is set, Melvix gets built and emulated using QEMU (cross+clean+build)\n\n" echo "You can set the environment variable 'MELVIX_CONFIG' to a config group defined in '.build.mk'" echo "Set the environment variable 'MELVIX_MAKE=\"CONFIG_FOO=bar\"' to override build configs" fi