#! /bin/sh # # Necessary preparations/configurations for the reproducible project. # # Copyright (C) 2018-2020 Mohammad Akhlaghi # # This script is part of Maneage. Maneage is free software: you can # redistribute it and/or modify it under the terms of the GNU General # Public License as published by the Free Software Foundation, either # version 3 of the License, or (at your option) any later version. # # Maneage is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # more details. See . # Script settings # --------------- # Stop the script if there are any errors. set -e # Internal directories # -------------------- # # These are defined to help make this script more readable. topdir=$(pwd) optionaldir="/optional/path" adir=reproduce/analysis/config cdir=reproduce/software/config pconf=$cdir/LOCAL.conf ptconf=$cdir/LOCAL_tmp.conf poconf=$cdir/LOCAL_old.conf depverfile=$cdir/versions.conf depshafile=$cdir/checksums.conf # --------- Delete for no Gnuastro --------- glconf=$adir/gnuastro/gnuastro-local.conf # ------------------------------------------ # Notice for top of generated files # --------------------------------- # # In case someone opens the files output from the configuration scripts in # a text editor and wants to edit them, it is important to let them know # that their changes are not going to be permenant. create_file_with_notice () { if echo "# IMPORTANT: file can be RE-WRITTEN after './project configure'" > "$1" then echo "#" >> "$1" echo "# This file was created during configuration" >> "$1" echo "# ('./project configure'). Therefore, it is not under" >> "$1" echo "# version control and any manual changes to it will be" >> "$1" echo "# over-written if the project re-configured." >> "$1" echo "#" >> "$1" else echo; echo "Can't write to $1"; echo; exit 1 fi } # Get absolute address # -------------------- # # Since the build directory will go into a symbolic link, we want it to be # an absolute address. With this function we can make sure of that. absolute_dir () { if stat "$1" 1> /dev/null; then echo "$(cd "$(dirname "$1")" && pwd )/$(basename "$1")" else exit 1; fi } # Check file permission handling (POSIX-compatibility) # ---------------------------------------------------- # # Check if a `given' directory handles permissions as expected. # # This is to prevent a known bug in the NTFS filesystem that prevents # proper installation of Perl, and probably some other packages. This # function receives the directory as an argument and then, creates a dummy # file, and examines whether the given directory handles the file # permissions as expected. # # Returns `0' if everything is fine, and `255' otherwise. Choosing `0' is # to mimic the `$ echo $?' behavior, while choosing `255' is to prevent # misunderstanding 0 and 1 as true and false. # # ===== CAUTION! ===== # # # Since there is a `set -e' before running this function, the whole script # stops and exits IF the `check_permission' (or any other function) returns # anything OTHER than `0'! So, only use this function as a test. Here's a # minimal example: # # if $(check_permission $some_directory) ; then # echo "yay"; else "nay"; # fi ; check_permission () { # Make a `junk' file, activate its executable flag and record its # permissions generally. local junkfile=$1/check_permission_tmp_file rm -f $junkfile echo "Don't let my short life go to waste" > $junkfile chmod +x $junkfile local perm_before=$(ls -l $junkfile | awk '{print $1}') # Now, remove the executable flag and record the permissions. chmod -x $junkfile local perm_after=$(ls -l $junkfile | awk '{print $1}') # Clean up before leaving the function rm -f $junkfile # If the permissions are equal, the filesystem doesn't allow # permissions. if [ $perm_before = $perm_after ]; then # Setting permission FAILED return 1 else # Setting permission SUCCESSFUL return 0 fi } # Check for C/C++ compilers # ------------------------- # # To build the software, we'll need some basic tools (the compilers in # particular) to be present. hascc=0; if type cc > /dev/null 2>/dev/null; then if type c++ > /dev/null 2>/dev/null; then export CC=cc; hascc=1; fi else if type gcc > /dev/null 2>/dev/null; then if type g++ > /dev/null 2>/dev/null; then export CC=gcc; hascc=1; fi else if type clang > /dev/null 2>/dev/null; then if type clang++ > /dev/null 2>/dev/null; then export CC=clang; hascc=1; fi fi fi fi if [ $hascc = 0 ]; then cat < $testsource < #include int main(void){printf("...C compiler works.\n"); return EXIT_SUCCESS;} EOF if $CC $testsource -o$testprog && $testprog; then rm $testsource $testprog else rm $testsource cat < $testsource < #include int main(void) {return EXIT_SUCCESS;} EOF if $CC $testsource -o$testprog -Wl,-rpath-link 2>/dev/null > /dev/null; then export rpath_command="-Wl,-rpath-link=$instdir/lib" else export rpath_command="" fi rm -f $testprog $testsource # See if we need the dynamic-linker (-ldl) # ---------------------------------------- # # Some programs (like Wget) need dynamic loading (using `libdl'). On # GNU/Linux systems, we'll need the `-ldl' flag to link such programs. But # Mac OS doesn't need any explicit linking. So we'll check here to see if # it is present (thus necessary) or not. cat > $testsource < #include int main(void) { void *handle=dlopen ("/lib/CEDD_LIB.so.6", RTLD_LAZY); return 0; } EOF if $CC $testsource -o$testprog 2>/dev/null > /dev/null; then needs_ldl=no; else needs_ldl=yes; fi rm -f $testprog $testsource # See if the C compiler can build static libraries # ------------------------------------------------ # # We are manually only working with shared libraries: because some # high-level programs like Wget and cURL need dynamic linking and if we # build the libraries statically, our own builds will be ignored and these # programs will go and find their necessary libraries on the host system. # # Another good advantage of shared libraries is that we can actually use # the shared library tool of the system (`ldd' with GNU C Library) and see # exactly where each linked library comes from. But in static building, # unless you follow the build closely, its not easy to see if the source of # the library came from the system or our build. static_build=no # If we are on a Mac OS system # ---------------------------- # # For the time being, we'll use the existance of `otool' to see if we are # on a Mac OS system or not. Some tools (for example OpenSSL) need to know # this. # # On Mac OS, the building of GCC crashes sometimes while building libiberty # with CLang's `g++'. Until we find a solution, we'll just use the host's C # compiler. if type otool > /dev/null 2>/dev/null; then host_cc=1 on_mac_os=yes cat <&1 \ | tr ' ' '\n' \ | awk '/\-\-target/' \ | sed -e's/\-\-target=//') if [ x"$gcctarget" != x ]; then if [ -f /usr/lib/$gcctarget/libc.a ]; then export sys_library_path=/usr/lib/$gcctarget export sys_cpath=/usr/include/$gcctarget fi fi # For a check: #echo "sys_library_path: $sys_library_path" #echo "sys_cpath: $sys_cpath" fi # See if a link-able static C library exists # ------------------------------------------ # # A static C library and the `sys/cdefs.h' header are necessary for # building GCC. if [ x"$host_cc" = x0 ]; then echo; echo; echo "Checking if static C library is available..."; cat > $testsource < #include #include int main(void){printf("...yes\n"); return EXIT_SUCCESS;} EOF cc_call="$CC $testsource $CPPFLAGS $LDFLAGS -o$testprog -static -lc" if $cc_call && $testprog; then gccwarning=0 rm $testsource $testprog else echo; echo "Compilation command:"; echo "$cc_call" rm $testsource gccwarning=1 host_cc=1 cat < /dev/null 2>/dev/null; then hasfc=1; fi if [ $hasfc = 0 ]; then cat < $testsource echo " END" >> $testsource if gfortran $testsource -o$testprog && $testprog; then rm $testsource $testprog else rm $testsource cat < /dev/null 2>/dev/null; then name=$(which wget) # By default Wget keeps the remote file's timestamp, so we'll have # to disable it manually. downloader="$name --no-use-server-timestamps -O"; elif type curl > /dev/null 2>/dev/null; then name=$(which curl) # - cURL doesn't keep the remote file's timestamp by default. # - With the `-L' option, we tell cURL to follow redirects. downloader="$name -L -o" else cat < /dev/null; then instring="the already existing" bdir=$(absolute_dir $build_dir) rm -rf $build_dir/$junkname else echo " ** Can't write in '$build_dir'"; echo fi else if mkdir $build_dir 2> /dev/null; then instring="the newly created" bdir=$(absolute_dir $build_dir) else echo " ** Can't create '$build_dir'"; echo fi fi # If its given, make sure it isn't a subdirectory of the source # directory. if ! [ x"$bdir" = x ]; then if echo "$bdir/" \ | grep '^'$currentdir 2> /dev/null > /dev/null; then # If it was newly created, it will be empty, so delete it. if ! [ "$(ls -A $bdir)" ]; then rm --dir $bdir; fi # Inform the user that this is not acceptable and reset `bdir'. bdir= echo " ** The build-directory cannot be under the source-directory." fi fi # If everything is fine until now, see if we're able to manipulate # file permissions. if ! [ x"$bdir" = x ]; then if ! $(check_permission $bdir); then bdir= echo " ** File permissions can't be modified in this directory" fi fi # If the build directory was good, the loop will stop, if not, # reset `build_dir' to blank, so it continues asking for another # directory and let the user know that they must select a new # directory. if [ x$bdir = x ]; then build_dir= echo " ** Please select another directory." echo "" else echo " -- Build directory set to ($instring): '$bdir'" fi done fi # Input directory # --------------- if [ x"$input_dir" = x ]; then indir=$optionaldir else indir=$input_dir fi wfpc2name=$(awk '!/^#/ && $1=="WFPC2IMAGE" {print $3}' $adir/INPUTS.conf) wfpc2md5=$(awk '!/^#/ && $1=="WFPC2MD5" {print $3}' $adir/INPUTS.conf) wfpc2size=$(awk '!/^#/ && $1=="WFPC2SIZE" {print $3}' $adir/INPUTS.conf) wfpc2url=$(awk '!/^#/ && $1=="WFPC2URL" {print $3}' $adir/INPUTS.conf) if [ $rewritepconfig = yes ] && [ x"$input_dir" = x ]; then cat <> $pconf else # Read the values from existing configuration file. inbdir=$(awk '$1=="BDIR" {print $3}' $pconf) # Read the software directory. ddir=$(awk '$1=="DEPENDENCIES-DIR" {print $3}' $pconf) # The downloader command may contain multiple elements, so we'll just # change the (in memory) first and second tokens to empty space and # write the full line (the original file is unchanged). downloader=$(awk '$1=="DOWNLOADER" {$1=""; $2=""; print $0}' $pconf) # Make sure all necessary variables have a value err=0 verr=0 novalue="" if [ x"$inbdir" = x ]; then novalue="BDIR, "; fi if [ x"$downloader" = x ]; then novalue="$novalue"DOWNLOADER; fi if [ x"$novalue" != x ]; then verr=1; err=1; fi # Make sure `bdir' is an absolute path and it exists. berr=0 ierr=0 bdir=$(absolute_dir $inbdir) if ! [ -d $bdir ]; then if ! mkdir $bdir; then berr=1; err=1; fi; fi if [ $err = 1 ]; then cat <> $glconf echo " minmapsize $minmapsize" >> $glconf echo >> $glconf echo "# Version of Gnuastro that must be used." >> $glconf echo " onlyversion $gversion" >> $glconf else ingversion=$(awk '$1=="onlyversion" {print $NF}' $glconf) if [ x$ingversion != x$gversion ]; then cat <8GB) is large enough for the parallel building of the software. # # For the name of the directory under `/dev/shm' (for this project), we'll # use the names of the two parent directories to the current/running # directory, separated by a `-' instead of `/'. We'll then appended that # with the user's name (in case multiple users may be working on similar # project names). Maybe later, we can use something like `mktemp' to add # random characters to this name and make it unique to every run (even for # a single user). tmpblddir=$sdir/build-tmp rm -rf $tmpblddir/* $tmpblddir # If its a link, we need to empty its # contents first, then itself. # Set the top-level shared memory location. if [ -d /dev/shm ]; then shmdir=/dev/shm else shmdir="" fi # If a shared memory mounted directory exists and there is enough space # there (in RAM), build a temporary directory for this project. needed_space=2000000 if [ x"$shmdir" != x ]; then available_space=$(df $shmdir | awk 'NR==2{print $4}') if [ $available_space -gt $needed_space ]; then dirname=$(pwd | sed -e's/\// /g' \ | awk '{l=NF-1; printf("%s-%s",$l, $NF)}') tbshmdir=$shmdir/"$dirname"-$(whoami) if ! [ -d $tbshmdir ]; then mkdir $tbshmdir; fi fi else tbshmdir="" fi # If a shared memory directory was created set `build-tmp' to be a # symbolic link to it. Otherwise, just build the temporary build # directory under the project build directory. if [ x$tbshmdir = x ]; then mkdir $tmpblddir; else ln -s $tbshmdir $tmpblddir; fi # Inform the user that the build process is starting # ------------------------------------------------- if [ $printnotice = yes ]; then tsec=10 cat < /dev/null; then if [ $jobs = 0 ]; then numthreads=$(nproc --all); else numthreads=$jobs fi else numthreads=1; fi # Build `flock' before other program # ---------------------------------- # # Flock (or file-lock) is a unique program that is necessary to serialize # the (generally parallel) processing of make when necessary. GNU/Linux # machines have it as part of their `util-linux' programs. But to be # consistent in non-GNU/Linux systems, we will be using our own build. # # The reason that `flock' is sepecial is that we need it to serialize the # download process of the software tarballs. flockversion=$(awk '/flock-version/{print $3}' $depverfile) flockchecksum=$(awk '/flock-checksum/{print $3}' $depshafile) flocktar=flock-$flockversion.tar.gz flockurl=http://github.com/discoteq/flock/releases/download/v$flockversion/ # Prepare/download the tarball. if ! [ -f $tardir/$flocktar ]; then flocktarname=$tardir/$flocktar ucname=$flocktarname.unchecked if [ -f $ddir/$flocktar ]; then cp $ddir/$flocktar $ucname else if ! $downloader $ucname $flockurl/$flocktar; then rm -f $ucname; echo echo "DOWNLOAD ERROR: Couldn't download the 'flock' tarball:" echo " $flockurl" echo echo "You can manually place it in '$ddir' to avoid downloading." exit 1 fi fi # Make sure this is the correct tarball. if type sha512sum > /dev/null 2>/dev/null; then checksum=$(sha512sum "$ucname" | awk '{print $1}') if [ x$checksum = x$flockchecksum ]; then mv "$ucname" "$flocktarname" else echo "ERROR: Non-matching checksum for '$flocktar'."; exit 1 fi; else mv "$ucname" "$flocktarname" fi fi # If the tarball is newer than the (possibly existing) program (the version # has changed), then delete the program. if [ -f .local/bin/flock ]; then if [ $tardir/$flocktar -nt $ibidir/flock ]; then rm $ibidir/flock fi fi # Build `flock' if necessary. if ! [ -f $ibidir/flock ]; then cd $tmpblddir tar xf $tardir/$flocktar cd flock-$flockversion ./configure --prefix=$instdir make make install cd $topdir rm -rf $tmpblddir/flock-$flockversion echo "Discoteq flock $flockversion" > $ibidir/flock fi # Paths needed by the host compiler (only for `basic.mk') # ------------------------------------------------------- # # At the end of the basic build, we need to build GCC. But GCC will build # in multiple phases, making its own simple compiler in order to build # itself completely. The intermediate/simple compiler doesn't recognize # some system specific locations like `/usr/lib/ARCHITECTURE' that some # operating systems use. We thus need to tell the intermediate compiler # where its necessary libraries and headers are. if [ x"$sys_library_path" != x ]; then if [ x"$LIBRARY_PATH" = x ]; then export LIBRARY_PATH="$sys_library_path" else export LIBRARY_PATH="$LIBRARY_PATH:$sys_library_path" fi if [ x"$CPATH" = x ]; then export CPATH="$sys_cpath" else export CPATH="$CPATH:$sys_cpath" fi fi # Build basic software # -------------------- # # When building these software we don't have our own un-packing software, # Bash, Make, or AWK. In this step, we'll install such low-level basic # tools, but we have to be very portable (and use minimal features in all). echo; echo "Building necessary software (if necessary)..." make -k -f reproduce/software/make/basic.mk \ sys_library_path=$sys_library_path \ rpath_command=$rpath_command \ static_build=$static_build \ numthreads=$numthreads \ needs_ldl=$needs_ldl \ on_mac_os=$on_mac_os \ host_cc=$host_cc \ -j$numthreads # All other software # ------------------ # # We will be making all the dependencies before running the top-level # Makefile. To make the job easier, we'll do it in a Makefile, not a # script. Bash and Make were the tools we need to run Makefiles, so we had # to build them in this script. But after this, we can rely on Makefiles. if [ $jobs = 0 ]; then numthreads=$($instdir/bin/nproc --all) else numthreads=$jobs fi .local/bin/env -i HOME=$bdir \ .local/bin/make -k -f reproduce/software/make/high-level.mk \ sys_library_path=$sys_library_path \ rpath_command=$rpath_command \ static_build=$static_build \ numthreads=$numthreads \ on_mac_os=$on_mac_os \ sys_cpath=$sys_cpath \ host_cc=$host_cc \ -j$numthreads # Make sure TeX Live installed successfully # ----------------------------------------- # # TeX Live is managed over the internet, so if there isn't any, or it # suddenly gets cut, it can't be built. However, when TeX Live isn't # installed, the project can do all its processing independent of it. It # will just stop at the stage when all the processing is complete and it is # only necessary to build the PDF. So we don't want to stop the project's # configuration and building if its not present. if [ -f $itidir/texlive-ready-tlmgr ]; then texlive_result=$(cat $itidir/texlive-ready-tlmgr) else texlive_result="NOT!" fi if [ x"$texlive_result" = x"NOT!" ]; then cat <0 { \ c++; \ if(c==1) \ { \ if('$num'==1) printf("%s", $0); \ else printf("%s", $0); \ } \ else if(c=='$num') printf(" and %s\n", $0); \ else printf(", %s", $0) \ }' fi } # Report the different software in separate contexts (separating Python and # TeX packages from the C/C++ programs and libraries). proglibs=$(prepare_name_version $verdir/proglib/*) pymodules=$(prepare_name_version $verdir/python/*) texpkg=$(prepare_name_version $verdir/tex/texlive) # Write them as one paragraph for LaTeX. pkgver=$mtexdir/dependencies.tex .local/bin/echo "This research was done with the following free" > $pkgver .local/bin/echo "software programs and libraries: $proglibs." >> $pkgver if [ x"$pymodules" != x ]; then .local/bin/echo "Within Python, the following modules" >> $pkgver echo "were used: $pymodules." >> $pkgver fi .local/bin/echo "The \LaTeX{} source of the paper was compiled" >> $pkgver .local/bin/echo "to make the PDF using the following packages:" >> $pkgver .local/bin/echo "$texpkg. We are very grateful to all their" >> $pkgver .local/bin/echo "creators for freely providing this necessary" >> $pkgver .local/bin/echo "infrastructure. This research (and many " >> $pkgver .local/bin/echo "others) would not be possible without them." >> $pkgver # Prepare the BibTeX entries for the used software (if there are any). hasentry=0 bibfiles="$ictdir/*" for f in $bibfiles; do if [ -f $f ]; then hasentry=1; break; fi; done; # Make sure we start with an empty output file. pkgbib=$mtexdir/dependencies-bib.tex echo "" > $pkgbib # Fill it in with all the BibTeX entries in this directory. We'll just # avoid writing any comments (usually copyright notices) and also put an # empty line after each file's contents to make the output more readable. if [ $hasentry = 1 ]; then for f in $bibfiles; do awk '!/^%/{print} END{print ""}' $f >> $pkgbib done fi # Clean the temporary build directory # --------------------------------- # # By the time the script reaches here the temporary software build # directory should be empty, so just delete it. Note `tmpblddir' may be a # symbolic link to shared memory. So, to work in any scenario, first delete # the contents of the directory (if it has any), then delete `tmpblddir'. .local/bin/rm -rf $tmpblddir/* $tmpblddir # Register successful completion # ------------------------------ echo `.local/bin/date` > $finaltarget # Final notice # ------------ # # The configuration is now complete, we can inform the user on the next # step(s) to take. if [ x$reproducible_paper_group_name = x ]; then buildcommand="./project make -j8" else buildcommand="./project make --group=$reproducible_paper_group_name -j8" fi cat <