“automake: 如何可移植地抛出错误并中止目标”

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

automake: how to portably throw an error and aborting the target

问题

根据在configure脚本中检查的条件,我想要抛出一个错误,从而中止目标操作(即,如果测试框架未安装,则拒绝在'make check'上编译测试)。

我想要实现的行为是,当您调用make check时,它将直接打印诊断信息并退出。

然而,如果Makefile包含以下内容:

# 如果check丢失,不执行任何操作
if MISSING_CHECK
check:
	@echo "由于缺少check,测试被禁用"
	$(error 由于缺少check,测试被禁用)
endif

Automake会返回以下消息:

automake-1.16: 警告被视为错误
Makefile.am:9: 警告: 由于非POSIX变量名称(可能是GNU make扩展),错误:由于缺少check,测试被禁用
make: *** [Makefile:347: ../Makefile.in] 错误 1

所以问题是,如何可以可移植地引发错误。

感谢@John Bollinger,/Makefile.am现在如下所示:

## 使用automake处理此文件以生成Makefile.in

SUBDIRS = . src tests

# 如果check丢失,不执行任何操作
if MISSING_CHECK
check:
	@echo "由于缺少check,测试被禁用"
	@exit 1
endif

ACLOCAL_AMFLAGS = --install -I build-macro

在/configure.ac中有以下内容:

AM_CONDITIONAL([MISSING_CHECK], [test x${enable_tests} = xno])

其中enable_tests可以设置为yes或no。

/tests/Makefile.am包含以下内容:

# 使用automake处理此文件以生成Makefile.in

AM_CFLAGS = -Wall -Wextra $(CHECK_CFLAGS)

TESTS =
TESTS += test_integer

check_PROGRAMS = $(TESTS)
[...]

如果我执行make check,它当前的输出如下:

在 .
make[1]: 进入目录 '/tmp/build'
make[1]: 不需要为 'check-am' 执行任何操作。
make[1]: 离开目录 '/tmp/build'
在 src 中进行检查
make[1]: 进入目录 '/tmp/build/src'
在 lib 中进行检查
make[2]: 进入目录 '/tmp/build/src/lib'
make[2]: 不需要为 'check' 执行任何操作。
make[2]: 离开目录 '/tmp/build/src/lib'
make[2]: 进入目录 '/tmp/build/src'
make[2]: 离开目录 '/tmp/build/src'
make[1]: 离开目录 '/tmp/build/src'
在 tests 中进行检查
make[1]: 进入目录 '/tmp/build/tests'
make
make[2]: 进入目录 '/tmp/build/tests'
make[2]: 不需要为 'all' 执行任何操作。
make[2]: 离开目录 '/tmp/build/tests'
make  check-TESTS
make[2]: 进入目录 '/tmp/build/tests'
make[3]: 进入目录 '/tmp/build/tests'
============================================================================
libvector 0.9 的测试套件摘要
============================================================================
# 总数: 0
# 通过:  0
# 跳过:  0
# 预期失败: 0
# 失败:  0
# 预期通过: 0
# 错误: 0
============================================================================
make[3]: 离开目录 '/tmp/build/tests'
make[2]: 离开目录 '/tmp/build/tests'
make[1]: 离开目录 '/tmp/build/tests'
由于缺少check,测试被禁用
make: *** [Makefile:820: check] 错误 1

这似乎告诉用户所有测试都已成功。但是,我希望在尝试运行测试套件之前引发错误。

已选择的解决方案

事实证明,覆盖测试框架非常困难,因为它会在任何自定义规则之前无条件地添加,要这样做,您必须依赖于automake的实现定义特性。有关更多详细信息,请参见John Bollinger的回答。

这就是为什么我不覆盖测试框架,而是仅在不打算编译测试时添加一个shell脚本的原因。
/tests/Makefile.am:

# 如果check丢失,运行脚本通知用户
if MISSING_CHECK
check_SCRIPTS = no_test.sh
# 允许脚本输出到stderr
AM_TESTS_FD_REDIRECT = 9>&2
endif

if !MISSING_CHECK
check_PROGRAMS =
check_PROGRAMS += test_integer
endif

TESTS = $(check_SCRIPTS) $(check_PROGRAMS)

该脚本由以下内容生成:
/configure.ac:

AC_CONFIG_FILES([tests/no_test.sh], [chmod +x tests/no_test.sh])

从以下内容生成:
/tests/no_test.sh.in:

#!/bin/bash

# 通知用户由于缺少check或用户偏好,未创建任何测试。
echo "由于缺少check或用户偏好,未创建任何测试。" >&2;
echo "要启用测试,请使用--enable-tests=yes运行configure。" >&2;
exit 1

请注意,由于并行测试框架将stderr/stdout重定向到日志文件,因此用户通知会发出两次。&9然后通过AM_TESTS_FD_REDIRECT = 9>&2将其重定向到真正的stderr。对于串行测试框架,它只是简单地重定向到真正的stderr,并且AM_TESTS_FD_REDIRECT会被忽略。
请参见:https://www.gnu.org/software/automake/manual/html_node/Testsuite-Environment-Overrides.html

顶层/Makefile.am完全没有更改。

英文:

Depending on a condition checked for in the configure script, I want to throw an error, thus aborting the target. (i.e. refuse to compile tests on 'make check', if the test framework is not installed)

The behavior I want to achieve is, that when you call make check, it will directly print diagnostics and exits.

However, if the Makefile contains:

# if check is missing, don't do anything at all
if MISSING_CHECK
check:
	@echo "tests are disabled due to missing check"
	$(error tests are disabled due to missing check)

endif

Automake answers this:

automake-1.16: warnings are treated as errors
Makefile.am:9: warning: error tests are disabled due to missing check: non-POSIX variable name
Makefile.am:9: (probably a GNU make extension)
make: *** [Makefile:347: ../Makefile.in] Error 1

So the question is, how I could portably can throw an error.


Thanks to @John Bollinger /Makefile.am now looks like this:

## Process this file with automake to produce Makefile.in

SUBDIRS = . src tests

# if check is missing, don't do anything at all
if MISSING_CHECK
check:
	@echo "tests are disabled due to missing check"
	@exit 1

endif

ACLOCAL_AMFLAGS = --install -I build-macro

And in /configure.ac there is:

AM_CONDITIONAL([MISSING_CHECK], [test x${enable_tests} = xno])

where enable_tests is either set to yes or no.

/tests/Makefile.am contains this:

# Process this file with automake to produce Makefile.in

AM_CFLAGS = -Wall -Wextra $(CHECK_CFLAGS)

TESTS =
TESTS += test_integer

check_PROGRAMS = $(TESTS)
[...]

If I execute make check, it currently looks like this:

Making check in .
make[1]: Entering directory '/tmp/build'
make[1]: Nothing to be done for 'check-am'.
make[1]: Leaving directory '/tmp/build'
Making check in src
make[1]: Entering directory '/tmp/build/src'
Making check in lib
make[2]: Entering directory '/tmp/build/src/lib'
make[2]: Nothing to be done for 'check'.
make[2]: Leaving directory '/tmp/build/src/lib'
make[2]: Entering directory '/tmp/build/src'
make[2]: Leaving directory '/tmp/build/src'
make[1]: Leaving directory '/tmp/build/src'
Making check in tests
make[1]: Entering directory '/tmp/build/tests'
make  
make[2]: Entering directory '/tmp/build/tests'
make[2]: Nothing to be done for 'all'.
make[2]: Leaving directory '/tmp/build/tests'
make  check-TESTS
make[2]: Entering directory '/tmp/build/tests'
make[3]: Entering directory '/tmp/build/tests'
============================================================================
Testsuite summary for libvector 0.9
============================================================================
# TOTAL: 0
# PASS:  0
# SKIP:  0
# XFAIL: 0
# FAIL:  0
# XPASS: 0
# ERROR: 0
============================================================================
make[3]: Leaving directory '/tmp/build/tests'
make[2]: Leaving directory '/tmp/build/tests'
make[1]: Leaving directory '/tmp/build/tests'
tests are disabled due to missing check
make: *** [Makefile:820: check] Error 1

Which seams to tell a user, that all tests have succeed. But I want the error to be thrown before the Testsuite is tried to run.

Chosen solution

As it turned out, that overwriting the test-harness is quite difficult, because it is unconditionally added before any custom rules and to do so you have to depend on implementation defined characteristics of automake. See John Bollinger's answer for more details.

That's why instead of overwriting the test harness, I add only a single shell script to it, in case the tests aren't to be compiled.
/tests/Makefile.am:

# if check is missing, run script to inform the user of this
if MISSING_CHECK
check_SCRIPTS = no_test.sh
# allow the script to echo to stderr
AM_TESTS_FD_REDIRECT = 9>&2
endif

if !MISSING_CHECK
check_PROGRAMS =
check_PROGRAMS += test_integer

endif

TESTS = $(check_SCRIPTS) $(check_PROGRAMS)

The script is generated by:
/configure.ac:

AC_CONFIG_FILES([tests/no_test.sh], [chmod +x tests/no_test.sh])

from:
/tests/no_test.sh.in:

#!/bin/bash

# Inform the user that tests are disabled and how to change that.
echo "No tests created due to missing check or user preference." >&2;
echo "No tests created due to missing check or user preference." >&9;
echo "To enable tests, run configure with --enable-tests=yes" >&2;
echo "To enable tests, run configure with --enable-tests=yes" >&9;
exit 1

Note that the user notification is issued twice as the stderr/stdout of the script is redirected to logfiles by the parallel test harness. &9 is then redirected to the real stderr by AM_TESTS_FD_REDIRECT = 9>&2. For the serial test harness it is simply directedto the real stderr and AM_TESTS_FD_REDIRECT is just ignored.
See: https://www.gnu.org/software/automake/manual/html_node/Testsuite-Environment-Overrides.html

The toplevel /Makefile.am is not changed at all.

答案1

得分: 1

Autotools 专注于在类似UNIX的系统之间实现可移植性。您可以以不同的方式解决这个问题,但是首先要考虑为什么要使用Autotools。

然而,与Autoconf相比,Automake在分析与可移植性相关的Makefile.am方面能力更强,而Autoconf在相同方面分析configure.ac的能力较弱。如果您将自定义规则写入Makefile.am(或包含它的其他文件),那么应该坚持使用可移植功能,这主要是指POSIX规范中涵盖的功能,GNU扩展基本上不包括在内,特别是所有它提供的make“函数”,如$(error)

通常情况下,在make的配方中,您应该专注于shell功能。在这种情况下,除非您使用make语法指定其他方式,否则如果其中任何命令失败,配方将失败,导致make停止并报告错误。有一个标准的命令,其唯一目的是失败:false。或者,exit 1将以失败的退出状态终止shell,在这种情况下将具有相同的目的。因此,您可以这样做:

# 如果检查缺失,则根本不执行任何操作
if MISSING_REQUIREMENTS
foo:
        @echo "由于缺少先决条件,无法构建可选组件foo"
        @exit 1

endif

或者,您可以考虑是否仅打印诊断消息而无需失败。

然而,您实际上想要做的是修改属于Automake的目标(check)的行为。这是允许的,但有些复杂。特别是,文档指出:

通常不建议覆盖Automake规则,特别是在带有子目录的包的最顶层目录中。

您已经练习了覆盖Automake规则的最不建议情况,“不建议”在这里意味着“它不会产生您可能期望的结果”。

如果您深入研究configure生成的Makefile,您将发现您观察到的行为是由于顶级check目标具有一个前提条件(check-recursive)来实现递归。在覆盖目标check的配方执行之前,将构建前提条件。

在递归Automake makefile中,每个标准目标的本地、非递归版本都带有-am后缀。您实际上可以在您的输出中看到,顶级目录中的check-am是在执行make check时构建的第一个目标。

掌握这些知识后,您可以通过覆盖check-am而不是check来获得您想要的行为:

SUBDIRS = . src tests

# 如果检查缺失,则根本不执行任何操作
if MISSING_CHECK
check-am:
    @echo "由于缺少检查,测试被禁用"
    @exit 1

endif

但请注意:

  1. 要以这种方式获取所需的结果,您必须首先处理顶级目录,这通常与您希望用于其他目的的内容相冲突。

  2. 现在我们依赖于未记录的Automake实现细节。

  3. 一般来说,支持在任何子目录中运行make,这将绕过在顶层实施的任何覆盖或错误生成。

总的来说,我建议不要这样做

确保configure在libcheck不可用时发出警告(AM_COND_IF可以帮助处理)。这是通知构建器问题的最佳且最及时的方式。

CHECK_MISSING为true时,确保不定义任何测试用例,以便在构建器尝试make check时不会出现奇怪的构建失败,尽管已经从configure收到了警告。

可能还要考虑覆盖顶级check目标,以发出关于测试用例缺失的解释性消息,也许是更显眼的消息,例如:

if MISSING_CHECK
check:
    @echo "注意:由于未安装libcheck,无法使用测试用例"
    @echo "==================================================="
    @exit 1

endif

如果您确实需要,可以通过已经描述的方式导致make check在这种情况下失败。

但是,不要担心如果构建器尽管已经收到了没有测试可用的警告仍然执行make check,那么空的测试套件会被执行和总结。

英文:

The Autotools have a strong focus on portability across UNIX-like systems. You can fight that in various ways, but then the question arises of why you are using the Autotools in the first place.

However, Automake has better ability to analyze your Makefile.am with respect to portability than Autoconf has to analyze your configure.ac in the same light. If you write custom rules into your Makefile.am (or into another file that it includes) then you should stick to portable features, which mostly means those covered by the POSIX specifications for make. GNU extensions are largely out, especially all the make "functions" it provides, such as $(error).

> how I could portably can throw an error.

Generally speaking, in make recipes you should focus on shell features. In this case, unless you employ the make syntax to specify otherwise, a recipe will fail, causing make to stop and report an error, if any of the commands in it fails. There is a standard command whose entire purpose is to fail: false. Alternatively, exit 1 will terminate the shell with a failing exit status, which would serve the same purpose in this case. Thus, you might do something like this:

# if check is missing, don't do anything at all
if MISSING_REQUIREMENTS
foo:
        @echo "optional component foo cannot be built due to missing requirements"
        @exit 1

endif

Alternatively, you could consider whether it would be sufficient to just print the diagnostic message, without failing.


HOWEVER, what you actually want to do is alter the behavior of a target (check) that belongs to Automake. This is allowed, but somewhat fraught. In particular, the documentation remarks that

> Overriding Automake rules is often inadvisable, particularly in the topmost directory of a package with subdirectories.

You have exercised exactly the least-advisable case for overriding Automake rules, and "inadvisable" here manifests as "it won't have the result that you probably expect".

If you dig into the Makefile generated by configure, you will discover that the behavior you observe results from the fact that the top-level check target has a prerequisite (check-recursive) that implements the recursion. The prerequisite is built before the override recipe for target check is executed.

In a recursive Automake makefile, the local, non-recursive version of each standard target is named with an -am suffix. You can in fact see in your output that check-am in the top directory is the first target built when you make check.

Armed with that knowledge, you could obtain the behavior you are after by overriding check-am instead of check:

SUBDIRS = . src tests

# if check is missing, don't do anything at all
if MISSING_CHECK
check-am:
    @echo "tests are disabled due to missing check"
    @exit 1

endif

But note that

  1. To get the result you want this way, you have to process the top-level directory first, which often conflicts with what you want for other purposes.

  2. We are now depending on undocumented Automake implementation details.

  3. Generally speaking, it is supported to run make in any of the subdirectories, and doing so will bypass any overrides or error generation implemented at the top level.

Overall, my recommendation is don't do that.

DO have configure emit a warning if libcheck is not available (AM_COND_IF can help with that). This is the best and most timely way to notify a builder of the issue.

DO suppress definition of any test cases when CHECK_MISSING is true, so that you don't hit builders with weird build failures in the event that they try a make check despite the warning from configure.

Possibly do even override the top-level check target to emit an explanatory message about the absence of test cases, maybe something more prominent, such as:

if MISSING_CHECK
check:
    @echo ============================================================================
    @echo "NOTICE: no test cases are available because libcheck is not installed"
    @echo ============================================================================

endif

And if you feel you must, do cause make check to fail in this case, by the means already described.

But DO NOT sweat the fact that an empty test suite is executed and summarized if a builder executes a make check despite having already been notified that no tests are available.

huangapple
  • 本文由 发表于 2023年6月1日 03:49:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/76376806.html
匿名

发表评论

匿名网友

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

确定