#!/bin/bash # $Id: make_pem_fcm 2026-04-20 jbclement $ # This is a script in Bash to compile the PEM # It is forked from makelmdz_fcm.sh and adapted to the PEM structure and needs # ##set -x set -e ######################################################################## # options par defaut pour la commande make ######################################################################## dim="64x48x32" physique=mars compil_mod=prod io=ioipsl job=1 full='' clean=false code="pem" arch_defined="FALSE" arch_path="arch" arch_default_path="arch" lmdz_common_path="../LMDZ.COMMON" LMDGCM=`/bin/pwd` LIBOGCM=$LMDGCM/lib PEM_COMMON_PATH=$LMDGCM/src/common PEM_PLANET_PATH=$LMDGCM/src/${physique} PEM_MISC_REQ_PATH=$LMDGCM/tmp_src/misc_required # Path to fcm utility: fcm_path=$(dirname $(which fcm)) ######################################################################## # Quelques initialisations de variables du shell. ######################################################################## CPP_KEY="" INCLUDE_DIR="" LIB="" COMPIL_FFLAGS="%PROD_FFLAGS" PARA_FFLAGS="" PARA_LD="" EXT_SRC="" ######################################################################## # lecture des options de mymake ######################################################################## # Get the compilation command compilation_command="$(basename $0) $@" # Parse arguments and capture options while (($# > 0)) do case $1 in "-h") cat < .void_file rm -rf .void_dir mkdir .void_dir if [[ "$arch_defined" == "TRUE" ]] then rm -f arch.path rm -f arch.env if test -f $arch_path/arch-${arch}.path then ln -s $arch_path/arch-${arch}.path arch.path elif test -f $arch_default_path/arch-${arch}.path then ln -s $arch_default_path/arch-${arch}.path arch.path fi if test -f $arch_path/arch-${arch}.env then ln -s $arch_path/arch-${arch}.env arch.env elif test -f $arch_default_path/arch-${arch}.env then ln -s $arch_default_path/arch-${arch}.env arch.env else ln -s .void_file arch.env fi # source architecture PATH and ENV files source arch.env source arch.path else echo "You must define a target architecture" exit 1 fi if [[ ! -f ${arch_path}/arch-${arch}.fcm ]]; then echo "Error: cannot find architecture file ${arch_path}/arch-${arch}.fcm" exit 1 fi ######################################################################## # Definition des clefs CPP, des chemins des includes et modules # et des libraries ######################################################################## if [[ "$compil_mod" == "prod" ]] then COMPIL_FFLAGS="%PROD_FFLAGS" elif [[ "$compil_mod" == "dev" ]] then COMPIL_FFLAGS="%DEV_FFLAGS" elif [[ "$compil_mod" == "debug" ]] then COMPIL_FFLAGS="%DEBUG_FFLAGS" else echo "Error: unsupported compilation mode '$compil_mod'" exit 1 fi if [[ "$code" != "pem" ]] then echo "Error: only executable 'pem' is supported by make_pem_fcm" exit 1 fi if [[ "$physique" != "mars" && "$physique" != "generic" ]] then echo "Error: unsupported planet '$physique' (supported: mars, generic)." exit 1 fi if [[ ! -d "$PEM_COMMON_PATH" ]] then echo "Error: missing PEM common sources in $PEM_COMMON_PATH" exit 1 fi PEM_PLANET_PATH="$LMDGCM/src/${physique}" if [[ ! -d "$PEM_PLANET_PATH" ]] then echo "Error: missing PEM planet sources in $PEM_PLANET_PATH" exit 1 fi if [[ ! -d "$lmdz_common_path" ]] then echo "Error: missing LMDZ.COMMON path: $lmdz_common_path" exit 1 fi # Sanity checks for required dependencies in LMDZ.COMMON/libf/misc. for req in job.F90 program_options.F90 do if [[ ! -f "$lmdz_common_path/libf/misc/$req" ]] then echo "Error: required file missing in LMDZ.COMMON/libf/misc: $req" exit 1 fi done if [[ -z "$NETCDF_INCDIR" || -z "$NETCDF_LIBDIR" || -z "$NETCDF_LIB" ]] then echo "Error: NETCDF paths are not set by arch.path" exit 1 fi if [[ -z "$IOIPSL_INCDIR" || -z "$IOIPSL_LIBDIR" || -z "$IOIPSL_LIB" ]] then echo "Error: IOIPSL paths are not set by arch.path" exit 1 fi rm -rf tmp_src mkdir -p "$PEM_MISC_REQ_PATH" cp "$lmdz_common_path/libf/misc/job.F90" "$PEM_MISC_REQ_PATH/" cp "$lmdz_common_path/libf/misc/program_options.F90" "$PEM_MISC_REQ_PATH/" # Base PEM keys CPP_KEY="CPP_PHYS CPP_IOIPSL CPP_1D $CPP_KEY" if [[ "$io" == "xios" || "$io" == "mix" ]] then if [[ -z "$XIOS_INCDIR" || -z "$XIOS_LIBDIR" || -z "$XIOS_LIB" ]] then echo "Error: XIOS selected but XIOS paths are not set by arch.path" exit 1 fi CPP_KEY="$CPP_KEY CPP_XIOS" INCLUDE_DIR="$INCLUDE_DIR $XIOS_INCDIR" LIB="$LIB $XIOS_LIBDIR $XIOS_LIB" fi if [[ "$io" == "xios" ]] then CPP_KEY="$CPP_KEY CPP_IOIPSL_NO_OUTPUT" fi INCLUDE_DIR="$INCLUDE_DIR $NETCDF_INCDIR $IOIPSL_INCDIR -I$PEM_COMMON_PATH -I$PEM_PLANET_PATH -I$PEM_MISC_REQ_PATH" LIB="$LIB $NETCDF_LIBDIR $NETCDF_LIB $IOIPSL_LIBDIR $IOIPSL_LIB" ######################################################################## # Generation of a Fortran subroutine to track compilation and version # details through the executable file ######################################################################## # Path to the root directory where we want to check version control system root_dir="$LMDGCM/.." # Path and name of the generated file version_F90file="$PEM_MISC_REQ_PATH/version_control.F90" # Path and name of the file containing the compilation and version details default_filename="pgrm_version_control.txt" # Get the current date current_date=$(date) # Function to process problematic characters # - Convert input to UTF-8 and ignore invalid characters # - Remove carriage return characters to prevent formatting issues # - Escape backslashes and single quotes clean_output() { iconv -f "$(locale charmap)" -t UTF-8//IGNORE \ | tr -d '\r' \ | sed 's/\\/\\\\/g; s/'\''/'\'\''/g' } vcs_info="" vcs_diff="" vcs_stat="" # Check if there is a Git repository at the root if command -v git > /dev/null 2>&1 && git -C "$root_dir" rev-parse --is-inside-work-tree > /dev/null 2>&1; then dir_name="$(basename "$root_dir")" vcs_info+="-> Git Information for \"$dir_name\"\n$(git -C "$root_dir" log -1 --pretty=format:"%H%n%an%n%ad%n%s" | clean_output)\n" vcs_stat+="-> Git Status for \"$dir_name\"\n$(git -C "$root_dir" status --short | clean_output)\n" vcs_diff+="-> Git Diff for \"$dir_name\"\n$(git -C "$root_dir" diff | clean_output)\n" else # No so we iterate through each subdirectory to determine version control system for dir in "$root_dir"/*; do # Skip if it is not a directory or the name contains "git" or "svn" [ -d "$dir" ] || continue [[ "$dir" == *git* || "$dir" == *svn* ]] && continue # Determine the version control system for each subdirectory dir_name="$(basename "$dir")" if command -v svn > /dev/null 2>&1 && svn info "$dir" > /dev/null 2>&1; then # SVN vcs_info+="\n-> SVN Information for \"$dir_name\"\n$(svn info "$dir" | clean_output)\n" vcs_diff+="\n-> SVN Diff for \"$dir_name\"\n$(svn diff "$dir" | clean_output)\n" vcs_stat+="\n-> SVN Status for \"$dir_name\"\n$(svn status "$dir" | clean_output)\n" elif command -v git > /dev/null 2>&1 && git -C "$dir" rev-parse --is-inside-work-tree > /dev/null 2>&1; then # Git vcs_info+="-> Git Information for \"$dir_name\"\n$(git -C "$dir" log -1 --pretty=format:"%H%n%an%n%ad%n%s" | clean_output)\n" vcs_diff+="\n-> Git Diff for \"$dir_name\"\n$(git -C "$dir" diff | clean_output)\n" vcs_stat+="\n-> Git Status for \"$dir_name\"\n$(git -C "$dir" status --short | clean_output)\n" else # None vcs_info+="\n-> No version control system for \"$dir_name\"\n" fi done fi # Generate the Fortran subroutine cat << EOF > "$version_F90file" MODULE version_control !----------------------------------------------------------------------- ! NAME ! version_control ! ! DESCRIPTION ! Manage compilation details and version control information for the ! program. ! ! AUTHORS & DATE ! JB Clement, 13/01/2025 ! ! NOTES ! File generated automatically at compilation to include compilation ! details and version control information in the executable. !----------------------------------------------------------------------- ! DECLARATIONS ! ------------ implicit none ! PARAMETERS ! ---------- character(*), parameter, private :: default_filename = "${default_filename}" ! Default name for the output file character(128), protected, private :: curr_dir = ' ' ! Current directory character(32), protected, private :: username = ' ' ! User name character(32), protected, private :: hostname = ' ' ! Machine/station name character(128), protected, private :: cmd_pgrm = ' ' ! Command used to run the programm character(8), protected, private :: date = ' ' ! Current date (YYYYMMDD) character(10), protected, private :: time = ' ' ! Current time (hhmmss.sss) character(5), protected, private :: zone = ' ' ! UTC offset (+/-hhmm) contains !+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ !======================================================================= SUBROUTINE print_pgrm_version(user_filename) !----------------------------------------------------------------------- ! NAME ! print_pgrm_version ! ! DESCRIPTION ! Prints compilation details and the results of the version control ! information (SVN or Git), with commannds "info", "status" and ! "diff" if applicable. ! ! Use the command-line option "--version [file]" when running your ! program: ! ./myprogram --version [file] ! This will write compilation details and version control information ! into the specified [file]. If [file] is omitted, the default name ! "pgrm_version_control.txt" will be used. ! ! AUTHORS & DATE ! JB Clement, 13/01/2025 ! ! NOTES ! This feature helps track code builds and their exact compilation ! context directly from the executable. !----------------------------------------------------------------------- ! DECLARATIONS ! ------------ implicit none ! ARGUMENTS ! --------- character(*), optional, intent(in) :: user_filename ! LOCAL VARIABLES ! --------------- integer :: funit, ierr character(:), allocatable :: filename integer, dimension(8) :: values ! CODE ! ---- ! Some user info call date_and_time(date,time,zone,values) call getcwd(curr_dir) call getlog(username) call hostnm(hostname) call get_command(cmd_pgrm) ! Open the file filename = trim(adjustl(default_filename)) if (present(user_filename)) filename = trim(adjustl(user_filename)) open(newunit = funit,file = filename,status = "replace",action = "write",iostat = ierr) if (ierr /= 0) error stop 'error opening file "'//filename//'"!' ! Write compilation details write(*,*) write(*,'(a)') 'Generating program control file: "'//filename//'"' write(funit,'(a)') '########################################################################' write(funit,'(a)') '########################### PROGRAM CONTROL ############################' write(funit,'(a)') '########################################################################' write(funit,*) write(funit,'(a)') '> User : '//trim(adjustl(username)) write(funit,'(a)') '> Machine : '//trim(adjustl(hostname)) write(funit,'(a)') '> Directory: '//trim(adjustl(curr_dir)) write(funit,'(a)') '> Command : '//trim(adjustl(cmd_pgrm)) write(funit,'(a,a4,a,a2,a,a2)') '> Date : ',date(1:4),'-',date(5:6),'-',date(7:8) write(funit,'(a,a2,a,a2,a,a2,a,a3)') '> Time : ',time(1:2),':',time(3:4),':',time(5:6),'.',time(8:10) if (zone(1:1) == '+' .or. zone(1:1) == '-') then write(funit,'(a,a1,a2,a,a2)') '> Timezone : UTC',zone(1:1),zone(2:3),':',zone(4:5) else write(funit,'(a,i0,a)') '> Timezone : unknown (offset=',values(4),' min)' end if write(funit,*) write(*,'(a)') ' > Writing compilation details...' write(funit,'(a)') '######################### COMPILATION DETAILS ##########################' write(funit,'(a)') '> Date : ${current_date}' write(funit,'(a)') '> Command: ${compilation_command}' write(funit,*) ! Write version control information EOF if [ -n "$vcs_info" ]; then echo "write(*,'(a)') ' > Writing info result...'" >> "$version_F90file" echo "write(funit,'(a)') '##################### VERSION CONTROL INFORMATION ######################'" >> "$version_F90file" while IFS= read -r line; do echo "write(funit,'(a)') '${line}'" >> "$version_F90file" done <<< "$(echo -e "$vcs_info")" else echo "write(funit,'(a)') '###################### NO VERSION CONTROL SYSTEM #######################'" >> "$version_F90file" fi if [ -n "$vcs_stat" ]; then echo "write(*,'(a)') ' > Writing status result...'" >> "$version_F90file" echo "write(funit,*)" >> "$version_F90file" echo "write(funit,'(a)') '######################## VERSION CONTROL STATUS ########################'" >> "$version_F90file" while IFS= read -r line; do echo "write(funit,'(a)') '${line}'" >> "$version_F90file" done <<< "$(echo -e "$vcs_stat")" fi if [ -n "$vcs_diff" ]; then echo "write(*,'(a)') ' > Writing diff result...'" >> "$version_F90file" echo "write(funit,*)" >> "$version_F90file" echo "write(funit,'(a)') '######################### VERSION CONTROL DIFF #########################'" >> "$version_F90file" while IFS= read -r line; do echo "write(funit,'(a)') '${line}'" >> "$version_F90file" done <<< "$(echo -e "$vcs_diff")" fi cat << EOF >> "$version_F90file" write(funit,'(a)') '########################################################################' ! Close the file close(funit) write(*,'(a)') 'Done!' write(*,*) END SUBROUTINE print_pgrm_version !======================================================================= END MODULE version_control EOF # Termination message echo "'$version_F90file' has been generated successfully." ######################################################################## # Creation du suffixe de la configuration ######################################################################## SUFF_NAME=_${dim} SUFF_NAME=${SUFF_NAME}_${physique}_seq SUFF_DEBUG= if [[ "$compil_mod" == "debug" ]] then SUFF_DEBUG=_debug fi SUFF_DIR=${SUFF_NAME}${SUFF_DEBUG} SUFF_NAME=${SUFF_NAME}.e cd $LMDGCM config_fcm="config.fcm" rm -f $config_fcm touch $config_fcm echo "%ARCH $arch" >> $config_fcm echo "%INCDIR $INCLUDE_DIR" >> $config_fcm echo "%LIB $LIB" >> $config_fcm echo "%ROOT_PATH $PWD" >> $config_fcm echo "%LIBO $LIBOGCM" >> $config_fcm echo "%PEM_COMMON $PEM_COMMON_PATH" >> $config_fcm echo "%PEM_PLANET $PEM_PLANET_PATH" >> $config_fcm echo "%PEM_MISC_REQ $PEM_MISC_REQ_PATH" >> $config_fcm echo "%CPP_KEY $CPP_KEY" >> $config_fcm echo "%EXEC $code" >> $config_fcm echo "%SUFF_NAME $SUFF_NAME" >> $config_fcm echo "%SUFF_DIR $SUFF_DIR" >> $config_fcm echo "%COMPIL_FFLAGS $COMPIL_FFLAGS" >> $config_fcm echo "%PARA_FFLAGS $PARA_FFLAGS" >> $config_fcm echo "%PARA_LD $PARA_LD" >> $config_fcm echo "%EXT_SRC $EXT_SRC" >> $config_fcm # setup arch.fcm and arch.opt rm -f arch.fcm rm -f arch.opt ln -s ${arch_path}/arch-${arch}.fcm arch.fcm if test -f ${arch_path}/arch-${arch}.opt && [ $compil_mod = "prod" ] then ln -s ${arch_path}/arch-${arch}.opt arch.opt else ln -s .void_file arch.opt fi # cleanup before compiling rm -f bin/${code}${SUFF_NAME} rm -f $LIBOGCM/${arch}${SUFF_DIR}/.config/fcm.bld.lock if [[ $clean == "true" ]] then echo "-- Cleaning up folder $LIBOGCM/${arch}${SUFF_DIR} --" rm -rf tmp_src rm -rf config rm -rf $LIBOGCM/${arch}${SUFF_DIR} exit 0 fi ./build_pem ${fcm_path} -j $job $full || (echo "Build pem failed."; exit 1) rm -rf tmp_src rm -rf config ln -s $LIBOGCM/${arch}${SUFF_DIR}/.config config