cmake-pandocology/pandocology.cmake
Jeet Sukumaran 50cc03afce
Merge pull request #13 from jkrenzer/dev_add_preprocessing
Added preprocessing for PP
2019-05-12 11:46:42 -07:00

482 lines
23 KiB
CMake

################################################################################
##
## Provide Pandoc compilation support for the CMake build system
##
## Version: 0.0.1
## Author: Jeet Sukumatan (jeetsukumaran@gmail.com)
##
## Copyright 2015 Jeet Sukumaran.
##
## This software is released under the BSD 3-Clause License.
##
## 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.
##
################################################################################
include(CMakeParseArguments)
if(NOT EXISTS ${PANDOC_EXECUTABLE})
# find_program(PANDOC_EXECUTABLE NAMES pandoc)
find_program(PANDOC_EXECUTABLE pandoc)
mark_as_advanced(PANDOC_EXECUTABLE)
if(NOT EXISTS ${PANDOC_EXECUTABLE})
message(FATAL_ERROR "Pandoc not found. Install Pandoc (http://johnmacfarlane.net/pandoc/) or set cache variable PANDOC_EXECUTABLE.")
return()
endif()
endif()
if(NOT EXISTS ${PP_EXECUTABLE})
find_program(PP_EXECUTABLE pp)
mark_as_advanced(PP_EXECUTABLE)
if(NOT EXISTS ${PP_EXECUTABLE})
message("PP not found. If this is wrong, please set cache variable PP_EXECUTABLE.")
endif()
endif()
if(NOT DEFINED ${PREPROCESS_TEMP_DIRECTORY})
set(PREPROCESS_TEMP_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/_preprocessed")
endif()
###############################################################################
# Based on code from UseLATEX
# Author: Kenneth Moreland <kmorel@sandia.gov>
# Copyright 2004 Sandia Corporation.
# Under the terms of Contract DE-AC04-94AL85000, there is a non-exclusive
# license for use of this work by or on behalf of the
# U.S. Government. Redistribution and use in source and binary forms, with
# or without modification, are permitted provided that this Notice and any
# statement of authorship are reproduced on all copies.
# Adds command to copy file from the source directory to the destination
# directory: used to move source files from source directory into build
# directory before main build
function(pandocology_add_input_file source_path dest_dir dest_filelist_var native_dest_filelist_var)
set(dest_filelist)
set(native_dest_filelist)
file(GLOB globbed_source_paths "${source_path}")
foreach(globbed_source_path ${globbed_source_paths})
get_filename_component(filename ${globbed_source_path} NAME)
get_filename_component(absolute_dest_path ${dest_dir}/${filename} ABSOLUTE)
file(RELATIVE_PATH relative_dest_path ${CMAKE_CURRENT_BINARY_DIR} ${absolute_dest_path})
list(APPEND dest_filelist ${absolute_dest_path})
file(TO_NATIVE_PATH ${absolute_dest_path} native_dest_path)
list(APPEND native_dest_filelist ${native_dest_path})
file(TO_NATIVE_PATH ${globbed_source_path} native_globbed_source_path)
ADD_CUSTOM_COMMAND(
OUTPUT ${relative_dest_path}
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${native_globbed_source_path} ${native_dest_path}
DEPENDS ${globbed_source_path}
)
set(${dest_filelist_var} ${${dest_filelist_var}} ${dest_filelist} PARENT_SCOPE)
set(${native_dest_filelist_var} ${${native_dest_filelist_var}} ${native_dest_filelist} PARENT_SCOPE)
endforeach()
endfunction()
# A version of GET_FILENAME_COMPONENT that treats extensions after the last
# period rather than the first.
function(pandocology_get_file_stemname varname filename)
SET(result)
GET_FILENAME_COMPONENT(name ${filename} NAME)
STRING(REGEX REPLACE "\\.[^.]*\$" "" result "${name}")
SET(${varname} "${result}" PARENT_SCOPE)
endfunction()
function(pandocology_get_file_extension varname filename)
SET(result)
GET_FILENAME_COMPONENT(name ${filename} NAME)
STRING(REGEX MATCH "\\.[^.]*\$" result "${name}")
SET(${varname} "${result}" PARENT_SCOPE)
endfunction()
###############################################################################
function(pandocology_add_input_dir source_dir dest_parent_dir dir_dest_filelist_var native_dir_dest_filelist_var)
set(dir_dest_filelist)
set(native_dir_dest_filelist)
file(TO_CMAKE_PATH ${source_dir} source_dir)
get_filename_component(dir_name ${source_dir} NAME)
get_filename_component(absolute_dest_dir ${dest_parent_dir}/${dir_name} ABSOLUTE)
file(RELATIVE_PATH relative_dest_dir ${CMAKE_CURRENT_BINARY_DIR} ${absolute_dest_dir})
file(TO_NATIVE_PATH ${absolute_dest_dir} native_absolute_dest_dir)
add_custom_command(
OUTPUT ${relative_dest_dir}
COMMAND ${CMAKE_COMMAND} -E make_directory ${native_absolute_dest_dir}
DEPENDS ${source_dir}
)
file(GLOB_RECURSE source_files "${source_dir}/*")
foreach(source_file ${source_files})
file(RELATIVE_PATH relative_source_file ${source_dir} ${source_file})
# get_filename_component(absolute_source_path ${CMAKE_CURRENT_SOURCE_DIR}/${source_file} ABSOLUTE)
get_filename_component(relative_source_dir ${relative_source_file} DIRECTORY)
pandocology_add_input_file(${source_file} ${absolute_dest_dir}/${relative_source_dir} dir_dest_filelist native_dir_dest_filelist)
endforeach()
set(${dir_dest_filelist_var} ${${dir_dest_filelist_var}} ${dir_dest_filelist} PARENT_SCOPE)
set(${native_dir_dest_filelist_var} ${${native_dir_dest_filelist_var}} ${native_dir_dest_filelist} PARENT_SCOPE)
endfunction()
function(add_to_make_clean filepath)
file(TO_NATIVE_PATH ${CMAKE_CURRENT_BINARY_DIR}/${filepath} native_filepath)
get_directory_property(make_clean_files ADDITIONAL_MAKE_CLEAN_FILES)
set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${make_clean_files};${native_filepath}")
endfunction()
function(disable_insource_build)
IF ( CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR AND NOT MSVC_IDE )
MESSAGE(FATAL_ERROR "The build directory must be different from the main project source "
"directory. Please create a directory such as '${CMAKE_SOURCE_DIR}/build', "
"and run CMake from there, passing the path to this source directory as "
"the path argument. E.g.:
$ cd ${CMAKE_SOURCE_DIR}
$ mkdir build
$ cd build
$ cmake .. && make && sudo make install
This process created the file `CMakeCache.txt' and the directory `CMakeFiles'.
Please delete them:
$ rm -r CMakeFiles/ CmakeCache.txt
")
ENDIF()
endfunction()
# This builds a document
#
# Usage:
#
#
# INCLUDE(pandocology)
#
# add_document(
# TARGET figures
# OUTPUT_FILE figures.tex
# SOURCES figures.md
# RESOURCE_DIRS figs
# PANDOC_DIRECTIVES -t latex
# NO_EXPORT_PRODUCT
# )
#
# add_document(
# TARGET opus
# OUTPUT_FILE opus.pdf
# SOURCES opus.md
# RESOURCE_FILES opus.bib systbiol.template.latex systematic-biology.csl
# RESOURCE_DIRS figs
# PANDOC_DIRECTIVES -t latex
# --smart
# --template systbiol.template.latex
# --filter pandoc-citeproc
# --csl systematic-biology.csl
# --bibliography opus.bib
# --include-after-body=figures.tex
# DEPENDS figures
# EXPORT_ARCHIVE
# )
#
#
# Deprecated command signature for backwards compatibility:
#
# add_document(
# figures.tex
# SOURCES figures.md
# RESOURCE_DIRS figs
# PANDOC_DIRECTIVES -t latex
# NO_EXPORT_PRODUCT
# )
#
# This command signature will cause an error due to a circular dependency during
# build when the sources are at the top level directory of the project. A
# warning is issued. The solution is to use the alternative commande signature.
#
function(add_document)
set(options EXPORT_ARCHIVE NO_EXPORT_PRODUCT EXPORT_PDF DIRECT_TEX_TO_PDF VERBOSE PREPROCESS)
set(oneValueArgs TARGET OUTPUT_FILE PRODUCT_DIRECTORY)
set(multiValueArgs SOURCES RESOURCE_FILES RESOURCE_DIRS PANDOC_DIRECTIVES PP_DIRECTIVES DEPENDS)
cmake_parse_arguments(ADD_DOCUMENT "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )
# this is because `make clean` will dangerously clean up source files
disable_insource_build()
if ("${ADD_DOCUMENT_TARGET}" STREQUAL "" AND "${ADD_DOCUMENT_OUTPUT_FILE}" STREQUAL "")
set(bw_comp_mode_args TRUE)
endif()
if(bw_comp_mode_args)
list(LENGTH ADD_DOCUMENT_UNPARSED_ARGUMENTS unparsed_args_num)
if (${unparsed_args_num} GREATER 1)
message(FATAL_ERROR "add_document() requires at most one target name")
endif()
if (${CMAKE_CURRENT_SOURCE_DIR} STREQUAL ${CMAKE_SOURCE_DIR})
message(WARNING "This add_document() signature does not support sources on the top level directory. \
It will cause a circular dependency error during build.")
endif()
list(GET ADD_DOCUMENT_UNPARSED_ARGUMENTS 0 ADD_DOCUMENT_TARGET)
set(ADD_DOCUMENT_OUTPUT_FILE ${ADD_DOCUMENT_TARGET})
endif()
if ("${ADD_DOCUMENT_TARGET}" STREQUAL "")
MESSAGE(FATAL_ERROR "add_document() requires a target name by using the TARGET option")
endif()
if ("${ADD_DOCUMENT_OUTPUT_FILE}" STREQUAL "")
MESSAGE(FATAL_ERROR "add_document() requires an output file name by using the OUTPUT_FILE option")
endif()
if (NOT bw_comp_mode_args)
if ("${ADD_DOCUMENT_TARGET}" STREQUAL "${ADD_DOCUMENT_OUTPUT_FILE}")
message(FATAL_ERROR "Target '${ADD_DOCUMENT_TARGET}': Must different from OUTPUT_FILE")
endif()
endif()
set(output_file ${ADD_DOCUMENT_OUTPUT_FILE})
file(TO_NATIVE_PATH ${output_file} native_output_file)
# get the stem of the target name
pandocology_get_file_stemname(output_file_stemname ${output_file})
pandocology_get_file_extension(output_file_extension ${output_file})
if (${ADD_DOCUMENT_EXPORT_PDF})
if (NOT "${output_file_extension}" STREQUAL ".tex" AND NOT "${output_file_extension}" STREQUAL ".latex")
MESSAGE(FATAL_ERROR "Target '${ADD_DOCUMENT_TARGET}': Cannot use 'EXPORT_PDF' for target of type '${output_file_extension}': target type must be '.tex' or '.latex'")
endif()
endif()
if (${ADD_DOCUMENT_DIRECT_TEX_TO_PDF})
list(LENGTH ${ADD_DOCUMENT_SOURCES} SOURCE_LEN)
if (SOURCE_LEN GREATER 1)
MESSAGE(FATAL_ERROR "Target '${ADD_DOCUMENT_TARGET}': Only one source can be specified when using the 'DIRECT_TEX_TO_PDF' option")
endif()
pandocology_get_file_stemname(source_stemname ${ADD_DOCUMENT_SOURCES})
pandocology_get_file_extension(source_extension ${ADD_DOCUMENT_SOURCES})
if (NOT "${source_extension}" STREQUAL ".tex" AND NOT "${source_extension}" STREQUAL ".latex")
MESSAGE(FATAL_ERROR "Target '${ADD_DOCUMENT_TARGET}': Cannot use 'DIRECT_TEX_TO_PDF' for source of type '${source_extension}': source type must be '.tex' or '.latex'")
endif()
set(check_target ${source_stemname}.pdf)
if (NOT ${check_target} STREQUAL ${output_file})
MESSAGE(FATAL_ERROR "Target '${ADD_DOCUMENT_TARGET}': Must use target name of '${check_target}' if using 'DIRECT_TEX_TO_PDF'")
endif()
endif()
## set up output directory
if ("${ADD_DOCUMENT_PRODUCT_DIRECTORY}" STREQUAL "")
set(ADD_DOCUMENT_PRODUCT_DIRECTORY "product")
endif()
get_filename_component(product_directory ${CMAKE_BINARY_DIR}/${ADD_DOCUMENT_PRODUCT_DIRECTORY} ABSOLUTE)
file(TO_NATIVE_PATH ${product_directory} native_product_directory)
set(dest_output_file ${product_directory}/${output_file})
file(TO_NATIVE_PATH ${dest_output_file} native_dest_output_file)
## get primary source
set(build_sources)
set(native_build_sources)
foreach(input_file ${ADD_DOCUMENT_SOURCES} )
pandocology_add_input_file(${CMAKE_CURRENT_SOURCE_DIR}/${input_file} ${CMAKE_CURRENT_BINARY_DIR} build_sources native_build_sources)
endforeach()
## get resource files
set(build_resources)
set(native_build_resources)
foreach(resource_file ${ADD_DOCUMENT_RESOURCE_FILES})
if(IS_ABSOLUTE ${resource_file})
pandocology_add_input_file(${resource_file} ${CMAKE_CURRENT_BINARY_DIR} build_resources native_build_resources)
else()
pandocology_add_input_file(${CMAKE_CURRENT_SOURCE_DIR}/${resource_file} ${CMAKE_CURRENT_BINARY_DIR} build_resources native_build_resources)
endforeach()
## get resource dirs
set(exported_resources)
set(native_exported_resources)
foreach(resource_dir ${ADD_DOCUMENT_RESOURCE_DIRS})
if(IS_ABSOLUTE ${resource_dir})
pandocology_add_input_dir(${resource_dir} ${CMAKE_CURRENT_BINARY_DIR} build_resources native_build_resources)
else()
pandocology_add_input_dir(${CMAKE_CURRENT_SOURCE_DIR}/${resource_dir} ${CMAKE_CURRENT_BINARY_DIR} build_resources native_build_resources)
endif()
if (${ADD_DOCUMENT_EXPORT_ARCHIVE})
pandocology_add_input_dir(${CMAKE_CURRENT_SOURCE_DIR}/${resource_dir} ${product_directory} exported_resources native_exported_resources)
endif()
endforeach()
## primary command
if (${ADD_DOCUMENT_DIRECT_TEX_TO_PDF})
if (${ADD_DOCUMENT_VERBOSE})
add_custom_command(
OUTPUT ${output_file} # note that this is in the build directory
DEPENDS ${build_sources} ${build_resources} ${ADD_DOCUMENT_DEPENDS}
COMMAND ${CMAKE_COMMAND} -E make_directory ${native_product_directory}
COMMAND latexmk -gg -halt-on-error -interaction=nonstopmode -file-line-error -pdf ${native_build_sources}
)
else()
add_custom_command(
OUTPUT ${output_file} # note that this is in the build directory
DEPENDS ${build_sources} ${build_resources} ${ADD_DOCUMENT_DEPENDS}
COMMAND ${CMAKE_COMMAND} -E make_directory ${native_product_directory}
COMMAND latexmk -gg -halt-on-error -interaction=nonstopmode -file-line-error -pdf ${native_build_sources} 2>/dev/null >/dev/null || (grep --no-messages -A8 ".*:[0-9]*:.*" ${output_file_stemname}.log && false)
)
endif()
add_to_make_clean(${output_file})
else()
if(${ADD_DOCUMENT_PREPROCESS})
if(EXISTS ${PP_EXECUTABLE})
message("Preprocessing documents with PP.")
set(PREPROCESSING_WITH_PP)
file(MAKE_DIRECTORY ${PREPROCESS_TEMP_DIRECTORY})
set(preprocessed_build_sources)
foreach(native_build_source ${native_build_sources})
get_filename_component(native_source_name ${native_build_source} NAME)
set(preprocessed_build_source "${PREPROCESS_TEMP_DIRECTORY}/${native_source_name}")
set(pp_working_directory ${CMAKE_CURRENT_SOURCE_DIR})
file(RELATIVE_PATH preprocessed_output_filepath ${pp_working_directory} ${preprocessed_build_source})
add_custom_command(
OUTPUT ${preprocessed_build_source}
DEPENDS ${build_sources} ${build_resources} ${ADD_DOCUMENT_DEPENDS}
COMMAND ${PP_EXECUTABLE} ${ADD_DOCUMENT_PP_DIRECTIVES} ${native_build_source} > ${preprocessed_output_filepath}
VERBATIM
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
add_to_make_clean(${preprocessed_build_source})
list(APPEND preprocessed_build_sources ${preprocessed_build_source})
endforeach()
add_custom_command(
OUTPUT ${output_file} # note that this is in the build directory
DEPENDS ${preprocessed_build_sources} ${build_resources} ${ADD_DOCUMENT_DEPENDS}
COMMAND ${CMAKE_COMMAND} -E make_directory ${native_product_directory}
COMMAND ${PANDOC_EXECUTABLE} ${preprocessed_build_sources} ${ADD_DOCUMENT_PANDOC_DIRECTIVES} -o ${native_output_file}
)
add_to_make_clean(${output_file})
else()
message(FATAL_ERROR "PREPROCESS set but no preprocessor found!")
return()
endif()
else()
add_custom_command(
OUTPUT ${output_file} # note that this is in the build directory
DEPENDS ${build_sources} ${build_resources} ${ADD_DOCUMENT_DEPENDS}
COMMAND ${CMAKE_COMMAND} -E make_directory ${native_product_directory}
COMMAND ${PANDOC_EXECUTABLE} ${native_build_sources} ${ADD_DOCUMENT_PANDOC_DIRECTIVES} -o ${native_output_file}
)
add_to_make_clean(${output_file})
endif()
endif()
add_custom_target (
${output_file}_product
DEPENDS ${output_file}
)
## figure out what all is going to be produced by this build set, and set
## those as dependencies of the primary target
set(primary_target_dependencies)
set(primary_target_dependencies ${primary_target_dependencies} ${CMAKE_CURRENT_BINARY_DIR}/${output_file})
if (NOT ${ADD_DOCUMENT_NO_EXPORT_PRODUCT})
set(primary_target_dependencies ${primary_target_dependencies} ${product_directory}/${output_file})
endif()
if (${ADD_DOCUMENT_EXPORT_PDF})
set(primary_target_dependencies ${primary_target_dependencies} ${CMAKE_CURRENT_BINARY_DIR}/${output_file_stemname}.pdf)
set(primary_target_dependencies ${primary_target_dependencies} ${product_directory}/${output_file_stemname}.pdf)
endif()
if (${ADD_DOCUMENT_EXPORT_ARCHIVE})
set(primary_target_dependencies ${primary_target_dependencies} ${product_directory}/${output_file_stemname}.tbz)
endif()
## primary target
# # target cannot have same (absolute name) as dependencies:
# # http://www.cmake.org/pipermail/cmake/2011-March/043378.html
add_custom_target(
${ADD_DOCUMENT_TARGET}
ALL
DEPENDS ${primary_target_dependencies} ${ADD_DOCUMENT_DEPENDS}
)
# run post-pdf
if (${ADD_DOCUMENT_EXPORT_PDF})
add_custom_command(
OUTPUT ${native_product_directory}/${output_file_stemname}.pdf ${CMAKE_CURRENT_BINARY_DIR}/${output_file_stemname}.pdf
DEPENDS ${output_file} ${build_sources} ${build_resources} ${ADD_DOCUMENT_DEPENDS}
# Does not work: custom template used to generate tex is ignored
# COMMAND ${PANDOC_EXECUTABLE} ${output_file} -f latex -o ${output_file_stemname}.pdf
# (1) Apparently, both nonstopmode and batchmode produce an output file
# even if there was an error. This tricks latexmk into believing
# the file is actually up-to-date.
# So we use `-halt-on-error` or `-interaction=errorstopmode`
# instead.
# (2) `grep` returns a non-zero error code if the pattern is not
# found. So, in our scheme below to filter the output of
# `pdflatex`, it is precisely when there is NO error that
# grep returns a non-zero code, which fools CMake into thinking
# tex'ing failed.
# Hence the need for `| grep ...| cat` or `| grep || true`.
# But we can go better:
# latexmk .. || (grep .. && false)
# So we can have our cake and eat it too: here we want to
# re-raise the error after a successful grep if there was an
# error in `latexmk`.
# COMMAND latexmk -gg -halt-on-error -interaction=nonstopmode # -file-line-error -pdf ${output_file} 2>&1 | grep -A8 ".*:[0-9]*:.*" || true
COMMAND latexmk -gg -halt-on-error -interaction=nonstopmode -file-line-error -pdf ${native_output_file} 2>/dev/null >/dev/null || (grep --no-messages -A8 ".*:[0-9]*:.*" ${output_file_stemname}.log && false)
COMMAND ${CMAKE_COMMAND} -E copy ${output_file_stemname}.pdf ${native_product_directory}
)
add_to_make_clean(${output_file_stemname}.pdf)
add_to_make_clean(${product_directory}/${output_file_stemname}.pdf)
endif()
## copy products
if (NOT ${ADD_DOCUMENT_NO_EXPORT_PRODUCT})
add_custom_command(
OUTPUT ${native_dest_output_file}
DEPENDS ${build_sources} ${build_resources} ${ADD_DOCUMENT_DEPENDS} ${output_file}_product
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${native_output_file} ${native_dest_output_file}
)
add_to_make_clean(${product_directory}/${output_file})
endif()
## copy resources
set(export_archive_file ${product_directory}/${output_file_stemname}.tbz)
file(TO_NATIVE_PATH ${export_archive_file} native_export_archive_file)
if (${ADD_DOCUMENT_EXPORT_ARCHIVE})
add_custom_command(
OUTPUT ${export_archive_file}
DEPENDS ${output_file}
COMMAND ${CMAKE_COMMAND} -E tar cjf ${native_export_archive_file} ${native_output_file} ${native_build_resources} ${ADD_DOCUMENT_DEPENDS}
)
add_to_make_clean(${export_archive_file})
endif()
endfunction(add_document)
function(add_tex_document)
add_document(${ARGV} DIRECT_TEX_TO_PDF)
endfunction()
# LEGACY SUPPORT
function(add_pandoc_document)
add_document(${ARGV})
endfunction()