1# GncAddSchemeTargets.cmake Define a command to compile Scheme programs with Guile 2# Copyright (c) 2015, Rob Gowin 3# Copyright 2017 John Ralls <jralls@ceridwen.us> 4# 5# This program is distributed in the hope that it will be useful, 6# but WITHOUT ANY WARRANTY; without even the implied warranty of 7# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 8# GNU General Public License for more details. 9# 10# You should have received a copy of the GNU General Public License 11# along with this program; if not, contact: 12# Free Software Foundation Voice: +1-617-542-5942 13# 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 14# Boston, MA 02110-1301, USA gnu@gnu.org 15 16# Guile and ltdl require MSYS paths on MinGW-w64; this function transforms them. 17function(make_unix_path PATH) 18 string(REGEX REPLACE "^([A-Za-z]):" "/\\1" newpath ${${PATH}}) 19 string(REGEX REPLACE "\\\\" "/" newpath ${newpath}) 20 set(${PATH} ${newpath} PARENT_SCOPE) 21endfunction() 22 23#PATH variables in the environment are separated by colons, but CMake lists are separated by semicolons. This function transforms the separators. 24function(make_unix_path_list PATH) 25 string(REPLACE ";" ":" newpath "${${PATH}}") 26 set(${PATH} ${newpath} PARENT_SCOPE) 27endfunction() 28 29# This function will set two or four environment variables to a directory in the parent PARENT_SCOPE 30# * _DIRCLASS (eg "prefix", "sitedir" is used to construct the variable name(s) and in error messages 31# * _DIRCMD is the guile command to run to get the path for the given _DIRCLASS (eg "(display (%site-dir))") 32# * _PREFIX: if set will be used to calculate paths relative to this prefix and 33# set two more environment variable with this relative path. 34# When run successfully this function will set following variables in the parent scope: 35# * GUILE_${CMDCLASS} and GUILE_UNIX_${CMDCLASS} - the latter is the former transformed 36# into an msys compatible format (c:\some\directory => /c/some/directory) 37# * If _PREFIX was set: GUILE_${CMDCLASS} and GUILE_REL_UNIX_${CMDCLASS} 38# which are the former two variables with _PREFIX removed 39# (_PREFIX=/usr, GUILE_${CMDCLASS} = /usr/share/something 40# => GUILE_REL_${CMDCLASS} = share/something 41function(find_one_guile_dir _DIRCLASS _DIRCMD _PREFIX) 42 43 string(TOUPPER ${_DIRCLASS} CLASS_UPPER) 44 string(TOLOWER ${_DIRCLASS} CLASS_LOWER) 45 execute_process( 46 COMMAND ${GUILE_EXECUTABLE} -c ${_DIRCMD} 47 RESULT_VARIABLE CMD_RESULT 48 OUTPUT_VARIABLE CMD_OUTPUT 49 ERROR_VARIABLE CMD_ERROR 50 OUTPUT_STRIP_TRAILING_WHITESPACE 51 ERROR_STRIP_TRAILING_WHITESPACE 52 ) 53 if (CMD_RESULT) 54 message(SEND_ERROR "Could not determine Guile ${CLASS_LOWER}:\n${CMD_ERROR}") 55 endif() 56 57 set(GUILE_${CLASS_UPPER} ${CMD_OUTPUT} PARENT_SCOPE) 58 set(CMD_UNIX_OUTPUT ${CMD_OUTPUT}) 59 make_unix_path(CMD_UNIX_OUTPUT) 60 set(GUILE_UNIX_${CLASS_UPPER} ${CMD_UNIX_OUTPUT} PARENT_SCOPE) 61 62 if (_PREFIX) 63 string(REGEX REPLACE "^${_PREFIX}[\\/]*" "" CMD_REL_OUTPUT ${CMD_OUTPUT}) 64 set(GUILE_REL_${CLASS_UPPER} ${CMD_REL_OUTPUT} PARENT_SCOPE) 65 set(CMD_REL_UNIX_OUTPUT ${CMD_REL_OUTPUT}) 66 make_unix_path(CMD_REL_UNIX_OUTPUT) 67 set(GUILE_REL_UNIX_${CLASS_UPPER} ${CMD_REL_UNIX_OUTPUT} PARENT_SCOPE) 68 endif() 69 70endfunction() 71 72# Query the guile executable for path information. We're interested in guile's 73# datadir, libdir, sitedir, ccachedir and siteccachedir 74macro(find_guile_dirs) 75 # Get GUILE_PREFIX and GUILE_UNIX_PREFIX 76 find_one_guile_dir("prefix" "(display (assoc-ref %guile-build-info 'prefix))" "") 77 # Get GUILE_DATADIR, GUILE_UNIX_DATADIR, GUILE_REL_DATADIR, GUILE_REL_UNIX_DATADIR 78 find_one_guile_dir("datadir" "(display (%package-data-dir))" ${GUILE_PREFIX}) 79 # Get GUILE_LIBDIR, GUILE_UNIX_LIBDIR, GUILE_REL_LIBDIR, GUILE_REL_UNIX_LIBDIR 80 find_one_guile_dir("libdir" "(display (%library-dir))" ${GUILE_PREFIX}) 81 # Get GUILE_CCACHEDIR, GUILE_UNIX_CCACHEDIR, GUILE_REL_CCACHEDIR, GUILE_REL_UNIX_CCACHEDIR 82 find_one_guile_dir("ccachedir" "(display (assoc-ref %guile-build-info 'ccachedir))" ${GUILE_PREFIX}) 83 # Get GUILE_SITEDIR, GUILE_UNIX_SITEDIR, GUILE_REL_SITEDIR, GUILE_REL_UNIX_SITEDIR 84 find_one_guile_dir("sitedir" "(display (%site-dir))" ${GUILE_PREFIX}) 85 # Get GUILE_SITECCACHEDIR, GUILE_UNIX_SITECCACHEDIR, GUILE_REL_SITECCACHEDIR, GUILE_REL_UNIX_SITECCACHEDIR 86 find_one_guile_dir("siteccachedir" "(display (%site-ccache-dir))" ${GUILE_PREFIX}) 87 string(REGEX REPLACE "[/\\]*${GUILE_EFFECTIVE_VERSION}$" "" GUILE_REL_TOP_SITEDIR ${GUILE_REL_SITEDIR}) 88 string(REGEX REPLACE "[/]*${GUILE_EFFECTIVE_VERSION}$" "" GUILE_REL_UNIX_TOP_SITEDIR ${GUILE_REL_UNIX_SITEDIR}) 89 90 # Generate replacement strings for use in environment file. The paths used are 91 # the paths found in %load-path and %load-compiled-path by default but 92 # rebased on {GNC_HOME} (which is the runtime resolved variable to where gnucash 93 # gets installed). 94 set (GNC_GUILE_LOAD_PATH 95 "{GNC_HOME}/${GUILE_REL_UNIX_LIBDIR}" 96 "{GNC_HOME}/${GUILE_REL_UNIX_SITEDIR}" 97 "{GNC_HOME}/${GUILE_REL_UNIX_TOP_SITEDIR}" 98 "{GNC_HOME}/${GUILE_REL_UNIX_SITEDIR}/gnucash/deprecated" # Path to gnucash' deprecated modules 99 "{GNC_HOME}/${GUILE_REL_UNIX_DATADIR}") 100 101 set (GNC_GUILE_LOAD_COMPILED_PATH 102 "{GNC_HOME}/${GUILE_REL_UNIX_CCACHEDIR}" 103 "{GNC_HOME}/${GUILE_REL_UNIX_CCACHEDIR}/gnucash/deprecated" 104 "{GNC_HOME}/${GUILE_REL_UNIX_SITECCACHEDIR}") 105endmacro(find_guile_dirs) 106 107# gnc_add_scheme_targets (target 108# SOURCES source1 source2 ... 109# OUTPUT_DIR directory 110# [DEPENDS depedency1 dependency2 ...] 111# [MAKE_LINKS] [TEST]) 112# 113#̉ Use this function to add scheme targets, that is *.scm files to 114# compile into their corresponding *.go files. 115# 116# SOURCES is a list of scm source files to compile. 117# 118# The resulting *.go binaries will be generated in OUTPUT_DIR directory. 119# This directory is interpreted as a path relative to the build's guile compiled directory 120# directory. For example if guile binaries in the build directory are located in 121# $HOME/gnucash/build/lib/x86_64-linux-gnu/guile/2.0/site-cache and OUTPUT_DIR is "gnucash" 122# the binary .go files will go into 123# $HOME/gnucash/build/lib/x86_64-linux-gnu/guile/2.0/site-cache/gnucash 124# 125# If cmake targets are provided via the DEPENDS keyword those will be added to 126# the guile targets as dependencies. 127# 128# If MAKE_LINKS is set links (or copies on Windows) will be set up 129# from the source directory to the build's guile sources directory. 130# For example if guile source path in the build directory is 131# $HOME/gnucash/build/share/guile/site/2.0 and OUTPUT_DIR is "gnucash" 132# the links or copies will go into 133# $HOME/gnucash/build/share/guile/site/2.0/gnucash 134# 135# If keyword TEST is specified this target will be treated as a test target. 136# That is its compiled files won't be installed and will be added to the set 137# of tests to run via the "check" target. If TEST is not set the targets are 138# considerd normal targets and will be added to the list of files to install. 139# They will be installed in the guile compied directory relative to the prefix 140# set up for this build, with the OUTPUT_DIR appended to it. For example: 141# /usr/local/lib/x86_64-linux-gnu/guile/2.0/site-cache/gnucash 142function(gnc_add_scheme_targets _TARGET) 143 set(noValues MAKE_LINKS TEST) 144 set(singleValues OUTPUT_DIR) 145 set(multiValues SOURCES DEPENDS) 146 cmake_parse_arguments(SCHEME_TGT "${noValues}" "${singleValues}" "${multiValues}" ${ARGN}) 147 148 set(__DEBUG FALSE) 149 if (__DEBUG) 150 message("Parameters to COMPILE_SCHEME for target ${_TARGET}") 151 message(" SOURCE_FILES: ${SCHEME_TGT_SOURCES}") 152 message(" GUILE_DEPENDS: ${SCHEME_TGT_DEPENDS}") 153 message(" MAKE_LINKS: ${SCHEME_TGT_MAKE_LINKS}") 154 message(" TEST: ${SCHEME_TGT_TEST}") 155 message(" DIRECTORIES: ${BINDIR_BUILD}, ${LIBDIR_BUILD}, ${DATADIR_BUILD}, ${SCHEME_TGT_OUTPUT_DIR}") 156 endif() 157 set(_CMD "create_symlink") 158 if(WIN32) 159 set(_CMD "copy") 160 endif() 161 set(current_srcdir ${CMAKE_CURRENT_SOURCE_DIR}) 162 set(current_bindir ${CMAKE_CURRENT_BINARY_DIR}) 163 set(build_bindir ${BINDIR_BUILD}) 164 set(build_libdir ${LIBDIR_BUILD}) 165 set(build_datadir ${DATADIR_BUILD}) 166 if(MINGW64 AND ${GUILE_EFFECTIVE_VERSION} VERSION_LESS 2.2) 167 make_unix_path(build_bindir) 168 make_unix_path(build_libdir) 169 make_unix_path(build_datadir) 170 make_unix_path(current_bindir) 171 make_unix_path(current_srcdir) 172 make_unix_path(CMAKE_BINARY_DIR) 173 make_unix_path(CMAKE_SOURCE_DIR) 174 endif() 175 176 # If links are requested, we simply link (or copy, for Windows) each source file to the dest directory 177 set(TARGET_LINKS "") 178 if(SCHEME_TGT_MAKE_LINKS) 179 set(_LINK_DIR ${CMAKE_BINARY_DIR}/${GUILE_REL_UNIX_SITEDIR}/${SCHEME_TGT_OUTPUT_DIR}) 180 file(MAKE_DIRECTORY ${_LINK_DIR}) 181 set(_SCHEME_LINKS "") 182 foreach(scheme_file ${SCHEME_TGT_SOURCES}) 183 set(_SOURCE_FILE ${current_srcdir}/${scheme_file}) 184 if(IS_ABSOLUTE ${scheme_file}) 185 set(_SOURCE_FILE ${scheme_file}) 186 endif() 187 get_filename_component(name ${scheme_file} NAME) 188 set(_OUTPUT_FILE ${_LINK_DIR}/${name}) 189 if(NOT EXISTS ${_OUTPUT_FILE}) 190 list(APPEND _SCHEME_LINKS ${_OUTPUT_FILE}) 191 add_custom_command( 192 OUTPUT ${_OUTPUT_FILE} 193 COMMAND ${CMAKE_COMMAND} -E ${_CMD} ${_SOURCE_FILE} ${_OUTPUT_FILE} 194 ) 195 endif() 196 endforeach(scheme_file) 197 set(TARGET_LINKS ${_TARGET}-links) 198 add_custom_target(${TARGET_LINKS} ALL DEPENDS ${_SCHEME_LINKS}) 199 endif() 200 201 # Construct the guile source and compiled load paths 202 set(_GUILE_LOAD_PATH "${current_srcdir}" "${current_bindir}" "${current_bindir}/deprecated") 203 set(_GUILE_LOAD_COMPILED_PATH "${current_bindir}") 204 # VERSION_GREATER_EQUAL introduced in CMake 3.7. 205 if(MINGW64 AND (${GUILE_EFFECTIVE_VERSION} VERSION_GREATER_EQUAL 2.2)) 206 if (NOT (DEFINED ENV{GUILE_LOAD_PATH} AND DEFINED ENV{GUILE_LOAD_COMPILED_PATH})) 207 message(FATAL_ERROR "$GUILE_LOAD_PATH and $GUILE_LOAD_COMPILED_PATH must be defined in the environment to configure GnuCash on Microsoft Windows.") 208 endif() 209 file(TO_CMAKE_PATH $ENV{GUILE_LOAD_PATH} guile_load_path) 210 file(TO_CMAKE_PATH $ENV{GUILE_LOAD_COMPILED_PATH} guile_load_compiled_path) 211 list(APPEND _GUILE_LOAD_PATH ${guile_load_path}) 212 list(APPEND _GUILE_LOAD_COMPILED_PATH ${guile_load_compiled_path}) 213 endif() 214 set(_GUILE_CACHE_DIR "${CMAKE_BINARY_DIR}/${GUILE_REL_UNIX_SITECCACHEDIR}") 215 list(APPEND _GUILE_LOAD_PATH "${CMAKE_BINARY_DIR}/${GUILE_REL_UNIX_SITEDIR}") 216 list(APPEND _GUILE_LOAD_COMPILED_PATH ${_GUILE_CACHE_DIR} 217 "${CMAKE_BINARY_DIR}/${GUILE_REL_UNIX_SITECCACHEDIR}/gnucash/deprecated") 218 219 set(_TARGET_FILES "") 220 221 foreach(source_file ${SCHEME_TGT_SOURCES}) 222 set(guile_depends ${SCHEME_TGT_DEPENDS}) 223 get_filename_component(basename ${source_file} NAME_WE) 224 225 set(output_file ${basename}.go) 226 set(_TMP_OUTPUT_DIR ${SCHEME_TGT_OUTPUT_DIR}) 227 if (_TMP_OUTPUT_DIR) 228 set(output_file ${SCHEME_TGT_OUTPUT_DIR}/${basename}.go) 229 endif() 230 set(output_file ${_GUILE_CACHE_DIR}/${output_file}) 231 list(APPEND _TARGET_FILES ${output_file}) 232 233 set(source_file_abs_path ${CMAKE_CURRENT_SOURCE_DIR}/${source_file}) 234 if (IS_ABSOLUTE ${source_file}) 235 set(source_file_abs_path ${source_file}) 236 endif() 237 if (__DEBUG) 238 message("add_custom_command: output = ${output_file}") 239 endif() 240 if (MINGW64) 241 set(fpath "") 242 file(TO_CMAKE_PATH "$ENV{PATH}" fpath) 243 set(LIBRARY_PATH "PATH=${BINDIR_BUILD};${fpath}") 244 else() 245 set (LIBRARY_PATH "LD_LIBRARY_PATH=${LIBDIR_BUILD}:${LIBDIR_BUILD}/gnucash:$ENV{LD_LIBRARY_PATH}") 246 endif() 247 if (APPLE) 248 set (LIBRARY_PATH "DYLD_LIBRARY_PATH=${LIBDIR_BUILD}:${LIBDIR_BUILD}/gnucash:$ENV{DYLD_LIBRARY_PATH}") 249 endif() 250 set(_GNC_MODULE_PATH "") 251 if(MINGW64) 252 set(_GNC_MODULE_PATH "${build_bindir}") 253 else() 254 set(_GNC_MODULE_PATH "${LIBDIR_BUILD}" "${LIBDIR_BUILD}/gnucash" "${GNC_MODULE_PATH}") 255 endif() 256 if(NOT MINGW64 OR ${GUILE_EFFECTIVE_VERSION} VERSION_LESS 2.2) 257 make_unix_path_list(_GUILE_LOAD_PATH) 258 make_unix_path_list(_GUILE_LOAD_COMPILED_PATH) 259 endif() 260 make_unix_path_list(_GNC_MODULE_PATH) 261 if (__DEBUG) 262 message(" ") 263 message(" LIBRARY_PATH: ${LIBRARY_PATH}") 264 message(" GUILE_LOAD_PATH: ${_GUILE_LOAD_PATH}") 265 message(" GUILE_LOAD_COMPILED_PATH: ${_GUILE_LOAD_COMPILED_PATH}") 266 message(" GNC_MODULE_PATH: ${_GNC_MODULE_PATH}") 267 endif() 268 #We quote the arguments to stop CMake stripping the path separators. 269 add_custom_command( 270 OUTPUT ${output_file} 271 COMMAND ${CMAKE_COMMAND} -E env 272 "${LIBRARY_PATH}" 273 "GNC_UNINSTALLED=YES" 274 "GNC_BUILDDIR=${CMAKE_BINARY_DIR}" 275 "GUILE_LOAD_PATH=${_GUILE_LOAD_PATH}" 276 "GUILE_LOAD_COMPILED_PATH=${_GUILE_LOAD_COMPILED_PATH}" 277 "GNC_MODULE_PATH=${_GNC_MODULE_PATH}" 278 ${GUILE_EXECUTABLE} -e "\(@@ \(guild\) main\)" -s ${GUILD_EXECUTABLE} compile -o ${output_file} ${source_file_abs_path} 279 DEPENDS ${guile_depends} 280 MAIN_DEPENDENCY ${source_file_abs_path} 281 VERBATIM 282 ) 283 endforeach(source_file) 284 if (__DEBUG) 285 message("TARGET_FILES are ${_TARGET_FILES}") 286 endif() 287 add_custom_target(${_TARGET} ALL DEPENDS ${_TARGET_FILES} ${TARGET_LINKS}) 288 289 set(_TARGET_FILES "${_TARGET_FILES}" PARENT_SCOPE) 290 291 if(SCHEME_TGT_TEST) 292 add_dependencies(check ${_TARGET}) 293 else() 294 install(FILES ${_TARGET_FILES} DESTINATION ${CMAKE_INSTALL_PREFIX}/${GUILE_REL_SITECCACHEDIR}/${SCHEME_TGT_OUTPUT_DIR}) 295 install(FILES ${SCHEME_TGT_SOURCES} DESTINATION ${CMAKE_INSTALL_PREFIX}/${GUILE_REL_SITEDIR}/${SCHEME_TGT_OUTPUT_DIR}) 296 endif() 297endfunction() 298 299 300# gnc_add_scheme_test_targets (target 301# SOURCES source1 source2 ... 302# OUTPUT_DIR directory 303# [DEPENDS depedency1 dependency2 ...] 304# [MAKE_LINKS]) 305# 306# Calls gnc_add_scheme_targets with the TEST keyword set 307# See that function's description for more details. 308function(gnc_add_scheme_test_targets _TARGET) 309 gnc_add_scheme_targets(${_TARGET} ${ARGN} TEST) 310endfunction() 311 312# gnc_add_scheme_deprecated_module (OLD_MODULE old_module_name 313# [NEW_MODULE new_module_name 314# DEPENDS new_module_target] 315# [MESSAGE msg_string]) 316# 317# Function to write boilerplate code for deprecated guile modules 318# such that invocation of the old module will emit a deprecation warning 319# message. 320# 321# All but the OLD_MODULE keyword are optional 322# 323# OLD_MODULE and NEW_MODULE should be passed in the form 324# "gnucash mod parts" 325# 326# If NEW_MODULE is set that module will be loaded instead of the 327# deprecated module. 328# If NEW_MODULE is set, DEPENDS should be set to the target for which 329# that module is a source file. 330# For example module (gnucash reports standard transaction) 331# is defined in transaction.scm, which is a source file for 332# cmake target scm-reports-standard so that should be set as DEPENDS. 333# 334# If MESSAGE is left blank, the module will emit a generic message. 335# Otherwise MESSAGE will be emitted. 336function(gnc_add_scheme_deprecated_module) 337 338 set(singleValues OLD_MODULE NEW_MODULE MESSAGE) 339 set(multiValues DEPENDS) 340 cmake_parse_arguments(DM "" "${singleValues}" "${multiValues}" ${ARGN}) 341 342 string(STRIP DM_OLD_MODULE "${DM_OLD_MODULE}") 343 string(REPLACE " " "-" _TARGET ${DM_OLD_MODULE}) 344 set(_TARGET "scm-deprecated-${_TARGET}") 345 346 string(REPLACE " " ";" MODPARTS "${DM_OLD_MODULE}") 347 list(GET MODPARTS -1 DEPFILENAME) 348 set(SOURCEFILE "${CMAKE_CURRENT_BINARY_DIR}/deprecated/${DEPFILENAME}.scm") 349 350 string(FIND "${DM_OLD_MODULE}" ${DEPFILENAME} POS REVERSE) 351 if (${POS} LESS 2) 352 set(MODPATH "gnucash/deprecated") 353 else() 354 list(REMOVE_AT MODPARTS -1) 355 string(REPLACE ";" "/" MODPATH "${MODPARTS}") 356 set(MODPATH "gnucash/deprecated/${MODPATH}") 357 endif() 358 359 set(DEPPREFIX "* WARN <gnc-guile-deprecation> *: ") 360 if (DM_MESSAGE) 361 set(DEPWARNING "(issue-deprecation-warning \"${DEPPREFIX}${DM_MESSAGE}\")") 362 else() 363 set(DEPWARNING 364 "(issue-deprecation-warning \"${DEPPREFIX}Module '(${DM_OLD_MODULE})' has been deprecated and will be removed in the future.\")") 365 if (DM_NEW_MODULE) 366 set(DEPWARNING "${DEPWARNING} 367 (issue-deprecation-warning \"${DEPPREFIX}Use module '(${DM_NEW_MODULE})' instead.\")") 368 endif() 369 endif() 370 371 # Write the stub file 372 file(WRITE ${SOURCEFILE} " 373;; ${DEPFILENAME}.scm 374;; Compatibility module for deprecated (${DM_OLD_MODULE}). 375;; This file is autogenerated, do not modify by hand. 376 377(define-module (${DM_OLD_MODULE})) 378 379${DEPWARNING} 380") 381 382 if (DM_NEW_MODULE) 383 file(APPEND ${SOURCEFILE} " 384(use-modules (${DM_NEW_MODULE})) 385 386(let ((i (module-public-interface (current-module)))) 387 (module-use! i (resolve-interface '(${DM_NEW_MODULE}))))") 388 endif() 389 390 gnc_add_scheme_targets("${_TARGET}" 391 SOURCES "${SOURCEFILE}" 392 OUTPUT_DIR "${MODPATH}" 393 DEPENDS "${DM_DEPENDS}") 394endfunction() 395