#!/usr/bin/env sh # Copyright (c) 2018 - 2022, Martin Scheidt (ISC license) # Permission to use, copy, modify, and/or distribute this file for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. # # Variables in upper case, e.g.: $VARIABLE # Functions in lower case as action with underscore, e.g.: do_something # # Halt on error set -e ## -- print usage print_usage() { cat << EOF Usage: ./build.sh [OPTIONS] install, test or release a package for tikz-trackschematic -h, --help Display this help message. -s, --silent Run script in silent mode. The -s option overrides any previous -v or -d options. -v, --verbose Run script in verbose mode. The -v option overrides any previous -s or -d options. -d, --debug Run script in debug mode. The -d option overrides any previous -s or -v options. -m, --messy Do not clean up afterwards. -n, --noninteractive Run script with no interaction. -i, --install-dev Install as dev-package in local TeX Live environment. The -i option overrides any previous -u option. -u, --uninstall-dev Deinstall dev-package from local TeX Live environment. The -u option overrides any previous -i option. -t, --test Tests the current src/ against the test/. -r, --release VERSION Creates a .zip with the release for given VERSION in Semantic Versioning with leading 'v', e.g: v1.0.0 EOF } ## -- processes getopts VERBOSITY=2 # set by cli argument NOINTERACT=0 # set by cli argument INSTALL=0 # set by cli argument TESTING=0 # set by cli argument RELEASE=0 # set by cli argument CLEANUP=1 # set by cli argument process_arguments() { while true; do # loop condition - test for empty string: if [ -z "$1" ]; then break; fi # loop test case $1 in -h|--help) print_usage exit 0 ;; -s|--silent) VERBOSITY=0 ;; -v|--verbose) VERBOSITY=3 ;; -d|--debug) VERBOSITY=4 ;; -m|--messy) CLEANUP=0 ;; -n|--noninteractive) NOINTERACT=1 ;; -i|--install-dev) INSTALL=1 ;; -u|--uninstall-dev) INSTALL=2 ;; -t|--test) TESTING=1 ;; -r|--release) RELEASE=1 shift if [ -z "$1" ]; then print_usage exit 1 fi if [ ${1:0:1} = "-" ]; then print_usage exit 1 fi VERSION_STR=$1 ;; *) print_usage exit 1 ;; esac shift done } ## -- run variables # PDFTOPPM_CONVERT=0 # set by check_pdftoppm POLICY_MOD=0 # set by check_imagemagick_policy # ERROR_OCCURRED=0 ## -- logging functions ## colors RED="\033[0;31m" GREEN="\033[0;32m" YELLOW="\033[0;33m" COLOR_RESET="\033[0;m" ## cross platform echo option if [ "`echo -n`" = "-n" ]; then n=""; c="\c" else n="-n"; c="" fi log() { NO_LINE_BREAK=0 COLOR=${COLOR_RESET} if [ ${2:0:2} = "-n" ]; then NO_LINE_BREAK=1 fi if [ $1 = 1 ]; then COLOR=${RED} fi if [ $1 = 2 ]; then COLOR=${GREEN} fi if [ $1 = 4 ]; then COLOR=${YELLOW} fi if [ $VERBOSITY -ge $1 ]; then shift if [ $NO_LINE_BREAK = 0 ]; then echo "${COLOR}$@${COLOR_RESET}" | fold -s else shift echo $n "${COLOR}$@ $c${COLOR_RESET}" | fold -s fi fi } log_show() { log 0 $@; } # will always show log_error() { log 1 $@; } # print message in RED ; but not when --silent log_info() { log 2 $@; } # print message in GREEN ; but not when --silent log_note() { log 3 $@; } # print message ; with --verbose and --debug log_debug() { log 4 $@; } # print message in YELLOW; only with --debug ## -- commands check_path() { # test for project specific files STATUS=0 set -- doc/tikz-trackschematic-documentation.sty src/tikz-trackschematic.sty test/turnout.tex for FILE in "$@"; do if [ ! -e $FILE ]; then STATUS=1 fi done if [ $STATUS = 0 ]; then log_note "Build script is within the project folder." return 0 fi log_error "Please run this script from inside the project folder!" exit 1 } ## checks for installed software sedi () { ## cross platform sed -i option without a backup file name # https://stackoverflow.com/questions/2320564/sed-i-command-for-in-place-editing-to-work-with-both-gnu-sed-and-bsd-osx sed --version >/dev/null 2>&1 && sed -i -- "$@" || sed -i "" "$@" } check_zip() { # check for zip STATUS=0 command -v zip >/dev/null 2>&1 || STATUS=1 if [ $STATUS = 0 ]; then log_note "zip found" return 0 fi log_error "Program 'zip' not found. Be sure to have zip installed!" exit 1 } check_sudo() { # checks if sudo is available STATUS=0 rootrun="" # If we are root, we do note require sudo if [ "$EUID" = 0 ]; then log_note "you are root" return 0 fi command -v sudo >/dev/null 2>&1 || STATUS=1 if [ $STATUS = 0 ]; then log_note "sudo found" rootrun="sudo" return 0 else log_debug "sudo failed." fi log_error "This script must be run as root!" exit 1 } check_texlive() { # check for kpsewhich (and mktexlsr) STATUS=0 command -v kpsewhich >/dev/null 2>&1 || STATUS=1 command -v mktexlsr >/dev/null 2>&1 || STATUS=1 if [ $STATUS = 0 ]; then log_note "kpsewhich and mktexlsr found" TEXMFLOCAL=$(kpsewhich --var-value TEXMFLOCAL) return 0 fi log_error "Program 'kpsewhich' or 'mktexlsr' not found. Be sure to use texlive or mactex!" exit 1 } check_pdflatex() { # check for pdflatex STATUS=0 command -v pdflatex >/dev/null 2>&1 || STATUS=1 if [ $STATUS = 0 ]; then log_note "pdflatex found" return 0 fi log_error "Program 'pdflatex' not found. Be sure to have texlive or mactex installed!" exit 1 } check_imagemagick() { # check for ImageMagick/compare STATUS=0 command -v compare >/dev/null 2>&1 || STATUS=1 if [ $STATUS = 0 ]; then log_note "compare found" return 0 fi log_error "Program 'compare' not found. Be sure to have ImageMagick installed!" exit 1 } check_pdftoppm() { # check for poppler/pdftoppm STATUS=0 command -v pdftoppm >/dev/null 2>&1 || STATUS=1 if [ $STATUS = 0 ]; then log_note "pdftoppm found" PDFTOPPM_CONVERT=1 return 0 fi log_error "Program 'pdftoppm' not found." # no # exit 1 ## can still modify ImageMagick policy! } check_imagemagick_policy() { STATUS=1 convert -list policy | grep -q "pattern: PDF" || STATUS=0 if [ $STATUS = 0 ]; then log_note "ImageMagick allows to convert PDFs. Great!" return 0 else log_note "ImageMagick does not allow to convert PDFs." ## check for alternative check_pdftoppm # if pdftoppm is available, then PDFTOPPM_CONVERT=1 if [ $PDFTOPPM_CONVERT = 0 ]; then ## modify ImageMagick-6/policy.xml if [ $NOINTERACT = 0 ]; then log_show "Be sure to have either poppler(-utils) installed or an ImageMagick \ policy which allows for PDF conversion! Do you wish to temporaly remove \ the policy preventing ImageMagick from converting PDFs?" log_show -n "(y/n)" while true; do read -p "" answer case $answer in [Yy]* ) break;; [Nn]* ) exit 1;; * ) log_show "Please answer yes or no.";; esac done fi check_sudo POLICY_PATH=$(convert -list policy | grep "Path" | awk "NR==1" | cut -d " " -f2) # default /etc/ImageMagick-*/policy.xml if [ ! -e $POLICY_PATH ]; then VERSION=$(convert --version | grep "Version" | cut -d " " -f3 | cut -d "." -f1 ) POLICY_PATH="/etc/ImageMagick-${VERSION}/policy.xml" if [ ! -e $POLICY_PATH ]; then log_error "ImageMagick policy is preventing converting PDFs to PNGs and program \ 'pdftoppm' was not found. Modifying the policy temporaly failed! Be sure \ to have either poppler(-utils) installed or an ImageMagick policy which \ allows for PDF conversion!" exit 1 fi fi POLICY_MOD=1 $rootrun sed -i".backup" 's/^.*policy.*coder.*none.*PDF.*//' $POLICY_PATH log_note "Modified ${POLICY_PATH}!" fi fi } check_trackschematic() { # check for tikz-trackschematic STATUS=0 TEXMFLOCAL=$(kpsewhich --var-value TEXMFLOCAL) DEVDIR=$(find $TEXMFLOCAL -name 'tikz-trackschematic-dev') ls $DEVDIR/tikz-trackschematic-dev.sty >> /dev/null 2>&1 || STATUS=1 if [ $STATUS = 0 ]; then log_note "tikz-trackschematic-dev found" return 0 fi log_error "Library 'tikz-trackschematic-dev' not found. Be sure to have tikz-trackschematic-dev installed!" exit 1 } ## checks for updated repository check_version_number() { while true; do # loop condition - test format of $VERSION_STR: echo "$VERSION_STR" | egrep -q "v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)?" && break; # loop test if [ $NOINTERACT = 0 ]; then log_error "Your version '$VERSION_STR' has not the correct format!" log_show -n "Please specify as Semantic Versioning ( e.g. v1.0.0 ): " read VERSION_STR else log_error "Your version '$VERSION_STR' has not the correct format! \ Please use Semantic Versioning with leading 'v'" exit 1 fi done # remove leading 'v' VERSION_NUM=$(echo $VERSION_STR | cut -c 2-) } check_versionhistory() { # check if $VERSION is present in doc/versionhistory.tex STATUS=0 grep -qs "vhEntry{$VERSION_NUM" doc/versionhistory.tex || STATUS=1 if [ $STATUS = 0 ]; then log_note "Version $VERSION_NUM is present in versionhistory.tex." return 0 fi log_error "Version $VERSION_NUM not found in versionhistory.tex. \ Be sure to edit versionhistory.tex and specify current version!" exit 1 } check_changelog() { # check if $VERSION is present in CHANGELOG.md STATUS=0 grep -qs "Version \[$VERSION_NUM\]" CHANGELOG.md || STATUS=1 if [ $STATUS = 0 ]; then log_note "Version $VERSION_NUM is present in CHANGELOG.md." return 0 fi log_error "Version $VERSION_NUM not found in CHANGELOG.md. \ Be sure to edit CHANGELOG.md and specify current version!" exit 1 } check_date() { ## extract DATE from versionhistory.tex and CHANGELOG.md LINE_1=$(grep "vhEntry{$VERSION_NUM" doc/versionhistory.tex) LINE_2=$(grep "Version \[$VERSION_NUM\]" CHANGELOG.md) DATEISO_1=$(echo $LINE_1 | egrep -o '[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])') DATEISO_2=$(echo $LINE_2 | egrep -o '[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])') if [ $DATEISO_1 = $DATEISO_2 ]; then # DATE=$(date "+%Y-%m-%d") DATE="$DATEISO_1" log_note "The date $DATE was extracted from versionhistory.tex and CHANGELOG.md." return 0 fi log_error "The date in versionhistory.tex and CHANGELOG.md did not match.\ Be sure to edit versionhistory.tex or CHANGELOG.md and modifiy the date!" exit 1 } check_url1() { ## extract urls from CHANGELOG.md STATUS=0 LINE=$(grep "\[$VERSION_NUM\]: https://" CHANGELOG.md) echo $LINE | grep -qs "...$VERSION_STR" || STATUS=1 if [ $STATUS = 0 ]; then log_note "Version $VERSION_NUM URL is present in CHANGELOG.md." return 0 fi log_error "Version $VERSION_NUM URL was not found in CHANGELOG.md. \ Be sure to edit CHANGELOG.md and specify a URL for the current version!" exit 1 } check_url2() { ## extract urls from CHANGELOG.md STATUS=0 LINE=$(grep "\[Unreleased\]: https://" CHANGELOG.md) echo $LINE | grep -qs "/$VERSION_STR..." || STATUS=1 if [ $STATUS = 0 ]; then log_note "The URL for [Unreleased] was also updated in CHANGELOG.md! Thx!" return 0 fi log_show "WARNING: URL for [Unreleased] in CHANGELOG.md does not reflect the current version $VERSION_NUM." log_show "WARNING: Be sure to edit CHANGELOG.md and specify current version!" if [ $NOINTERACT = 0 ]; then log_show "Do you wish to continue without updated URL for [Unreleased]?" log_show -n "(y/n)" while true; do read -p "" answer case $answer in [Yy]* ) break;; [Nn]* ) exit 1;; * ) log_show "Please answer yes or no.";; esac done else log_error "Aborting in batch mode!" exit 1 fi } ## functionality create_release() { #### # This function produces a .zip-file in accordance to the requirements for CTAN. # For more information see https://ctan.org/help/upload-pkg. #### if [ $NOINTERACT = 0 ]; then log_show "Do you wish to create a release for the version $VERSION_NUM?" log_show -n "(y/n)" while true; do read -p "" answer case $answer in [Yy]* ) break;; [Nn]* ) exit 1;; * ) log_show "Please answer yes or no.";; esac done fi ## create backup-file und update VERSIONDATE in tikz-trackschematic.sty sed -i".backup" -e"s/%\[VERSIONDATE/\[$DATE $VERSION_STR/g" src/tikz-trackschematic.sty sedi "/%%\[SCRIPT\]/d" src/tikz-trackschematic.sty log_note "Updated version in src/tikz-trackschematic.sty" ## -- create zip-archive # create temporary folder TMP="tikz-trackschematic-$VERSION_STR" mkdir -p $TMP # copy README, CHANGELOG, LICENSE and CITATION cp README.md $TMP/README.md cp CHANGELOG.md $TMP/CHANGELOG.md cp LICENSE $TMP/LICENSE cp CITATION.cff $TMP/CITATION.cff # copy and rename documentation cp doc/tikz-trackschematic-documentation.sty $TMP/ cp doc/manual.pdf $TMP/tikz-trackschematic.pdf cp doc/manual.tex $TMP/tikz-trackschematic.tex cp doc/snippets.pdf $TMP/tikz-trackschematic-snippets.pdf cp doc/snippets.tex $TMP/tikz-trackschematic-snippets.tex cp doc/symbology-table.pdf $TMP/tikz-trackschematic-symbology-table.pdf cp doc/symbology-table.tex $TMP/tikz-trackschematic-symbology-table.tex mkdir $TMP/tikz-trackschematic-examples mkdir $TMP/tikz-trackschematic-snippets cp -R doc/examples/* $TMP/tikz-trackschematic-examples/ cp -R doc/snippets/* $TMP/tikz-trackschematic-snippets/ log_note "copied documentation" # copy src-files for SRC in src/*; do EXT=${SRC##*.} # do not copy backup created by sed if [ $EXT != "backup" ]; then cp $SRC $TMP/ fi done log_note "copied src-files" # zip package zip -r $TMP.zip $TMP/* >/dev/null 2>&1 log_note "compressed the release in $TMP.zip" } create_release_notes() { if [ $NOINTERACT = 0 ]; then log_show "Do you wish to create a release notes for the version $VERSION_NUM?" log_show -n "(y/n)" while true; do read -p "" answer case $answer in [Yy]* ) break;; [Nn]* ) exit 1;; * ) log_show "Please answer yes or no.";; esac done fi ## -- create release note as excerpt from CHANGELOG.md # determine beginning and end in CHANGELOG.md TOP=$(grep -n "Version \[$VERSION_NUM\]" CHANGELOG.md | cut -d: -f1) awk "NR>$TOP" CHANGELOG.md > release-note-tmp.md BOTTOM=$(grep -n -m 1 "## Version" release-note-tmp.md | cut -d: -f1) BOTTOM=$(( $TOP + $BOTTOM )) BOTTOM=$(( $BOTTOM - 2 )) TOP=$(( $TOP + 1 )) # extract the excerpt awk "NR>$TOP&&NR<$BOTTOM" CHANGELOG.md > release-note-$VERSION_STR.md sedi "s/###/##/g" release-note-$VERSION_STR.md } run_test_cases() { cd test/ TESTDIR="../test" mkdir -p .tex STATUS=0 # Start with an empty List: FAILED="" for TEST in `ls $TESTDIR/*.tex`; do # setup FILE=$(basename "$TEST") # remove path NAME=${FILE%.*} # remove extension ADD_TO_LIST=0 # log_info "$NAME test:" # # ## TeX build # EXIT_CODE=0 /usr/bin/time -p -o .tex/${NAME}.time \ pdflatex -output-directory=.tex -interaction=NOINTERACT -halt-on-error ${NAME}.tex >> /dev/null 2>&1 || EXIT_CODE=1 # TIME=$(awk "NR==2" .tex/${NAME}.time | cut -d " " -f2) # understanding TeX statistics: # -> https://tex.stackexchange.com/questions/26208/components-of-latexs-memory-usage MEMORY_USAGE=$(grep "words of memory out of" .tex/${NAME}.log | cut -d " " -f2) MEMORY_USAGE=$(($MEMORY_USAGE/1000)) # if [ $EXIT_CODE = 0 ]; then log_info " - build successful in ${TIME}s and with ${MEMORY_USAGE}k memory." else STATUS=1 ADD_TO_LIST=1 log_error " - build failed." fi # # ## compare images with following compare metrics # AE: absolute error count, number of different pixels (-fuzz affected) # DSSIM: structural dissimilarity index # FUZZ: mean color distance # MAE: mean absolute error (normalized), average channel error distance # MEPP: mean error per pixel (normalized mean error, normalized peak error) # MSE: mean error squared, average of the channel error squared # NCC: normalized cross correlation # PAE: peak absolute (normalized peak absolute) # PHASH: perceptual hash for the sRGB and HCLp colorspaces. # PSNR: peak signal to noise ratio # RMSE: root mean squared (normalized root mean squared) # SSIM: structural similarity index # EXIT_CODE=0 if [ $PDFTOPPM_CONVERT = 0 ]; then # 'compare' will convert the pdf to png # unless the policy of ImageMagick prevents it # -> this reasonably fast! compare -metric RMSE -colorspace RGB .tex/${NAME}.pdf ${NAME}_expected.pdf NULL: >> /dev/null 2>&1 || EXIT_CODE=1 else # use 'pdftoppm' convert the pdf to png # then use 'compare' for comparison without converting # -> this is slower! pdftoppm -png -r 144 -singlefile .tex/${NAME}.pdf .tex/${NAME} pdftoppm -png -r 144 -singlefile ${NAME}_expected.pdf .tex/${NAME}_expected compare -metric RMSE -colorspace RGB .tex/${NAME}.png .tex/${NAME}_expected.png NULL: >> /dev/null 2>&1 || EXIT_CODE=1 fi if [ $EXIT_CODE = 0 ]; then log_info " - comparison successful." else STATUS=1 ADD_TO_LIST=1 log_error " - comparison failed." fi ## if a test failed add to list if [ $ADD_TO_LIST = 1 ]; then if [ -z "$FAILED" ]; then # first item FAILED="$NAME" else FAILED="$FAILED, $NAME" fi fi done if [ $STATUS = 0 ]; then log_info "\n=> All tests passed!\n" else log_error "\n=> Some or all tests failed!" log_debug "The following tests failed: ${FAILED}!" ERROR_OCCURRED=1 fi cd .. } link_dev_files() { # destination folder inside the TeX Live installation TEXMFLOCAL=$(kpsewhich --var-value TEXMFLOCAL) DEVDIR="$TEXMFLOCAL/tex/latex/tikz-trackschematic-dev" PROJECTDIR=$(pwd -P) if [ $NOINTERACT = 0 ]; then log_show "Do you wish to install this package for development to $DEVDIR?" log_show -n "(y/n)" while true; do read -p "" answer case $answer in [Yy]* ) break;; [Nn]* ) exit 1;; * ) log_show "Please answer yes or no.";; esac done fi # make sure that destination folder exists if [ ! -d "$DEVDIR" ]; then $rootrun mkdir -p $DEVDIR fi # link every file in src/ and rename it for SRC in src/*; do FILE=$(basename "$SRC") # remove path NAME=${FILE%.*} # remove extension PREFIX=${NAME%%.*} POSTFIX=${NAME#*.} EXT=${SRC##*.} if [ "$PREFIX" = "$POSTFIX" ]; then DST="$PREFIX-dev.$EXT" else DST="$PREFIX-dev.$POSTFIX.$EXT" fi $rootrun ln -sfn $PROJECTDIR/$SRC $DEVDIR/$DST if [ $VERBOSITY = 1 ]; then echo "linked '$DST'" fi done # update TeX Live installation TEXlsr=`which mktexlsr` if [ $VERBOSITY -ge 3 ]; then $rootrun $TEXlsr else $rootrun $TEXlsr --quiet fi } remove_dev_files() { # destination folder inside the TeX Live installation cd $DEVDIR # from check_trackschematic if [ $NOINTERACT = 0 ]; then log_show "Do you wish to remove the package '$DEVDIR'?" log_show -n "(y/n)" while true; do read -p "" answer case $answer in [Yy]* ) break;; [Nn]* ) exit 1;; * ) log_show "Please answer yes or no.";; esac done fi log_note "removing $DEVDIR!" $rootrun rm -f * cd .. $rootrun rmdir tikz-trackschematic-dev # update TeX Live installation TEXlsr=`which mktexlsr` if [ $VERBOSITY -ge 3 ]; then $rootrun $TEXlsr else $rootrun $TEXlsr --quiet fi } cleanup() { ## -- cleanup ## from create_release if [ $CLEANUP = 1 ]; then if [ $RELEASE = 1 ]; then # remove TMP-folder rm -rf $TMP # undo changes to tikz-trackschematic.sty by sed mv src/tikz-trackschematic.sty.backup src/tikz-trackschematic.sty # remove TMP-release-note rm release-note-tmp.md fi ## from check_imagemagick_policy if [ $POLICY_MOD = 1 ]; then # undo changes to /etc/ImageMagick-6/policy.xml by sed $rootrun mv ${POLICY_PATH}.backup $POLICY_PATH fi ## from run_test_cases if [ $TESTING = 1 ]; then # remove TMP-folder rm -rf test/.tex fi ## log_note "Clean up done!" fi } ## -- execution ## Process user arguments process_arguments $@ check_path ## do what is requested if [ $INSTALL = 1 ]; then ## install package check_texlive check_sudo ## link_dev_files fi if [ $INSTALL = 2 ]; then ## deinstall package check_texlive check_trackschematic check_sudo ## remove_dev_files fi if [ $TESTING = 1 ]; then ## check_pdflatex check_trackschematic check_imagemagick check_imagemagick_policy ## run_test_cases fi if [ $RELEASE = 1 ]; then ## check if version ist in the correct format check_version_number ## check if $VERSION is present in CHANGELOG.md and versionhistory.tex check_versionhistory check_changelog check_url1 check_url2 check_date ## check for installed software check_zip ## create_release create_release_notes fi ## cleanup ## if [ $ERROR_OCCURRED = 0 ]; then exit 0 else exit 1 fi #EOF