【问题标题】:Passing arguments to "make run"将参数传递给“make run”
【发布时间】:2011-01-13 22:45:40
【问题描述】:

我使用 Makefile。

我有一个名为run 的目标,它运行构建目标。简化后如下所示:

prog: ....
  ...

run: prog
  ./prog

有什么方法可以传递参数吗?这样

make run asdf --> ./prog asdf
make run the dog kicked the cat --> ./prog the dog kicked the cat

【问题讨论】:

标签: makefile


【解决方案1】:

我不知道有一种方法可以完全按照您的意愿行事,但一种解决方法可能是:

run: ./prog
    ./prog $(ARGS)

然后:

make ARGS="asdf" run
# or
make run ARGS="asdf"

【讨论】:

  • @Rob: $() 更便携,它可以在 Nmake 和 make 中使用。
  • @Rob:Nmake 从未支持 ${} 进行宏扩展,现在它似乎是一种古老的形式。我看过的每个在线教程都推荐使用 $()。 $() 也更符合 bash 等其他工具。
  • 也许它是过时的。我一直使用 ${},但 GNU Make 手册指出“要替换变量的值,请在括号或大括号中写一个美元符号,后跟变量名称:$(foo)' or ${foo}' 是对变量‘foo’的有效引用。”并继续给出仅使用 $() 的示例。嗯嗯。
  • 欢呼约翰和冷静,我回去看到这个建议来自我的第一版 OReilly 书“使用 Make 管理项目”的副本。作者陈述了使用 () 和能够同时执行这两种操作的宏的归档替换规则,但建议使用 {} 来区分。但是.... 新版本现在重新命名为“使用 GNU Make 管理项目”始终使用 ()。去想......我猜我必须现代化! (-:我仍然对在 {} 的 MS NMake 吐槽感到惊讶。
  • @xealits 肯定是 - 问题here 中有一个示例
【解决方案2】:

这个问题已经快三年了,但无论如何......

如果您使用 GNU make,这很容易做到。唯一的问题是make 会将命令行中的非选项参数解释为目标。解决办法是把它们变成无所事事的目标,所以make 不会抱怨:

# If the first argument is "run"...
ifeq (run,$(firstword $(MAKECMDGOALS)))
  # use the rest as arguments for "run"
  RUN_ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
  # ...and turn them into do-nothing targets
  $(eval $(RUN_ARGS):;@:)
endif

prog: # ...
    # ...

.PHONY: run
run : prog
    @echo prog $(RUN_ARGS)

运行此命令:

$ make run foo bar baz
prog foo bar baz

【讨论】:

  • 这很好,只是它似乎不适用于以破折号开头的参数:prog foo bar --baz
  • 在这种情况下它也可以工作,但你必须告诉make 不要将--baz 解释为命令行选项:make -- prog foo bar --baz-- 的意思是“这之后的一切都是一个论点,而不是一个选项”。
  • 将现有目标转换为“无操作目标”仍将运行原始目标的任何依赖项,即如果已经存在“test: foo”目标,“make run pip install test”仍将运行运行“foo”目标。
  • 好点蓝眼!但有一个解决方案:用$(eval $(RUN_ARGS):dummy;@:) 替换“eval”行,没有定义虚拟目标。
  • @LukaszDynowski 是的,您可以将其替换为 ifneq ($(findstring $(firstword $(MAKECMDGOALS)),$(TARGETS)),)
【解决方案3】:

对于标准 make,您可以通过定义这样的宏来传递参数

make run arg1=asdf

然后像这样使用它们

run: ./prog $(arg1)
   etc

make 的参考资料 微软的NMake

【讨论】:

  • 什么是宏?这不就是人们所说的环境变量吗?宏和环境变量有什么区别?
  • @KcFnMi - 根据Google 上的“定义宏”,宏是“自动扩展为一组指令以执行特定任务的单个指令”。 search 上“什么是环境变量”的最高结果是:“环境变量是一个动态命名的值,它可以影响正在运行的进程在计算机上的行为方式。它们是环境的一部分。一个进程运行。”
  • 每次我听到“宏”,它都会让我想起 microsoft excel“你想启用宏吗?”我仍然没有清楚地理解这意味着什么。我尽量避免这个词,“变量”不够恰当吗?
  • @KcFnMi,你的直觉是正确的,这不是这个词的典型用法中的宏。可能会出现混淆,因为它有点类似于 C“宏”的子集,如#define ARG1 asdf。尽管我将其称为编译时常量、“磅定义”或类似的东西,因为它并不是真正的宏(因为它仅扩展为单个常量,而不是语句/语句集)。跨度>
【解决方案4】:

TL;DR 不要尝试这样做

$ make run arg

改为创建脚本build_and_run_prog.sh:

#! /bin/sh
# rebuild prog if necessary
make prog
# run prog with some arguments
./prog "$@"

然后这样做:

$ ./build_and_run_prog.sh arg

继续阅读以了解为什么这是最合理的选择以及为什么最好避免使用其他替代方案


对上述问题的回答:如何将参数传递给 make 目标

您可以在配方中使用变量

run: prog
    ./prog $(var)

然后将变量赋值作为参数传递给 make

$ make run var=arg

这将执行./prog arg

但要小心陷阱。我将在下面详细说明这种方法和其他方法的缺陷。


回答您的问题背后的假设意图:您希望使用一些参数运行 prog,但如果需要,请在运行前重建它。

创建一个脚本,必要时重建,然后使用 args 运行 prog

build_and_run_prog.sh

#! /bin/sh
# rebuild prog if necessary
make prog
# run prog with some arguments
./prog "$@"

这个脚本的意图非常明确。它使用 make 来做它擅长的事情:构建。它使用一个 shell 脚本来做它擅长的事情:批处理。

此外,您还可以利用 shell 脚本的充分灵活性和表现力做任何您可能需要的事情,而无需使用 makefile 的所有警告。

此外,调用语法现在几乎相同:

$ ./build_and_run_prog.sh foo "bar baz"

相比:

$ ./prog foo "bar baz"

对比

$ make run var="foo bar\ baz"

ma​​ke 如何处理参数的背景说明

Make 并非旨在将参数传递给目标。命令行上的所有参数都被解释为目标(也称为目标)、选项或变量赋值。

所以如果你运行这个:

$ make run foo --wat var=arg

make 会将runfoo 解释为目标(targets),以根据他们的配方进行更新。 --wat 作为 make 的一个选项。并将var=arg 作为变量赋值。

更多详情见gnu manual on goals (targets):

还有terminology


为什么我不建议使用变量赋值

$ make run var=arg

和配方中的变量

run: prog
    ./prog $(var)

这是将参数传递给配方的最“正确”和直接的方法。但是,虽然它可以用于运行带有参数的程序,但它肯定不是为那样使用而设计的。见gnu manual on overriding

在我看来,这有一个很大的缺点:你想要做的是运行 prog 和参数 arg。但不是写:

$ ./prog arg

你在写:

$ make run var=arg

当尝试传递多个参数或包含空格的参数时,这会变得更加尴尬:

$ make run var="foo bar\ baz"
./prog foo bar\ baz
argcount: 2
arg: foo
arg: bar baz

比较:

$ ./prog foo "bar baz"
argcount: 2
arg: foo
arg: bar baz

这就是我的prog 的样子:

#! /bin/sh
echo "argcount: $#"
for arg in "$@"; do
  echo "arg: $arg"
done

还请注意,您不应在 makefile 中将 $(var) 放在引号中:

run: prog
    ./prog "$(var)"

因为prog 将永远只得到一个参数:

$ make run var="foo bar\ baz"
./prog "foo bar\ baz"
argcount: 1
arg: foo bar\ baz

这就是我不推荐这条路线的原因。


为了完整起见,这里有一些“传递参数以使运行”的其他方法。

方法一

run: prog
    ./prog $(filter-out $@, $(MAKECMDGOALS))

%:
    @true

从目标列表中过滤掉当前目标。创建捕获所有目标 (%),它不会默默地忽略其他目标。

方法二

ifeq (run, $(firstword $(MAKECMDGOALS)))
  runargs := $(wordlist 2, $(words $(MAKECMDGOALS)), $(MAKECMDGOALS))
  $(eval $(runargs):;@true)
endif

run:
    ./prog $(runargs)

如果目标是run,则删除第一个目标并使用eval为剩余目标创建无操作目标。

两者都允许你写这样的东西

$ make run arg1 arg2

我建议阅读gnu manual on make 了解更多详情。

方法一的问题:

  • 以破折号开头的参数将由 make 解释,而不是作为目标传递。

      $ make run --foo --bar
    

    解决方法

      $ make run -- --foo --bar
    
  • 等号的参数将被make解释而不是通过

      $ make run foo=bar
    

    没有解决办法

  • 带空格的参数很尴尬

      $ make run foo "bar\ baz"
    

    没有解决办法

  • 如果一个参数恰好是run(等于目标),它也会被删除

      $ make run foo bar run
    

    将运行 ./prog foo bar 而不是 ./prog foo bar run

    方法 2 可能的解决方法

  • 如果参数是合法目标,它也会被运行。

      $ make run foo bar clean
    

    将运行 ./prog foo bar clean 以及目标 clean 的配方(假设它存在)。

    方法 2 可能的解决方法

  • 当您输入错误的合法目标时,由于捕获所有目标,它将被静默忽略。

      $ make celan
    

    只会默默地忽略celan

    解决方法是让一切变得冗长。所以你看看会发生什么。但这会给合法输出带来很多噪音。

方法2的问题:

  • 如果参数与现有目标具有相同的名称,则 make 将打印一条警告,说明它正在被覆盖。

    没有我知道的解决方法

  • 带有等号的参数仍然会被 make 解释并且不会被传递

    没有解决办法

  • 带空格的参数还是很尴尬

    没有解决办法

  • 带有空格的参数 eval 试图创建什么都不做的目标。

    解决方法:创建全局捕获所有目标,如上所示不做任何事情。上面的问题是它会再次默默地忽略输入错误的合法目标。

  • 它使用eval 在运行时修改makefile。在可读性和可调试性以及Principle of least astonishment 方面,你还能走多远。

    解决方法:不要!

我只用 gnu make 测试过。其他品牌可能有不同的行为。

【讨论】:

    【解决方案5】:

    您可以将变量传递给 Makefile,如下所示:

    run:
        @echo ./prog $$FOO
    

    用法:

    $ make run FOO="the dog kicked the cat"
    ./prog the dog kicked the cat
    

    或:

    $ FOO="the dog kicked the cat" make run
    ./prog the dog kicked the cat
    

    或者使用Beta提供的解决方案:

    run:
        @echo ./prog $(filter-out $@,$(MAKECMDGOALS))
    %:
        @:
    

    %: - 匹配任何任务名称的规则; @: - 空食谱 = 什么也不做

    用法:

    $ make run the dog kicked the cat
    ./prog the dog kicked the cat
    

    【讨论】:

      【解决方案6】:

      下面是另一种解决方案,可以帮助解决其中一些用例:

      test-%:
          $(PYTHON) run-tests.py $@
      

      换句话说,选择一些前缀(在本例中为test-),然后将目标名称直接传递给程序/运行器。我想如果涉及一些运行脚本可以将目标名称解包为对底层程序有用的东西,这将非常有用。

      【讨论】:

      • 您也可以使用$* 只传入与% 匹配的目标部分。
      【解决方案7】:

      没有。查看 GNU make 手册页中的语法

      make [ -f makefile ] [ 选项 ] ... [ 目标 ] ...

      您可以指定多个目标,因此“否”(至少按照您指定的确切方式为“否”)。

      【讨论】:

        【解决方案8】:

        您可以在命令行中显式提取每个第 n 个参数。为此,您可以使用变量MAKECMDGOALS,它包含赋予“make”的命令行参数列表,它解释为目标列表。如果要提取第n个参数,可以将该变量与“word”函数结合使用,例如,如果要第二个参数,可以将其存储在一个变量中,如下所示:

        second_argument := $(word 2, $(MAKECMDGOALS) )
        

        【讨论】:

        • 这也为该参数运行 make 命令。 make: *** No rule to make target 'arg'. Stop.
        • @ThomasReggi 你可以添加一个“catch all”目标来抑制错误:``` %: @echo Done ``
        【解决方案9】:

        run: ./prog 看起来有点奇怪,因为右边应该是一个目标,所以run: prog 看起来更好。

        我的建议很简单:

        .PHONY: run
        
        run:
            prog $(arg1)
        

        我想补充一点,可以传递参数:

        1. 作为参数:make arg1="asdf" run
        2. 或被定义为环境:arg1="asdf" make run

        【讨论】:

          【解决方案10】:

          对此不太自豪,但我不想传入环境变量,所以我颠倒了运行固定命令的方式:

          run:
              @echo command-you-want
          

          这将打印您要运行的命令,因此只需在子 shell 中评估它:

          $(make run) args to my command
          

          【讨论】:

          • 两年后回头看这个答案——为什么我一直固执到不想使用环境变量,为什么我认为内联生成另一个命令更好?跨度>
          【解决方案11】:

          这是我的例子。请注意,我是在 Windows 7 下使用 Dev-Cpp 附带的 mingw32-make.exe 编写的。 (我有c:\Windows\System32\make.bat,所以命令还是叫“make”。)

          clean:
              $(RM) $(OBJ) $(BIN) 
              @echo off
              if "${backup}" NEQ "" ( mkdir ${backup} 2> nul && copy * ${backup} )
          

          用于定期清洁:

          make clean
          

          用于在 mydir/ 中清理和创建备份:

          make clean backup=mydir
          

          【讨论】:

            【解决方案12】:

            我找到了一种使用等号= 获取参数的方法!答案尤其是对@lesmana 's answer 的补充(因为它在这里是最完整和解释最完整的一个),但是如果将其写成评论就太大了。我再次重复他的信息:TL;DR 不要尝试这样做!

            我需要一种方法来处理我的论点 --xyz-enabled=false(因为默认值为 true),我们现在都知道这不是 make 目标,因此不是 $(MAKECMDGOALS) 的一部分。

            通过回显$(.VARIABLES) 查看all variables of make 时,我得到了这些有趣的输出:

            [...] -*-command-variables-*- --xyz-enabled [...]
            

            这使我们可以采用两种方式:要么以-- 开头(如果这适用于您的情况),或者查看GNU make specific (probably not intended for us to use) 变量-*-command-variables-*-。 ** 有关其他选项,请参见页脚 ** 在我的情况下,此变量为:

            --xyz-enabled=false
            

            有了这个变量,我们可以将它与现有的$(MAKECMDGOALS) 解决方案结合起来,从而定义:

            # the other technique to invalidate other targets is still required, see linked post
            run:
                @echo ./prog $(-*-command-variables-*-) $(filter-out $@,$(MAKECMDGOALS))`
            

            并将其与(明确混淆参数的顺序)一起使用:

            make run -- config --xyz-enabled=false over=9000 --foo=bar show  isit=alwaysreversed? --help
            

            返回:

            ./prog isit=alwaysreversed? --foo=bar over=9000 --xyz-enabled=false config show --help
            

            如您所见,我们放松了 args 的总顺序。带有“assignment”-args 的部分似乎已经颠倒了,“target”-args 的顺序保持不变。我将“赋值”-args 放在开头,希望您的程序不关心参数的放置位置。


            下面的 make 变量看起来也很有希望:

            MAKEFLAGS =  -- isit=alwaysreverse? --foo=bar over=9000 --xyz-enabled=false
            MAKEOVERRIDES = isit=alwaysreverse? --foo=bar over=9000 --xyz-enabled=false
            

            【讨论】:

              【解决方案13】:

              我使用的另一个技巧是-n 标志,它告诉make 进行试运行。例如,

              $ make install -n 
              # Outputs the string: helm install stable/airflow --name airflow -f values.yaml
              $ eval $(make install -n) --dry-run --debug
              # Runs: helm install stable/airflow --name airflow -f values.yaml --dry-run --debug
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2021-09-20
                • 2019-05-01
                • 2018-12-29
                • 2017-01-12
                • 1970-01-01
                • 2017-08-24
                • 1970-01-01
                相关资源
                最近更新 更多