在 CMake 中向自定义 compiler/language 添加额外的编译步骤
Add an extra compile step to a custom compiler/language in CMake
这是对之前 . My basic problem was to build a application with Gambit Scheme 的一些跟进。
虽然上述问题中建议的解决方案有效,但它有点麻烦所以我决定尝试将 Gambit Scheme 作为自定义 compiler/language 添加到 CMake。按照 中的建议,我创建了以下文件:
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 文件创建一个 link 文件。这是一个简单的例子:
假设我添加了第二个文件,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
我的问题是第二步,创建 link 文件。理想情况下,我想添加到 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
的命令之前。
有没有办法在对象创建之后 linked 之前执行自定义命令?
我在 CMake/Modules but couldn't really find a language that does that. And this entry seems to be the most complete documentation for adding a new language, the official doc 中研究了其他一些编译器,似乎并没有真正提到它。
以下技巧应该可以实现您的目标。该解决方案消除了 gsc
和 cmake
并不总是相互配合,因为它们都有自己隐式处理文件扩展名的方式。不管怎样,让我们开始吧。
我打算从 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
是一个空的(生成的)虚拟文件。我们需要在最后的 linking 中使用它,因为我们无法修改传递给 CMAKE_Gambit_LINK_EXECUTABLE
的 <OBJECTS>
列表。相应的 linkstub.c
文件将是 gsc -o linkstub.c -link ...
生成的实际 link 文件。这是通过 add_custom_command
及其 PRE_LINK
选项实现的(每次目标应该被 linked 时执行命令)。相同的自定义命令还将 linkstub.c
编译成目标文件;在最后的 linking 开始之前,之前的 linkstub.o
被新创建的覆盖。通过这种方式,我们可以欺骗 CMake 吞下一个 linkstub.o
,只要所有其他目标文件都已编译,该 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)
这有点脏,因为它会在您的源代码树中留下构建工件(也许您可以在构建树的前面添加一个路径 - 在这里,我们稍后会自动删除它们)。请注意,在 per-source 的基础上将 .scm
编译成 .o
也可以更好地扩展,因为您最初尝试中的 linking 步骤可能会变得相当大(所有 .c
文件被编译成可执行文件)。接下来,link 文件的占位符:
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})
最后一条命令发挥了所有作用。它re-writeslinkstub.scm.c
,将其编译成目标文件,最后删除源树中所有生成的.scm.c
文件。请注意,其中一个路径中有一个丑陋的 hard-coded CMakeFiles/test.dir/
,应该有一种方法可以从目标查询此路径以解决此问题。
再次注意,文件扩展名在这里有些关键,而且解决方案很脆弱。 CMake 似乎绝对需要附加 .o
的目标文件,即 .scm.o
。 gsc
但是,如果没有另行通知,将生成 .c
而不是 .scm.c
的 ,这会导致 -link
步骤生成与编译成 .scm.o
.
的符号不匹配的符号
附带说明一下,这种方法显然无法处理方案源之间的任何 dynamic/implicit 依赖关系 - 如果您以 main.scm
需要 [=] 的方式更改 factmodule.scm
70=]- 和 re-compiled,这不会为您解决。据我所知,目前没有办法告诉 CMake 你想注册一个解析 .scm
文件的自定义依赖扫描器。
看来普通的 makefile
可能会做得更好。
这是对之前
虽然上述问题中建议的解决方案有效,但它有点麻烦所以我决定尝试将 Gambit Scheme 作为自定义 compiler/language 添加到 CMake。按照
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 文件创建一个 link 文件。这是一个简单的例子:
假设我添加了第二个文件,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
我的问题是第二步,创建 link 文件。理想情况下,我想添加到 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
的命令之前。
有没有办法在对象创建之后 linked 之前执行自定义命令?
我在 CMake/Modules but couldn't really find a language that does that. And this entry seems to be the most complete documentation for adding a new language, the official doc 中研究了其他一些编译器,似乎并没有真正提到它。
以下技巧应该可以实现您的目标。该解决方案消除了 gsc
和 cmake
并不总是相互配合,因为它们都有自己隐式处理文件扩展名的方式。不管怎样,让我们开始吧。
我打算从 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
是一个空的(生成的)虚拟文件。我们需要在最后的 linking 中使用它,因为我们无法修改传递给 CMAKE_Gambit_LINK_EXECUTABLE
的 <OBJECTS>
列表。相应的 linkstub.c
文件将是 gsc -o linkstub.c -link ...
生成的实际 link 文件。这是通过 add_custom_command
及其 PRE_LINK
选项实现的(每次目标应该被 linked 时执行命令)。相同的自定义命令还将 linkstub.c
编译成目标文件;在最后的 linking 开始之前,之前的 linkstub.o
被新创建的覆盖。通过这种方式,我们可以欺骗 CMake 吞下一个 linkstub.o
,只要所有其他目标文件都已编译,该 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)
这有点脏,因为它会在您的源代码树中留下构建工件(也许您可以在构建树的前面添加一个路径 - 在这里,我们稍后会自动删除它们)。请注意,在 per-source 的基础上将 .scm
编译成 .o
也可以更好地扩展,因为您最初尝试中的 linking 步骤可能会变得相当大(所有 .c
文件被编译成可执行文件)。接下来,link 文件的占位符:
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})
最后一条命令发挥了所有作用。它re-writeslinkstub.scm.c
,将其编译成目标文件,最后删除源树中所有生成的.scm.c
文件。请注意,其中一个路径中有一个丑陋的 hard-coded CMakeFiles/test.dir/
,应该有一种方法可以从目标查询此路径以解决此问题。
再次注意,文件扩展名在这里有些关键,而且解决方案很脆弱。 CMake 似乎绝对需要附加 .o
的目标文件,即 .scm.o
。 gsc
但是,如果没有另行通知,将生成 .c
而不是 .scm.c
的 ,这会导致 -link
步骤生成与编译成 .scm.o
.
附带说明一下,这种方法显然无法处理方案源之间的任何 dynamic/implicit 依赖关系 - 如果您以 main.scm
需要 [=] 的方式更改 factmodule.scm
70=]- 和 re-compiled,这不会为您解决。据我所知,目前没有办法告诉 CMake 你想注册一个解析 .scm
文件的自定义依赖扫描器。
看来普通的 makefile
可能会做得更好。