#!/bin/sh ############################################################################# # $Id$ # Add C++ API sample to the existing P2 project's (CMake/Conan). # Author: Vladimir Ivanov (ivanov@ncbi.nlm.nih.gov) ############################################################################# initial_dir=`pwd` script_name=`basename $0` script_dir=`dirname $0` script_dir=`(cd "${script_dir}" ; pwd)` repository="https://svn.ncbi.nlm.nih.gov/repos/toolkit/trunk/c++" cxxtk='ncbi-cxx-toolkit-core' assets_dir='deployment_artifacts' tag_base='@NEW_PROJECT::SampleAPI' indent=' ' separator='------------------------------------------------' tmp="tmp.sample.$$" trap "rm -rf $tmp >/dev/null 2>&1; echo; exit" 0 1 2 3 9 15 if test -x /usr/bin/nawk; then awk=nawk else awk=awk fi ############################################################################# Usage() { cat < [] SYNOPSIS: Add C++ API sample to the existing P2 project. ARGUMENTS: --help -- print Usage -- existing P2 project directory (local copy; use 'git clone') -- API type to add -- sample to use, if specified API have many samples (optional) EOF if test -z "$1"; then echo echo The following API types are available: svn list -R $repository/src/sample | while IFS= read -r line; do case "$line" in */ ) count=`echo $line | tr -cd '/' | wc -c` if [ $count -eq 2 ]; then prj=${line%?} case "$prj" in app/alnmgr) echo " app/alnmgr an application that uses Alignment Manager";; app/asn) echo " app/asn an application that uses ASN.1 specification";; app/basic) echo " app/basic a simple application";; app/blast) echo " app/blast a BLAST application";; app/cgi) echo " app/cgi a CGI application";; app/connect) echo " app/connect a Usage report logging";; app/dbapi) echo " app/dbapi a DBAPI application";; app/deployable_cgi) continue;; # not applicable app/eutils) echo " app/eutils an eUtils client application";; app/http_session) echo " app/http_session an application that uses CHttpSession";; app/lds) echo " app/lds an application that uses local data store";; app/multicmd) echo " app/multicmd a simple command-based application";; app/netcache) echo " app/netcache an application that uses NetCache";; app/netschedule) echo " app/netschedule an NCBI GRID (NetSchedule) application";; app/objects) echo " app/objects an application that uses ASN.1 objects";; app/objmgr) echo " app/objmgr an application that uses Object Manager";; app/sdbapi) echo " app/sdbapi a Simple-DBAPI application";; app/serial) echo " app/serial a data serialization application";; app/soap) echo " app/soap/client a SOAP client" echo " app/soap/server a SOAP server";; app/unit_test) echo " app/unit_test a Boost-based unit test application";; lib/basic) continue;; # not applicable lib/asn_lib) echo " lib/asn_lib a source generation from ASN.1 specification";; lib/dtd) echo " lib/dtd a source generation from DTD specification";; lib/jsd) echo " lib/jsd a source generation from JSON Schema specification";; lib/xsd) echo " lib/xsd a source generation from XML Schema specification";; *) echo " $prj";; esac fi ;; esac done fi if [ -n "$1" ]; then echo echo ERROR: $1 fi exit 1 } case $# in 3) prj_dir="$1" ; sample_dir="$2" ; sample_prj="$3" ;; 2) prj_dir="$1" ; sample_dir="$2" ;; 0) Usage "" ;; *) Usage "Invalid number of arguments" ;; esac case "$sample_dir" in lib/*) is_lib=true; is_app=false ;; *) is_lib=false; is_app=true ;; esac Error() { cd "$initial_dir" echo ERROR: "$@" echo exit 1 } # Usage: GetMakefileProperty # Parses CMake makefile $2 and return value for $1: property(value). # Replaces '$' with '@' for the sake of CMake variables, to avoid accidentally # change by any shell commands. # Put result into $result. result='' GetMakefileProperty() { result=`\ $awk -v p="$1" '$0 ~ p, /)/ {s=$0; gsub(/\\${/,"@{",s); src = src s} END {print src}' < "$2" \ | sed -e "s/$1//g" \ | tr -d "()" \ | xargs -n1 \ | xargs` } #---------------------------------------------------------------------------- # Checkout adding sample to temporary directory if [ ! -d $prj_dir ]; then Error "Project directory $prj_dir doesn\`t exists" fi cd $prj_dir rm -rf $tmp_dir 2>/dev/null mkdir $tmp 2>/dev/null svn co $repository/include/sample/$sample_dir $tmp >/dev/null 2>&1 svn co $repository/src/sample/$sample_dir $tmp >/dev/null 2>&1 find $tmp -name ".svn" -exec rm -rf {} \; >/dev/null 2>&1 if [ ! -d "$tmp" ]; then Error "Failed to checkout API samples $sample_dir" fi src_dir="$tmp" #---------------------------------------------------------------------------- # If sample project has not specified, and there are more than one, ask user. if [ -z "$sample_prj" ]; then # Prepare list of samples for specified API if $is_app; then list=`grep -s "NCBI_add_app" "$src_dir/CMakeLists.txt" | sed -e "s/NCBI_add_app(//" -e "s/).*//" | xargs -n1 | sort | xargs` if [ -z "list" ]; then Error "Unable to find sample apps for $sample_prj" fi else list=`grep -s "NCBI_add_library" "$src_dir/CMakeLists.txt" | sed -e "s/NCBI_add_library(//" -e "s/).*//" | xargs -n1 | sort | xargs` if [ -z "list" ]; then Error "Unable to find sample libs for $sample_prj" fi fi count=`echo "$list" | wc -w` if [ "$count" -eq "1" ]; then sample_prj="$list" else echo echo "Please select API sample to add:" i=1 for s in $list; do echo "$indent" $i"." $s i=`expr $i + 1` done echo "$indent" $i"." - ALL - while true; do read -p "Enter sample number: " n case $n in [1-9] ) if [ $n -le $i ]; then break fi ;; * );; esac done if [ $n -eq $i ]; then sample_prj="$list" else sample_prj=`echo "$list" | cut -d' ' -f$n` fi fi fi #---------------------------------------------------------------------------- # Add each selected sample project for sample in $sample_prj; do echo $separator echo "Adding API $sample_dir - $sample:" echo $separator if $is_app; then src_makefile="CMakeLists.$sample.app.txt" else for ext in lib asn; do src_makefile="CMakeLists.$sample.$ext.txt" if [ -f $src_dir/$src_makefile ]; then break fi done fi # --- Copy source files --- if $is_app; then echo "Copy new source files to '$prj_dir'" GetMakefileProperty "NCBI_sources" "$src_dir/$src_makefile" sources="$result" if [ -z "$sources" ]; then Error "Unable to find source files in the $src_makefile" fi new_files='' new_sources='' for src in $sources; do for ext in .h .hpp; do file="$src_dir/${src}${ext}" if [ -f $file ]; then echo "$indent" ${src}${ext} cp $file . new_files="$new_files ${src}${ext}" fi done # Conan/CMake build system require extentions for source files, # so detect and add if necessary for ext in '' .c .cpp .cxx .cc .c++ ; do file="$src_dir/${src}${ext}" if [ -f $file ]; then # Exclude main sample file if [ "${src}${ext}" = "${sample_prj}${ext}" ]; then continue fi echo "$indent" ${src}${ext} cp $file . new_files="$new_files ${src}${ext}" new_sources="$new_sources ${src}${ext}" fi done done fi if $is_lib; then echo "Copy schema specification to '$prj_dir'" GetMakefileProperty "NCBI_dataspecs" "$src_dir/$src_makefile" sources="$result" if [ -z "$sources" ]; then Error "Unable to find schema specification in the $src_makefile" fi for src in $sources; do echo "$indent" $src cp "$src_dir/$src" . done fi # --- CMakeLists.txt --- target_makefile="CMakeLists.txt" echo "Modifying $target_makefile..." if $is_lib; then # Add generation like: # NCBI_generate_cpp(ASN_SRC ASN_HEADERS sample.asn) # New sources wil be added as: # add_executable(app src1 src2 ... ${ASN_SRC}) new_sources='' for src in $sources; do type='' case "$src" in *.asn) type='ASN' ;; *.dtd) type='DTD' ;; *.jsd) type='JSD' ;; *.xsd) type='XSD' ;; *) Error "Unknown chema specifivation type: $src" ;; esac if grep "NCBI_generate_cpp(.*${src}" $target_makefile >/dev/null 2>&1; then continue fi for i in '' $(seq 1 99); do s="${type}_SRC${i}" h="${type}_HEADERS${i}" if grep "$s" $target_makefile >/dev/null 2>&1; then continue else break fi done $awk -v src="$src" \ -v s="$s" \ -v h="$h" \ -v OFS= ' /add_executable\(/ { print "NCBI_generate_cpp(" s " " h " " src ")" } // { print $0 } ' < "$target_makefile" > "$target_makefile.$$" mv -f "$target_makefile.$$" "$target_makefile" # Use '@' instead of '$', that wil be replaced back later new_sources="$new_sources @{$s}" done fi # Add sources like: # add_executable(app src1 src2 ...) if [ -n "$new_sources" ]; then GetMakefileProperty "add_executable" "$target_makefile" orig_sources=`echo $result | xargs -n1 | xargs | sed "s/[^ ]* //"` target_sources=`echo $result $new_sources | xargs -n1 | $awk '!seen[$0]++' | xargs` $awk -v src="$target_sources" ' /add_executable\(/ {p = 1} // { if (!p) { print $0 } } /)/ { if (p) { gsub(/@{/,"${", src); print "add_executable(" src ")" } p = 0 } ' < "$target_makefile" > "$target_makefile.$$" mv -f "$target_makefile.$$" "$target_makefile" fi # --- Components / Libs --- echo "Modifying conanfile.txt..." # ncbi-cxx-toolkit-core/*:with_targets=lib1;lib2;... # ncbi-cxx-toolkit-core/*:with_components=comp1;comp2;... GetMakefileProperty "NCBI_uses_toolkit_libraries.*${cxxtk}" "$src_dir/$src_makefile" comps=`echo $result | tr -d ':'` GetMakefileProperty "NCBI_uses_toolkit_libraries" "$src_dir/$src_makefile" libs=`echo $result | sed "s/${cxxtk}::[a-zA-Z0-9-]*//g"` if [ -z "$comps" -a -z "$libs" ]; then Error "Unable to find libraries to link in the $src_makefile" fi target_conanfile="conanfile.txt" target_comps=`grep -s "$cxxtk/.*:with_components" "$target_conanfile" | sed -e "s/.*=//" -e "s/;/ /g"` comps=`echo $target_comps $comps | xargs -n1 | $awk '!seen[$0]++' | xargs | tr ' ' ';'` target_libs=`grep -s "$cxxtk/.*:with_targets" "$target_conanfile" | sed -e "s/.*=//" -e "s/;/ /g"` libs=`echo $target_libs $libs | xargs -n1 | $awk '!seen[$0]++' | xargs | tr ' ' ';'` $awk -v comps="$comps" -v libs="$libs" -v OFS= ' /:with_components=/ { s = substr($0,0,index($0,"=")) print s,comps p = 1 } /:with_targets=/ { s = substr($0,0,index($0,"=")) print s,libs p = 1 } // { if (!p) { print $0 } else {p=0} } ' < "$target_conanfile" > "$target_conanfile.$$" mv -f "$target_conanfile.$$" "$target_conanfile" # --- Modify sources --- if $is_app; then echo "Injecting sample code..." # Find class name derived from CNcbiSample header='' class='' member='' for file in $new_files; do class=`grep 'public CNcbiSample' "$file" | sed -e "s/.*class *\(\w*\) *:.*$/\1/"` if [ -n "$class" ]; then header="$file" break; fi done if [ -z "$class" ]; then Error "Unable to find class name derived from CNcbiSample" fi member=`echo $class | sed -e 's/[^_]*_//'` member="m_Sample_${member}" # Search all source files specified in the original CMakeList and inject code for ext in '' .c .cpp .cxx .cc .c++; do for src in $orig_sources; do file="${src}${ext}" if [ -f $file ]; then if grep "$tag_base" $file >/dev/null 2>&1; then echo "$indent" $file # Inject code after tags. Process tags after first #include only. $awk -v tag_base="$tag_base" \ -v tag_header="$tag_base::Header@" \ -v tag_decl="$tag_base::Decl@" \ -v tag_init="$tag_base::Init@" \ -v tag_run="$tag_base::Run@" \ -v tag_exit="$tag_base::Exit@" \ -v header="$header" \ -v class="$class" \ -v member="$member" \ -v OFS= ' // { print $0 } /#include/ {a = 1} $0 ~ tag_base { if (a) { indent = substr($0,0,index($0,"/")-1) if ($0 ~ tag_header) { print indent "#include \"",header,"\"" } if ($0 ~ tag_decl) { print indent class " " member ";" } if ($0 ~ tag_init) { print indent member ".Init();" } if ($0 ~ tag_run) { print indent "/* int exit_code = */ " member ".Run();" } if ($0 ~ tag_exit) { print indent member ".Exit();" } } } ' < "$file" > "$file.$$" mv -f "$file.$$" "$file" fi fi done done fi # is_app # --- Assets --- GetMakefileProperty "NCBI_set_test_assets" "$src_dir/$src_makefile" assets="$result" if [ -n "$assets" ]; then echo "Copy sample assets to '$prj_dir/$assets_dir'" mkdir -p $assets_dir for src in $assets; do file="$src_dir/$src" if [ -f $file ]; then echo "$indent" ${src} cp -r $file $assets_dir fi done fi done #---------------------------------------------------------------------------- rm -rf $tmp >/dev/null 2>&1 cd "$initial_dir" echo $separator echo Done.