qmckl/tools/Building.org

14 KiB

Building tools

This file contains all the tools needed to build the QMCkl library.

Helper functions

if [[ $(basename ${PWD}) != "src" ]] ; then
  echo "This script needs to be run in the src directory"
  exit -1
fi
https://github.com/trex-coe/qmckl/issues
https://trex-coe.github.io/qmckl
BSD 3-Clause License

Copyright (c) 2020, TREX Center of Excellence
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
 list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
 this list of conditions and the following disclaimer in the documentation
 and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
 contributors may be used to endorse or promote products derived from
 this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Makefile

This is the main Makefile invoked by the make command. The Makefile compiling the library is Makefile.generated, and is generated by the script detailed in the next section.

Dependencies

LIBS=-lpthread

Variables

QMCKL_ROOT=$(shell dirname $(CURDIR))

shared_lib=$(QMCKL_ROOT)/lib/libqmckl.so
static_lib=$(QMCKL_ROOT)/lib/libqmckl.a
qmckl_h=$(QMCKL_ROOT)/include/qmckl.h
qmckl_f=$(QMCKL_ROOT)/share/qmckl/fortran/qmckl_f.f90

export CC CFLAGS FC FFLAGS LIBS QMCKL_ROOT

ORG_SOURCE_FILES=$(wildcard *.org)
C_SOURCE_FILES=$(patsubst %.org,%.c,$(ORG_SOURCE_FILES))
INCLUDE=-I$(QMCKL_ROOT)/include/

Compiler options

GNU, Intel and LLVM compilers are supported. Choose here:

COMPILER=GNU
#COMPILER=INTEL
#COMPILER=LLVM

GNU

ifeq ($(COMPILER),GNU)
#----------------------------------------------------------
CC=gcc -g
CFLAGS=-fPIC  $(INCLUDE) \
-fexceptions -Wall -Werror -Wpedantic -Wextra -fmax-errors=3

FC=gfortran -g
FFLAGS=-fPIC  $(INCLUDE) \
-fcheck=all -Waliasing -Wampersand -Wconversion -Wsurprising \
-Wintrinsics-std -Wno-tabs -Wintrinsic-shadow -Wline-truncation \
-Wreal-q-constant -Wuninitialized  -fbacktrace -finit-real=nan \
-ffpe-trap=zero,overflow,underflow

LIBS+=-lgfortran -lm
#----------------------------------------------------------
endif

Intel

ifeq ($(COMPILER),INTEL)
#----------------------------------------------------------
CC=icc -xHost
CFLAGS=-fPIC -g -O2 $(INCLUDE)

FC=ifort -xHost
FFLAGS=-fPIC -g -O2 $(INCLUDE)

LIBS+=-lm -lifcore -lirc
#----------------------------------------------------------
CC=icc -xHost
endif

LLVM

ifeq ($(COMPILER),LLVM)
#----------------------------------------------------------
CC=clang
CFLAGS=-fPIC -g -O2 $(INCLUDE)

FC=flang
FFLAGS=fPIC -g -O2 $(INCLUDE)

LIBS+=-lm
#----------------------------------------------------------
endif

Rules

The source files are created during the generation of the file Makefile.generated. The Makefile.generated is the one that will be distributed with the library.

.PHONY: clean shared static doc all check install uninstall
.SECONDARY: # Needed to keep the produced C and Fortran files

$(shared_lib) $(static_lib) install uninstall: $(qmckl_h) $(qmckl_f) Makefile.generated
$(MAKE) -f Makefile.generated $@

$(qmckl_f) $(qmckl_h): Makefile.generated
$(QMCKL_ROOT)/tools/build_qmckl_h.sh

shared: $(shared_lib)
static: $(static_lib)
all: shared static doc check

check: $(static_lib)
$(MAKE) -f Makefile.generated check

doc: $(ORG_SOURCE_FILES)
$(QMCKL_ROOT)/tools/build_doc.sh

clean:
- $(MAKE) -f Makefile.generated clean
- $(RM)	test_qmckl_* test_qmckl.c \
$(qmckl_h) $(qmckl_f) \
qmckl_*.f90 qmckl_*.c qmckl_*.h \
Makefile.generated *.html 

Makefile.generated: Makefile $(QMCKL_ROOT)/tools/create_makefile.sh  $(ORG_SOURCE_FILES)
$(QMCKL_ROOT)/tools/create_makefile.sh


.SUFFIXES: .org .c

.org.c:
$(QMCKL_ROOT)/tools/tangle.sh $<

Script to tangle the org-mode files

# <<header()>>

<<check_src>>

This file needs to be run from the QMCKL src directory.

It tangles all the files in the directory. It uses the config_tangle.el file, which contains information required to compute the current file names using for example (eval c) to get the name of the produced C file.

The file is not tangled if the last modification date of the org file is less recent than one of the tangled files.

function tangle()
{
  local org_file=$1
  local c_file=${org_file%.org}.c
  local f_file=${org_file%.org}.f90

  if [[ ${org_file} -ot ${c_file} ]] ; then
      return
  elif [[ ${org_file} -ot ${f_file} ]] ; then
      return
  fi
  emacs --batch ${org_file} --load=../tools/config_tangle.el -f org-babel-tangle
}

for i in $@
do
  echo "--- ${i} ----"
  tangle ${i}
done

Script to generate auto-generated Makefile

This script generates the Makefile that compiles the library. The OUTPUT variable contains the name of the generated Makefile,typically Makefile.generated.

# <<header()>>

<<check_src>>

OUTPUT=Makefile.generated

We start by tangling all the org-mode files.

${QMCKL_ROOT}/tools/tangle.sh *.org
${QMCKL_ROOT}/tools/build_qmckl_h.sh

Then we create the list of *.o files to be created, for library functions:

OBJECTS="qmckl_f.o"
for i in $(ls qmckl_*.c qmckl_*f.f90) ; do
  FILE=${i%.*}
  OBJECTS+=" ${FILE}.o"
done >> $OUTPUT

for tests in C:

TESTS=""
for i in $(ls test_qmckl_*.c) ; do
  FILE=${i%.c}
  TESTS+=" ${FILE}.o"
done >> $OUTPUT

and for tests in Fortran:

TESTS_F=""
for i in $(ls test_qmckl_*_f.f90) ; do
  FILE=${i%.f90}
  TESTS_F+=" ${FILE}.o"
done >> $OUTPUT

Finally, we append the rules to the Makefile

cat << EOF > ${OUTPUT}
.POSIX:
.SUFFIXES:

prefix=/usr/local

CC=$CC
CFLAGS=$CFLAGS -I../munit/

FC=$FC
FFLAGS=$FFLAGS

OBJECT_FILES=$OBJECTS
TESTS=$TESTS
TESTS_F=$TESTS_F

LIBS=$LIBS

QMCKL_ROOT=\$(shell dirname \$(CURDIR))
shared_lib=\$(QMCKL_ROOT)/lib/libqmckl.so
static_lib=\$(QMCKL_ROOT)/lib/libqmckl.a
qmckl_h=\$(QMCKL_ROOT)/include/qmckl.h
qmckl_f=\$(QMCKL_ROOT)/share/qmckl/fortran/qmckl_f.f90
munit=\$(QMCKL_ROOT)/munit/munit.c 

shared: \$(shared_lib)
static: \$(static_lib)
all: shared static

\$(shared_lib): \$(OBJECT_FILES)
\$(CC) -shared \$(OBJECT_FILES) -o \$(shared_lib)

\$(static_lib): \$(OBJECT_FILES)
\$(AR) rcs \$(static_lib) \$(OBJECT_FILES)


# Test

qmckl_f.o: \$(qmckl_f)
\$(FC) \$(FFLAGS) -c \$(qmckl_f) -o \$@

test_qmckl: test_qmckl.c \$(qmckl_h) \$(static_lib) \$(TESTS) \$(TESTS_F)
\$(CC) \$(CFLAGS) \
\$(munit) \$(TESTS) \$(TESTS_F) \$(static_lib) \$(LIBS) test_qmckl.c -o \$@

test_qmckl_shared: test_qmckl.c \$(qmckl_h) \$(shared_lib) \$(TESTS) \$(TESTS_F)
\$(CC) \$(CFLAGS) -Wl,-rpath,\$(QMCKL_ROOT)/lib -L\$(QMCKL_ROOT)/lib \
\$(munit) \$(TESTS) \$(TESTS_F) -lqmckl \$(LIBS) test_qmckl.c -o \$@

check: test_qmckl test_qmckl_shared
./test_qmckl

clean:
\$(RM) -- *.o *.mod \$(shared_lib) \$(static_lib) test_qmckl

install:
install -d \$(prefix)/lib
install -d \$(prefix)/include
install -d \$(prefix)/share/qmckl/fortran
install -d \$(prefix)/man
install \$(shared_lib) \$(prefix)/lib
install \$(static_lib) \$(prefix)/lib
install \$(qmckl_h) \$(prefix)/include
install \$(qmckl_f) \$(prefix)/share/qmckl/fortran

.SUFFIXES: .c .f90 .o

.c.o:
\$(CC) \$(CFLAGS) -c \$*.c -o \$*.o

.f90.o: qmckl_f.o
\$(FC) \$(FFLAGS) -c \$*.f90 -o \$*.o

.PHONY: check clean all
EOF

Script to build the final qmckl.h file

# <<header()>>
------------------------------------------
QMCkl - Quantum Monte Carlo kernel library
------------------------------------------

Documentation : <<url-web()>>
Issues        : <<url-issues()>>

<<license()>>

All the produced header files are concatenated in the qmckl.h file, located in the include directory. The *_private.h files are excluded.

Put .h files in the correct order:

HEADERS=""
for i in $(cat table_of_contents)
do
  HEADERS+="${i%.org}_type.h "
done

for i in $(cat table_of_contents)
do
  HEADERS+="${i%.org}_func.h "
done

Generate C header file

OUTPUT="../include/qmckl.h"

cat << EOF > ${OUTPUT}
/*
,*    <<qmckl-header>>
,*/

#ifndef __QMCKL_H__
#define __QMCKL_H__

#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
EOF

for i in ${HEADERS}
do
  if [[ -f $i ]] ; then
      cat $i >> ${OUTPUT}
  fi
done

cat << EOF >> ${OUTPUT}
#endif
EOF

Generate Fortran interface file from all qmckl_*_fh.f90 files

HEADERS_TYPE="qmckl_*_fh_type.f90"
HEADERS="qmckl_*_fh_func.f90"

OUTPUT="../share/qmckl/fortran/qmckl_f.f90"
cat << EOF > ${OUTPUT}
!
!    <<qmckl-header>>
!
module qmckl
use, intrinsic :: iso_c_binding
EOF

for i in ${HEADERS_TYPE}
do
  cat $i >> ${OUTPUT}
done

for i in ${HEADERS}
do
  cat $i >> ${OUTPUT}
done

cat << EOF >> ${OUTPUT}
end module qmckl
EOF

Script to build the documentation

First define readonly global variables.

readonly DOCS=${QMCKL_ROOT}/docs/
readonly SRC=${QMCKL_ROOT}/src/
readonly HTMLIZE=${DOCS}/htmlize.el
readonly CONFIG_DOC=${QMCKL_ROOT}/tools/config_doc.el  
readonly CONFIG_TANGLE=${QMCKL_ROOT}/tools/config_tangle.el

Check that all the defined global variables correspond to files.

function check_preconditions()
{
  if [[ -z ${QMCKL_ROOT} ]]
  then
      print "QMCKL_ROOT is not defined"
      exit 1
  fi

  for dir in ${DOCS} ${SRC}
  do
      if [[ ! -d ${dir} ]]
      then
          print "${dir} not found"
          exit 2
      fi
  done
  
  for file in ${CONFIG_DOC} ${CONFIG_TANGLE}
  do
      if [[ ! -f ${file} ]]
      then
          print "${file} not found"
          exit 3
      fi
  done
}

install_htmlize installs the htmlize Emacs plugin if the htmlize.el file is not present.

function install_htmlize()
{
  local url="https://github.com/hniksic/emacs-htmlize"
  local repo="emacs-htmlize"
  
  [[ -f ${HTMLIZE} ]] || (
      cd ${DOCS}
      git clone ${url} \
          && cp ${repo}/htmlize.el ${HTMLIZE} \
          && rm -rf ${repo}
      cd -
  )

  # Assert htmlize is installed
  [[ -f ${HTMLIZE} ]] \
      || exit 1
}

Extract documentation from an org-mode file.

function extract_doc()
{
  local org=$1
  local local_html=${SRC}/${org%.org}.html 
  local html=${DOCS}/${org%.org}.html 

  if [[ -f ${html} && ${org} -ot ${html} ]]
  then
      return
  fi
  emacs --batch                    \
        --load ${HTMLIZE}          \
        --load ${CONFIG_DOC}       \
        ${org}                     \
        --load ${CONFIG_TANGLE}    \
        -f org-html-export-to-html 
  mv ${local_html} ${DOCS}

}

The main function of the script.

function main() {
  
  [[ check_preconditions ]] \
      || exit 1
  
  # Install htmlize if needed
  [[ install_htmlize ]] \
      || exit 2

  # Create documentation
  cd ${SRC} \
      || exit 3

  for i in *.org
  do
      echo
      echo "=======  ${i} ======="
      extract_doc ${i}
  done

  if [[ $? -eq 0 ]]
  then
      cd ${DOCS}
      rm -f index.html
      ln README.html index.html
      exit 0
  else
      exit 3
  fi
}
main