怎样在cmake的构建阶段添加一个步骤?

huangapple go评论57阅读模式
英文:

How can I add a step to the build phase in cmake?

问题

我正在尝试将我们的安装过程转换为使用CMake,而不是我们今天使用的旧自定义构建。在项目中,我们有一些Perl文件,有时会更改,我们希望将其包含在构建的产品中。在这些Perl文件中,在安装过程中,我们希望设置shebang(#!/path/to/perl),以便它可以在运行CMake的系统上运行。

通常我会使用configure_file(),但由于configure_file()的性质,任何@Unidentified-sequence@在使用configure_file()时,除COPYONLY之外的任何其他选项时将默认为空字符串(https://gitlab.kitware.com/cmake/cmake/-/issues/22740)。这对Perl脚本来说不太方便,因为@符号用于定义数组。

我今天的解决方法是将文件读入CMake并使用字符串替换:

file(READ "${srcDir}/${file}"  FILE_CONTENTS)
#我对文件有完全控制,因此可以添加自定义变量,比如@PERLPATH@,而不是使用正则表达式
string(REGEX REPLACE "#![/A-Za-z_0-9.-]+/perl" "#!${PERL_PATH}" FILE_CONTENTS "${FILE_CONTENTS}")
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${destDir}/${file}" CONTENT "${FILE_CONTENTS}")

这是有效的,但对Perl文件的任何更改都需要重新运行cmake -B /path/to/build -S /path/to/source。而且,它将为任何文件而不仅仅是更改的文件重新运行。

我希望在执行cmake --build时能够自动更新文件。此外,我也希望检查文件是否更改,只有在文件更改时才更新它们(这需要保存旧的更改数据,因此不能在configure期间执行,cmake -B /path/to/build -S /path/to/source)。

如果有人有一个标准化的解决方案,我会很感激,但我了解,CMake中可能不完全支持这种用例。因此,使用一种解决方法可能已足够。获取更改日期很容易,可以使用stat -c %y

是否有一种方法可以强制CMake在cmake --build期间运行某个特定的CMake代码片段,比如上面的示例?

英文:

I am trying to convert our installation process to use cmake instead of our old custom build that we have today. In the project we do have a few perl files that sometimes change that we want to include in the built product. In these perl files we want to set the shebang (#!/path/to/perl) during installation, so it can run on the system where cmake is run.

Usually I would use configure_file(), but due to the nature of configure_file() any @Unidentified-sequence@ will default to an empty string, when using configure_file() with any other option than COPYONLY (https://gitlab.kitware.com/cmake/cmake/-/issues/22740). This becomes inconvenient for perl scripts, as @ is used to define an array.

The workaround I have today is to read the file into cmake and use string replace,

file(READ "${srcDir}/${file}"  FILE_CONTENTS)
#I have full control over the files. Thus adding a custom variable, such as @PERLPATH@, instead of a REGEX, is possible
string(REGEX REPLACE "#!/[/A-Za-z_0-9.-]+/perl" "#!${PERL_PATH}" FILE_CONTENTS "${FILE_CONTENTS}")
file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${destDir}/${file}" CONTENT "${FILE_CONTENTS}")

This works, but any changes to the perl files requires cmake -B /path/to/build -S /path/to/source to be rerun. Also, it will rerun this for any file and not only the changed files.

I would like it to automatically update the files when doing cmake --build. Further, I would also prefer to have a check whether the files are changed or not and only update them then (which requires old change data to be saved and thus cannot be done during configure, cmake -B /path/to/build -S /path/to/source).

I would appreciate if someone had a standardized solution to this, but as I understand it, this use case is not exactly supported in CMake. Thus I would consider it good enough with a workaround. Getting the change date is easy enough with stat -c %y.

Is there any way to force cmake to run some particular cmake code snippet during cmake --build, lets say the above example?

答案1

得分: 1

  1. 使用find_program()查找perl并定义例如PERL_EXECUTABLE
  2. 如果找不到perl(不知道对你有多重要),将PERL_EXECUTABLE设置为默认值,例如/usr/bin/perl,这样configure_file()将始终用非空内容替换shebang中的#!@PERL_EXECUTABLE@
  3. 使用add_custom_command(<fixed-shebang>.perl...)运行cmake -P <your-script-with-configure_file>.cmake [<input-files>],以从其“模板”生成要安装的perl文件。
  4. 使用install(<FILES|PROGRAMS> ${CMAKE_BINARY_DIR}/<fixed-shebang>.perl)命令将它们安装到所需位置。

这样编辑文件将在cmake --build …上“重建”它们,并在cmake --install上安装修复后的shebang版本。

英文:
  1. use find_program() to find perl and define e.g., PERL_EXECUTABLE
  2. if perl is not found (dunno how critical it's for you) set PERL_EXECUTABLE to a default value, e.g., /usr/bin/perl, so configure_file() will always substitute #!@PERL_EXECUTABLE@ in shebang to smth not empty
  3. use add_custom_command(&lt;fixed-shebang&gt;.perl...) to run cmake -P &lt;your-script-with-configure_file&gt;.cmake [&lt;input-files&gt;] to produce the perl file(s) to install from their "templates"
  4. use install(&lt;FILES|PROGRAMS&gt; ${CMAKE_BINARY_DIR}/&lt;fixed-shebang&gt;.perl) command to install 'em to a desired location

This way edit files will "rebuild" 'em on cmake --build … and install fixed shebang versions on cmake --install.

Update:

According to the substitution problem, I can't reproduce:

  1. I pick a random Perl module from my system (e.g., Storable.pm where @ symbols exist) and copy it to a test directory as Storable.pm.in
  2. add #!@PERL_EXECUTABLE@ as the very first line in it
  3. added a subst.cmake to the test directory with the following content:
message(STATUS &quot;PERL_EXECUTABLE=${PERL_EXECUTABLE}&quot;)
configure_file(Storable.pm.in Storable.pm @ONLY)
  1. run cmake -DPERL_EXECUTABLE=/usr/bin/perl -P subst.cmake
  2. and then diff Storable.pm.in Storable.pm:
--- Storable.pm.in      2023-02-24 15:10:18.630116604 +0400
+++ Storable.pm 2023-02-24 15:12:00.587358711 +0400
@@ -1,4 +1,4 @@
-#!@PERL_EXECUTABLE@
+#!/usr/bin/perl
 #
 #  Copyright (c) 1995-2001, Raphael Manfredi
 #  Copyright (c) 2002-2014 by the Perl 5 Porters

答案2

得分: 1

如果此过程的输出文件需要作为任何目标的源文件,请使用add_custom_command。否则,请使用add_custom_target

由于您已经编写了一些CMake来执行任务,只需将该CMake放在其自己的.cmake文件中,并在自定义命令/目标中使用"${CMAKE_COMMAND}" -P <路径到脚本文件>来运行它(请参阅此处的-P文档:链接)。

DEPENDS字段中,指定CMake脚本的路径以及CMake脚本所需的任何输入文件的路径。您将需要在命令字段中使用"-DCMAKE_CURRENT_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}"CMAKE_CURRENT_BINARY_DIR的值传递给CMake脚本,因为脚本不会自动具有调用它的项目的相同变量(请参阅此处有关脚本模式的-D标志的文档:链接)。

如果输出文件需要作为任何目标的源文件(使用add_custom_command路径),则在OUTPUT字段中指定其路径,然后每当构建需要该输出文件的目标时,将运行自定义命令。在add_custom_target路径中,始终运行自定义目标,因此如果您希望有一些精致的逻辑,以避免在输入文件未更改时运行,我认为您需要在脚本文件中实现这一点,您可以通过使用if(file1 IS_NEWER_THAN file2)命令以及file(TOUCH)在自定义目标体上次运行时创建一个带有时间戳的文件来实现这一点(因此如果触摸文件新于所有输入文件,则只需return)。

英文:

If the output file of this procedure is needed as a source file for any targets, use add_custom_command. Otherwise, use add_custom_target.

Since you've already written some CMake to perform the task, just put that CMake in its own .cmake file and run it in the custom command/target using &quot;${CMAKE_COMMAND}&quot; -P &lt;path to that script file&gt;. (see docs on -P here).

In the DEPENDS field, specify the path to the CMake script and the path to any input files that are required by the CMake script. You will need to pass the value of CMAKE_CURRENT_BINARY_DIR to the CMake script in the command field with &quot;-DCMAKE_CURRENT_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}&quot;, since the script will not automatically have the same variables as the project that calls it (see docs on script-mode's -D flag here).

If the output file is needed as a source file for any targets (the add_custom_command route), then specify its path in the OUTPUT field, and then the custom command will be run as needed any time a target that needs that output file gets built. In the add_custom_target route, custom targets are always run, so if you want to have fancy logic that skips running if the input files haven't changed, I think you'll need to implement that in the script file itself, which you can do by using the if(file1 IS_NEWER_THAN file2) command, and file(TOUCH) to create a file with the timestamp of when the custom target body was last run (so if the touch file is newer than all the input files, just return).

huangapple
  • 本文由 发表于 2023年2月24日 00:05:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/75547348.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定