向CMake中的自定义编译器/语言添加额外的编译步骤

问题描述

这是对早期question I posted的后续措施。我的基本问题是使用Gambit Scheme构建应用程序。

虽然上述问题中建议的解决方案有效,但它很麻烦,因此我决定尝试将gambit Scheme作为自定义编译器/语言添加到CMake。按照此question中的建议,我创建了以下文件

cmake / CMakeDeterminegambitCompiler.cmake

# Find the compiler
find_program(
    CMAKE_gambit_COMPILER 
        NAMES "gambitc"
        HINTS "${CMAKE_SOURCE_DIR}"
        DOC "gambit Scheme compiler" 
)

mark_as_advanced( CMAKE_gambit_COMPILER )

set( CMAKE_gambit_SOURCE_FILE_EXTENSIONS scm;six )
# Remember this as a potential error
set( CMAKE_gambit_OUTPUT_EXTENSION .c )
set( CMAKE_gambit_COMPILER_ENV_VAR "" )

# Configure variables set in this file for fast reload later on
configure_file( ${CMAKE_CURRENT_LIST_DIR}/CMakegambitCompiler.cmake.in
                ${CMAKE_PLATFORM_INFO_DIR}/CMakegambitCompiler.cmake )

cmake / CMakegambitinformation.cmake

# This file sets the basic flags for the gambit compiler

# Generate the C files
set( CMAKE_gambit_COMPILE_OBJECT
    "<CMAKE_gambit_COMPILER> -o <OBJECT> -c <SOURCE>"
)

# Build a executable 
set( CMAKE_gambit_LINK_EXECUTABLE 
    "<CMAKE_gambit_COMPILER> -o <TARGET> -exe <OBJECTS>"
)

set( CMAKE_gambit_informatION_LOADED 1 )

cmake / CMakegambitCompiler.cmake.in

set( CMAKE_gambit_COMPILER "@CMAKE_gambit_COMPILER@" )
set( CMAKE_gambit_COMPILER_LOADED 1 )
set( CMAKE_gambit_SOURCE_FILE_EXTENSIONS @CMAKE_gambit_SOURCE_FILE_EXTENSIONS@ )
set( CMAKE_gambit_OUTPUT_EXTENSION @CMAKE_gambit_OUTPUT_EXTENSION@ )
set( CMAKE_gambit_COMPILER_ENV_VAR "@CMAKE_gambit_COMPILER_ENV_VAR@" )

cmake / CMakeTestgambitCompiler.cmake

# For Now do nothing
set( CMAKE_gambit_COMPILER_WORKS 1 CACHE INTERNAL "" )

然后,在项目根目录中,我还有两个文件

CMakeTexts.txt

cmake_minimum_required( VERSION 3.10...3.18 )

if( ${CMAKE_VERSION} VERSION_LESS 3.12 )
    cmake_policy( VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MInor_VERSION} )
endif()

# Give the project a name
project( cmake-scheme-template NONE )

# Build simple gambit Scheme program
list( APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
enable_language( gambit )
add_executable( ${PROJECT_NAME} main.scm )

要构建的实际代码 main.scm

;;; Simple Scheme example
(begin (write "Hello,Schemer!")
       (newline))

提供以下结构:

project_root/
    cmake/
        CMakeDeterminegambitCompiler.cmake
        CMakegambitCompiler.cmake.in
        CMakegambitinformation.cmake
        CMakeTestgambitCompiler.cmake
    CMakeLists.txt
    main.scm

虽然这适用于单个文件,但是一旦我添加了另一个文件,我就需要gambit首先为从Scheme源生成的所有C文件创建一个链接文件。这是一个简单的示例:

说我添加第二个文件 factmodule.scm

;;; This is a simple Scheme module that provides a functions that will 
;;; calculate the factorial of a number n
(define fact
  (lambda (n)
    (if (zero? n)
        1
        (* n (fact (- n 1))))))

并更新 main.scm

(begin (write "Hello,Schemer!")
       (newline)
       (write "10! = ")
       (write (number->string (fact 10)))
       (newline))

要“手动”构建此文件,请执行以下操作:

$ gambitc -c factmodule.scm main.scm                      # generate C files from Scheme
$ gambitc -o link_file.c -link factmodule.c main.c        # generate a link file
$ gambitc -obj factmodule.c main.c link_file.c            # compile the C files in object files
$ gcc -o myexec -factmodule.o main.o link_file.o -lgambit # link the final executable

我的问题是第二步,创建链接文件。理想情况下,我想在 cmake / CMakegambitinformation.cmake 添加以下内容

# Generate the C,link,and object files
set( CMAKE_gambit_COMPILE_OBJECT
    "<CMAKE_gambit_COMPILER> -o <OBJECT> -c <SOURCE>"        # generate C files
    "<CMAKE_gambit_COMPILER> -o link_file.c -link <OBJECTS>" # generate link file
    "<CMAKE_gambit_COMPILER> -obj <OBJECTS>"                 # compile C and link files
)

但是这有两个问题。其中之一,<OBJECTS>保存生成的C文件;据我了解,CMAKE_gambit_COMPILE_OBJECT中给出的命令是在每个源文件的基础上执行的。第二,我显然只希望只运行最后两个命令一次,但是在调用CMAKE_gambit_LINK_EXECUTABLE的命令之前。

对象创建之后但链接之前有没有一种执行自定义命令的方法

我在CMake/Modules中研究了其他一些编译器,但实际上找不到能做到这一点的语言。 this entry似乎是添加新语言的最完整文档,official doc似乎并未真正提及它。

解决方法

以下技巧应该可以实现您所追求的目标。该解决方案释放了gsccmake并不总是很好玩的原因,因为它们都有隐式处理文件扩展名的方式。无论如何,让我们开始吧。

我打算从cmake内部复制(文件名略有不同)的一系列命令是

gsc -c linkstub.scm
gsc -c factmodule.scm
gsc -c main.scm
gsc -obj linkstub.scm
gsc -obj factmodule.scm
gsc -obj main.scm
gsc -o linkstub.c -link factmodule.c main.c
gsc -obj linkstub.c
gsc -o test -exe factmodule.o main.o linkstub.o

在这里linkstub.scm是一个空的(生成的)虚拟文件。我们需要此链接来进行最终链接,因为我们无法修改传递到<OBJECTS>的{​​{1}}列表。相应的CMAKE_Gambit_LINK_EXECUTABLE文件将是linkstub.c生成的实际链接文件。这可以通过gsc -o linkstub.c -link ...及其add_custom_command选项(每次应该链接目标时执行命令)来实现。相同的自定义命令还将PRE_LINK编译为目标文件。在最终链接开始之前,前一个linkstub.c被新创建的linkstub.o覆盖。这样,我们就可以欺骗CMake吞下linkstub.o,只要其他所有目标文件都已编译,它就会更新。

开始吧。我们需要在它们各自的文件中更改编译器命令和对象扩展名:

set(CMAKE_Gambit_COMPILE_OBJECT
    "<CMAKE_Gambit_COMPILER> -o <SOURCE>.c -c <SOURCE>"
    "<CMAKE_Gambit_COMPILER> -o <OBJECT> -obj <SOURCE>.c")

set(CMAKE_Gambit_OUTPUT_EXTENSION .o)

这有点脏,因为它将在源代码树中留下构建工件(也许您可以在构建树之前添加路径-在这里,我们稍后将自动删除它们)。请注意,在每个源的基础上将.scm编译为.o的扩展性也更好,因为原始尝试中的链接步骤会变得很大(所有.c文件都被编译成可执行文件)。接下来,链接文件的占位符:

set(linkstub ${CMAKE_CURRENT_BINARY_DIR}/linkstub.scm)
file(WRITE ${linkstub} "")

同样,这只是将相应的目标文件放入目标的对象依赖项中的一种手段。现在剩下的一切:

set(sources
    factmodule.scm
    main.scm)

list(TRANSFORM sources
    APPEND ".c"
    OUTPUT_VARIABLE cSources)

list(TRANSFORM cSources
    PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/"
    OUTPUT_VARIABLE cSources)

list(REMOVE_ITEM cSources
    linkstub.scm.c)

add_executable(test ${sources} ${linkstub})

add_custom_command(TARGET test
    PRE_LINK
    COMMAND ${CMAKE_Gambit_COMPILER}
    -o
    ${CMAKE_CURRENT_BINARY_DIR}/linkstub.scm.c
    -link
    ${cSources}
    COMMAND ${CMAKE_Gambit_COMPILER}
    -o 
    ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/test.dir/linkstub.scm.o
    -obj
    ${CMAKE_CURRENT_BINARY_DIR}/linkstub.scm.c
    COMMAND ${CMAKE_COMMAND} -E remove ${cSources})

最后一个命令可以完成所有操作。它重新编写linkstub.scm.c,将其编译为目标文件,最后删除源树中所有生成的.scm.c文件。请注意,其中一条路径中有一个难看的,硬编码的CMakeFiles/test.dir/,应该有一种方法可以从目标中查询该路径以解决此问题。

再次请注意,文件扩展名在这里至关重要,解决方案非常脆弱。 CMake似乎绝对需要附加.o的目标文件,即.scm.ogsc,如果没有另行通知,则会生成.c的{​​{1}}代替 ,这会导致.scm.c步骤生成符号与编译到-link中的那些不匹配。

作为旁注,此方法显然无法处理方案源之间的任何动态/隐式依赖关系-如果以需要.scm.o重新传输和重新传输的方式更改factmodule.scm -编译,将不会为您解决。据我所知,目前还没有办法告诉CMake您想要注册一个自定义的依赖项扫描程序来解析main.scm文件。

看来老的.scm可能做得更好。