如何强制 gnu make 不并行构建配方?

发布于 2024-10-06 01:17:27 字数 531 浏览 5 评论 0原文

我如何告诉 gnu make 不要并行构建某些配方。假设我有以下 makefile :

sources = a.xxx b.xxx c.xxx
target  = program

all : $(target)

$(target) : $(patsubst %.xxx,%.o,$(sources))
    $(CXX) -o $@ $<

%.o : %.cpp
    $(CXX) -c -o $@ $<

%.cpp : %.xxx
    my-pre-processor -o $@ $<

但是,my-pre-processor 命令创建具有固定名称的临时文件(我无法更改它)。如果我只使用不带 -j 参数的 make,则效果很好。但是,如果使用 -j 选项,构建有时会失败,因为两次并发调用 my-pre-processor 会覆盖其临时文件。

我想知道是否有一种方法可以告诉 make 它不能构建并行执行 %.cpp : %.xxx 配方的尝试。

How can I tell gnu make not to build some recipe in parallel. Let's say I have the following makefile :

sources = a.xxx b.xxx c.xxx
target  = program

all : $(target)

$(target) : $(patsubst %.xxx,%.o,$(sources))
    $(CXX) -o $@ 
lt;

%.o : %.cpp
    $(CXX) -c -o $@ 
lt;

%.cpp : %.xxx
    my-pre-processor -o $@ 
lt;

However, the my-pre-processor command create temporary files with fixed name (I cannot change this). This is working fine if I just use make without the -j parameter. However, if the -j option is used, the build sometimes fails because two concurrent invocation of my-pre-processor overwrite their temporary files.

I'd like to know if there is a way to tell make that it must not build the try to parallelize the execution of the %.cpp : %.xxx recipes.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

月寒剑心 2024-10-13 01:17:27

解决方案 1

GNU Make 包含特殊的内置伪目标 .NOTPARALLEL

示例:

.PHONY: all clean

.NOTPARALLEL:

anotherTarget: dependency1

解决方案 2

您还可以使用 -j,--命令行上的 jobs[=] 标志,其中 n 是允许并行运行的食谱数量。

用法:
make -jmake --jobs=

示例:
make -j 1make --jobs=1

注意: 省略 将允许任意要执行的配方数量,仅受系统可用资源的限制


解决方案 3

最后,您可以将解决方案 2 中的命令行标志分配给 Makefile

示例中的 MAKEFLAGS 变量:
MAKEFLAGS := -j 1
或者
MAKEFLAGS := --jobs=1

Solution 1

GNU Make contains the special built-in pseudo-target .NOTPARALLEL

Example:

.PHONY: all clean

.NOTPARALLEL:

anotherTarget: dependency1

Solution 2

You can also use the -j <n>,--jobs[=<n>] flag on the command line where n is the number of recipies allowed to run in parallel.

Usage:
make -j <n> or make --jobs=<n>

Example:
make -j 1 or make --jobs=1

note: omitting <n> will allow an arbitrary number of recipes to be executed, only limited by your system's available resources


Solution 3

Finally, you can assign the command line flag in solution 2 to the MAKEFLAGS variable from within your Makefile

Example:
MAKEFLAGS := -j 1
or
MAKEFLAGS := --jobs=1

只等公子 2024-10-13 01:17:27

一个相关的解决方案是指定您想要构建的确切顺序(而不是说“不要并行构建”)。

要指定确切的顺序,您可以使用仅订单先决条件。这是 GNU make 的手册页:

但是,有时您会遇到这样的情况:您想要强加一个
对要调用的规则进行特定排序,而不强制
如果执行这些规则之一,则更新目标。在那种情况下,
您想要定义仅限订单的先决条件。仅限订单先决条件
可以通过在先决条件中放置管道符号 (|) 来指定
列表:管道符号左侧的任何先决条件都是正常的;任何
右侧的先决条件仅适用于订单:

目标:正常先决条件 |仅订购先决条件

这是他们提供的示例:

OBJDIR := objdir
OBJS := $(addprefix $(OBJDIR)/,foo.o bar.o baz.o)

$(OBJDIR)/%.o : %.c
        $(COMPILE.c) $(OUTPUT_OPTION) 
lt;

all: $(OBJS)

$(OBJS): | $(OBJDIR)

$(OBJDIR):
        mkdir $(OBJDIR)

由于竞争条件,我不得不在 make dist 规则上使用仅顺序先决条件。 dist 配方依赖于 distcleandiff 规则,并且 diff 规则执行 svn diff -r XXX 显示确切的更改。有时,make 会删除它刚刚创建的 diff,因为 clean 规则将在 diff 规则之后运行。

A related solution is to specify the exact order you want things built (rather than saying, "don't build in parallel").

To specify the exact order, you can use order-only prerequisites. Here's GNU make's man page on it:

Occasionally, however, you have a situation where you want to impose a
specific ordering on the rules to be invoked without forcing the
target to be updated if one of those rules is executed. In that case,
you want to define order-only prerequisites. Order-only prerequisites
can be specified by placing a pipe symbol (|) in the prerequisites
list: any prerequisites to the left of the pipe symbol are normal; any
prerequisites to the right are order-only:

targets : normal-prerequisites | order-only-prerequisites

And here's the example they offer:

OBJDIR := objdir
OBJS := $(addprefix $(OBJDIR)/,foo.o bar.o baz.o)

$(OBJDIR)/%.o : %.c
        $(COMPILE.c) $(OUTPUT_OPTION) 
lt;

all: $(OBJS)

$(OBJS): | $(OBJDIR)

$(OBJDIR):
        mkdir $(OBJDIR)

I had to use order only prerequisites on a make dist rule due to a race condition. The dist recipe depended on a distclean and diff rules, and the diff rule performed an svn diff -r XXX to show the exact changes. On occasion, make would delete the diff it just created because the clean rule would run after the diff rule.

情仇皆在手 2024-10-13 01:17:27

这是一个可怕的拼凑,但它会完成工作:

b.cpp: a.cpp

c.cpp: b.cpp

或者如果实际上有很多这样的东西,你可以喝一些烈性饮料并执行此操作:(

c-sources = $(sources:.xxx=.cpp)

ALLBUTFIRST = $(filter-out $(firstword $(c-sources)), $(c-sources))
ALLBUTLAST = $(filter-out $(lastword $(c-sources)), $(c-sources))
PAIRS = $(join $(ALLBUTLAST),$(addprefix :,$(ALLBUTFIRST)))

$(foreach pair,$(PAIRS),$(eval $(pair)))

这在 GNUMake 中有效,我不知道其他版本。)

This is a horrible kludge, but it will do the job:

b.cpp: a.cpp

c.cpp: b.cpp

Or if there are actually a lot of these, you can have a few stiff drinks and do this:

c-sources = $(sources:.xxx=.cpp)

ALLBUTFIRST = $(filter-out $(firstword $(c-sources)), $(c-sources))
ALLBUTLAST = $(filter-out $(lastword $(c-sources)), $(c-sources))
PAIRS = $(join $(ALLBUTLAST),$(addprefix :,$(ALLBUTFIRST)))

$(foreach pair,$(PAIRS),$(eval $(pair)))

(This works in GNUMake, I don't know about other versions.)

玩套路吗 2024-10-13 01:17:27

如果临时文件是在当前工作目录中创建的,您也许可以使用子目录(不太漂亮,但很少见):

sources = a.xxx b.xxx c.xxx
target  = program

all : $(target)

$(target) : $(patsubst %.xxx,%.o,$(sources))
    $(CXX) -o $@ 
lt;

%.o : %.cpp
    $(CXX) -c -o $@ 
lt;

%.cpp : %.xxx
    mkdir [email protected]
    s=`realpath 
lt;` && cd [email protected] && my-pre-processor -o ../$@ "${s}" || { $(RM) -r [email protected] && false; }
    $(RM) -r [email protected]

此外,由于您使用的是语法,但不是 GNU make 专用的功能,请注意以下内容等效的 Makefile 应该更可移植

sources = a.xxx b.xxx c.xxx
target  = program

all : $(target)

$(target) : $(sources:.xxx=.o)
    $(CXX) -o $@ 
lt;

.cpp.o:
    $(CXX) -c -o $@ 
lt;

.xxx.cpp:
    mkdir [email protected]
    s=`realpath 
lt;` && cd [email protected] && my-pre-processor -o ../$@ "${s}" || { $(RM) -r [email protected] && false; }
    $(RM) -r [email protected]

.PHONY: all
.SUFFIXES: .xxx .cpp .o

另请注意,GNU make 的固有 .cpp.o: 规则允许用户在命令行上指定标志,(类似于)

.cpp.o:
    $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ 
lt;

您的用户在需要提供时可能会喜欢的标志,比如说,通过 -L... 自定义包含目录

If the temporary files are created in the current working directory, you may be able to use subdirectories (not pretty, but rare):

sources = a.xxx b.xxx c.xxx
target  = program

all : $(target)

$(target) : $(patsubst %.xxx,%.o,$(sources))
    $(CXX) -o $@ 
lt;

%.o : %.cpp
    $(CXX) -c -o $@ 
lt;

%.cpp : %.xxx
    mkdir [email protected]
    s=`realpath 
lt;` && cd [email protected] && my-pre-processor -o ../$@ "${s}" || { $(RM) -r [email protected] && false; }
    $(RM) -r [email protected]

Also, since you are using syntax but not features that are exclusively available to GNU make, please note that the following equivalent Makefile should be more portable

sources = a.xxx b.xxx c.xxx
target  = program

all : $(target)

$(target) : $(sources:.xxx=.o)
    $(CXX) -o $@ 
lt;

.cpp.o:
    $(CXX) -c -o $@ 
lt;

.xxx.cpp:
    mkdir [email protected]
    s=`realpath 
lt;` && cd [email protected] && my-pre-processor -o ../$@ "${s}" || { $(RM) -r [email protected] && false; }
    $(RM) -r [email protected]

.PHONY: all
.SUFFIXES: .xxx .cpp .o

Also note that GNU make's intrinsic .cpp.o: rule allows for users to specify flags on the command line, (similar to)

.cpp.o:
    $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ 
lt;

which your users may like when they need to provide, say, custom include directories via -L...

南街九尾狐 2024-10-13 01:17:27

你最好研究一下automake自带的ylwrap工具的操作:它解决了旧版本lex的大部分相同问题和yacc

http://git .savannah.gnu.org/cgit/automake.git/tree/lib/ylwrap

You would do well to study the operation of the ylwrap tool that comes with automake: It solves most of the same problems for old versions of lex and yacc:

http://git.savannah.gnu.org/cgit/automake.git/tree/lib/ylwrap

雅心素梦 2024-10-13 01:17:27

在 Linux 上,您可以使用 flock file -c "command"https://man7.org/linux/man-pages/man1/flock.1.html。我已经使用flock解决了GPU资源同步问题。

###############################
%.wav: %.txt
    flock .cuda.lock -c "python synthesize.py --in 
lt; --out $@ --dev cuda:0"
################################
%.mp3: %.wav
    ffmpeg -i 
lt; $@
################################

如果我调用:make 1.mp3 2.mp3 3.mp3 -j3 - 它将同步python,但不会同步ffmpeg

On linux you can use flock file -c "command": https://man7.org/linux/man-pages/man1/flock.1.html. I have solved a problem with GPU resource synchronization using the flock.

###############################
%.wav: %.txt
    flock .cuda.lock -c "python synthesize.py --in 
lt; --out $@ --dev cuda:0"
################################
%.mp3: %.wav
    ffmpeg -i 
lt; $@
################################

If I call: make 1.mp3 2.mp3 3.mp3 -j3 - it will sync python, but not ffmpeg.

狼亦尘 2024-10-13 01:17:27

我遇到了同样的问题(我的 sw 签名服务不接受多个并发请求)。我的解决方案是在接收器中添加一个互斥锁:

%.cpp : %.xxx
    @ until mkdir /tmp/make.lock 2>/dev/null ; do sleep 2 ; done
    my-pre-processor -o $@ 
lt;
    @ rmdir /tmp/make.lock

这允许 makefile 的所有其他部分并行运行,但此时只会运行一个“我的预处理器”。

I run into the same problem (my sw signing service did not accept multiple concurrent requests). My solution was to add a mutex in the recepie:

%.cpp : %.xxx
    @ until mkdir /tmp/make.lock 2>/dev/null ; do sleep 2 ; done
    my-pre-processor -o $@ 
lt;
    @ rmdir /tmp/make.lock

This allows all other parts of the makefile to run in parallel, but there will only be one "my-pre-processor" running at the time.

苏璃陌 2024-10-13 01:17:27

@airenas 使用集群的建议可以概括为一个实用程序 Makefile,您可以在此测试 makefile 中包含和使用:

NOTPARALLEL_test.mk 的内容

include NOTPARALLEL.mk

.ONESHELL:
$(shell mkdir -p t/NOTPARALLEL)

.NOTPARALLEL+=t/NOTPARALLEL/%
t/NOTPARALLEL/%::
    date > $@
    cat $@
    sleep 2

t: t/NOTPARALLEL/1 t/NOTPARALLEL/2 t/NOTPARALLEL/3

${USE_NOTPARALLEL}

,这取决于包含的实用程序:

NOTPARALLEL 的内容。 mk

define _flockCmdScript :=
#!/bin/env bash
flock "${1}" -c "${2}"
endef

_flockCmdPath:=$(dir $(lastword $(MAKEFILE_LIST)))_flockcmd
.SECONDARY: ${_flockCmdPath}
${_flockCmdPath}: private SHELL=/bin/bash
.PHONY: flockcmd

PPID!=echo $PPID
_flock_prefix!=mktemp ${TMPDIR-/tmp}/.flock_XXX_NOTPARALLEL.$(notdir $(firstword $(MAKEFILE_LIST)))
override define NOTPARALLEL 
$(shell flock ${_flock_prefix}_make_flockCmdPath -c $'if [ ! -e "${_flockCmdPath}" ] ; then cat > ${_flockCmdPath} <<< \'${_flockCmdScript}\' ; fi ;')
$1: private SHELL=${_flockCmdPath}
$1: private .SHELLFLAGS=${_flock_prefix}_$(subst /,.,$1)
endef

## define macro te be exanded at bottom of makefiles which `include
## NOTPARALLEL.mk` with the line: `${USE_NOTPARALLEL}'
USE_NOTPARALLEL=$(eval $(foreach pattern,${.NOTPARALLEL},$(call NOTPARALLEL,${pattern})))

## distributed & installed with this utility is NOTPARALLEL_test.mk
## which instructively may be called as:
t:
    $(MAKE) -O -j10 -B -f NOTPARALLEL_test.mk # which contains `include NOTPARALLEL.mk`
    $(MAKE) -O -j10 -B -f NOTPARALLEL_test.mk .NOTPARALLEL=t/% -B -j10 # all recipes targetting t/% are evaluated sequentially
    $(MAKE) -O -j10 -B -f NOTPARALLEL_test.mk NOTPARALLEL= -B -j10 # all recipes are run in parallel (since macro NOTPARALLEL (note: no leading period) is defined as empty).

然后您可以调用 NOTPARALLEL_test 文件的多种不同用法,如下所示:

make -O -j10 -B -f NOTPARALLEL.mk  t

这是一些文档:

目的:当您愿意时,在您的 makefile 中包含 NOTPARALLEL.mk
使用Make的并行执行(例如-j/--jobs)但想禁用它
对于选定的模式规则。它定义了两个变量 .NOTPARALLEL 和
USE_NOTPARALLEL。添加(例如使用 Makes += 运算符)到
.NOTPARALLEL 那些匹配目标不应该的模式规则
让他们的食谱彼此并行执行(尽管
仍然允许与任何其他目标并行执行
生成文件)。您也可以在命令行中提供它。放
在 makefile 末尾添加 ${USE_NOTPARALLEL} 来激活它。

它可以通过更改 Make 的 SHELL 来颠覆 GNUMake 的 --jobs 功能
和 .SHELLFLAGS,因此配方作为 flock. 创建的参数运行
一个锁定文件,一次只允许运行一个配方实例。

由于 flock 失败
NFSv4

(并且为了一般性能),锁文件存储在 $[TMPDIR}
(默认为 /tmp)(假设未安装 NFS)。这
lock 文件以当前 makefile 的进程 ID 命名
当前的 Makefile 以及模式本身。

实现将SHELL变量覆盖到bash脚本中
_flockcmd 依次调用 flock $1 -c $2。这个脚本是
第一次时在与 NOTPARALLEL.mk 相同的目录中按需创建
需要。

此方法仅在当前SHELL提供flock时有效
(sh 和 bash 也是如此);具体来说,通常预计它不会起作用
在“高级”使用 make 中,makefile 已更改 SHELL
(例如 perl、sqlite 或 R)。这是因为食谱是
作为 shell 命令传递给 flock 并由其执行。允许
将来可能会实现替代的 SHELL。

注意:如果没有的话,很难想象整个方法的价值
使用 .ONESHELL,但不是必需的。

注意:持有锁的配方仍会占用 Make 的进程槽之一。

@airenas's suggestion to use flock can be generalized as a utility Makefile you can include and use as in this test makefile:

Contents of NOTPARALLEL_test.mk

include NOTPARALLEL.mk

.ONESHELL:
$(shell mkdir -p t/NOTPARALLEL)

.NOTPARALLEL+=t/NOTPARALLEL/%
t/NOTPARALLEL/%::
    date > $@
    cat $@
    sleep 2

t: t/NOTPARALLEL/1 t/NOTPARALLEL/2 t/NOTPARALLEL/3

${USE_NOTPARALLEL}

which depends upon the included utility:

contents of NOTPARALLEL.mk

define _flockCmdScript :=
#!/bin/env bash
flock "${1}" -c "${2}"
endef

_flockCmdPath:=$(dir $(lastword $(MAKEFILE_LIST)))_flockcmd
.SECONDARY: ${_flockCmdPath}
${_flockCmdPath}: private SHELL=/bin/bash
.PHONY: flockcmd

PPID!=echo $PPID
_flock_prefix!=mktemp ${TMPDIR-/tmp}/.flock_XXX_NOTPARALLEL.$(notdir $(firstword $(MAKEFILE_LIST)))
override define NOTPARALLEL 
$(shell flock ${_flock_prefix}_make_flockCmdPath -c $'if [ ! -e "${_flockCmdPath}" ] ; then cat > ${_flockCmdPath} <<< \'${_flockCmdScript}\' ; fi ;')
$1: private SHELL=${_flockCmdPath}
$1: private .SHELLFLAGS=${_flock_prefix}_$(subst /,.,$1)
endef

## define macro te be exanded at bottom of makefiles which `include
## NOTPARALLEL.mk` with the line: `${USE_NOTPARALLEL}'
USE_NOTPARALLEL=$(eval $(foreach pattern,${.NOTPARALLEL},$(call NOTPARALLEL,${pattern})))

## distributed & installed with this utility is NOTPARALLEL_test.mk
## which instructively may be called as:
t:
    $(MAKE) -O -j10 -B -f NOTPARALLEL_test.mk # which contains `include NOTPARALLEL.mk`
    $(MAKE) -O -j10 -B -f NOTPARALLEL_test.mk .NOTPARALLEL=t/% -B -j10 # all recipes targetting t/% are evaluated sequentially
    $(MAKE) -O -j10 -B -f NOTPARALLEL_test.mk NOTPARALLEL= -B -j10 # all recipes are run in parallel (since macro NOTPARALLEL (note: no leading period) is defined as empty).

You can then invoke multiple different usages of NOTPARALLEL_test file like this:

make -O -j10 -B -f NOTPARALLEL.mk  t

Here's some documentation:

PURPOSE: include NOTPARALLEL.mk in your makefiles when you'd like to
use Make's parallel execution (e.g. -j/--jobs) but want to disable it
for selected pattern rules. It defines two variables .NOTPARALLEL and
USE_NOTPARALLEL. Add (e.g. using Makes += operator) to
.NOTPARALLEL those pattern rules whose matching targets ought never
have their recipes executed in parallel with each other (though
still allowed to be executed in parallel with any other target in the
makefile). You can also provide it at the command line. Put
${USE_NOTPARALLEL} at the end of your makefile to activate it.

It works to subvert GNUMake's --jobs feature by changing Make's SHELL
and .SHELLFLAGS so the recipes run as an argument to flock. creating
a lock file allowing only one instance of the recipe to run at a time.

Since flock fails with
NFSv4

(and for performance in general), the lockfile is stored in $[TMPDIR}
(defaulting to /tmp) (which is assumed to not be NFS mounted). The
lock file is named after the current makefile, the process ID of the
current Makefile, and the pattern itself.

The implementation override the SHELL variable to the bash script
_flockcmd which in turn calls flock $1 -c $2. This script is
created on demand in the same directory as NOTPARALLEL.mk when first
needed.

This approach will only work when the current SHELL provides flock
(as do sh & bash); specificially it is NOT generally expected to work
in "advanced" uses of make in which the makefile has changed SHELL
(e.g. to perl, or to sqlite, or to R). This is because the recipe is
passed to, and executed by, flock as a shell command. Allowing for
alternate SHELLs might be implemented in the future.

Note: it is hard to imagine the value of this whole approach without
using .ONESHELL, but it in NOT required.

NB: recipes holding a lock will still use up one of Make's process slots.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文