如何在appveyor.yml中将命令分成多行

16

我想在我的appveyor.yml文件中将一个长的构建命令分成多行,但是我无法使它自动换行,因此当第一个FOR命令被截断并返回错误时,构建会失败。 我不确定如何正确地在.yml文件中拆分这些行,以便在Appveyor内重新组装。应该如何做?

以下是一个简化版本:

build_script:
- cmd: >-
    @echo off
    FOR %%P IN (x86,x64) DO ( ^
      FOR %%C IN (Debug,Release) DO ( ^
        msbuild ^
          /p:Configuration=%%C ^
          /p:Platform=%%P ... ^
        || EXIT 1 ^
      ) ^
    )
我希望它在AppVeyor中显示为这样:
@echo off
FOR %%P IN (x86,x64) DO ( FOR %%C IN (Debug,Release) DO ( msbuild /p:Configuration=%%C /p:Platform=%%P ... || EXIT 1 ) )

多余的空格并不重要,重要的是以 FOR 开头直到最后一个 ) 出现在同一行。

请注意,在理论上,Appveyor 也可以看到这个:

@echo off
FOR %%P IN (x86,x64) DO ( ^
  FOR %%C IN (Debug,Release) DO ( ^
    msbuild ^
      /p:Configuration=%%C ^
      /p:Platform=%%P ... ^
    || EXIT 1 ^
  ) ^
)

由于 Windows 的 cmd.exe 解释器会在每行末尾看到续行符(^),并将它们视为一个长命令,是 Appveyor 似乎无法识别 ^ 标记,因此它会将每一行逐个发送到 cmd.exe,而不是将整个多行块一起发送。

这意味着第一种选项似乎是唯一可行的解决方案,其中 YAML 构建成这样一个结构,即 FOR 行和其后面的所有内容组合成单行。

我已尝试了以下方法:

  • 每行末尾没有额外字符的单倍行距。根据这份指南,单倍行距的 YML 应该展开成一行,但是在 Appveyor 中不会发生。
  • 每行末尾没有额外字符的双倍行距。这应该使每行成为单独的命令,确实如此,因为第一个 FOR 命令失败,出现 错误 255,因为它不完整(只有 FOR 行而没有循环的其余部分)。
  • 每行末尾带有 ^ 的双倍行距。Appveyor 只会逐行运行,因此第一个不完整的 FOR 命令会出现 错误 255
  • 如上所示,每行末尾带有 ^ 的单倍行距。与双倍行距相同问题,由于不完整的 FOR 命令,会出现 错误 255
  • 在每行末尾加上 && ^ 实际上可以在运行独立命令时(例如多个 msbuild 语句)起作用,但这对于 FOR 循环来说是行不通的,因为你不能在没有前置命令的情况下使用 &&

有没有什么诀窍可以将单个 cmd 命令拆分成多行在 appveyor.yml 中实现?

5个回答

9
如何在appveyor.yml中将命令拆分成多行?
以下是一些批处理、cmd和ps的语法示例。
希望这些示例能为您节省一些时间...
语法示例
批处理
```batch - cmd: | echo Hello echo World ```
CMD
```cmd - cmd: > echo Hello && echo World ```
PowerShell
```ps - ps: | Write-Host "Hello" Write-Host "World" ```
# please note the & at EOL in the next example
install:
    # Install VULKAN_SDK 
    - if not exist %VULKAN_SDK% (
       curl -L --silent --show-error --output Vulkan_SDK_Installer.exe https://sdk.lunarg.com/sdk/download/%VULKAN_VERSION%/windows/VulkanSDK-%VULKAN_VERSION%-Installer.exe?Human=true &
       Vulkan_SDK_Installer.exe /S
    )
    - dir %VULKAN_SDK%

before_build:
  - |-
    set MINGW32_ARCH=i686-w64-mingw32
  - if exist %PREFIX% set NEEDDEPENDS=rem

  # Depends
  - |-
    %NEEDDEPENDS% mkdir %PREFIX%\include\SDL2
    %NEEDDEPENDS% mkdir %PREFIX%\lib
    %NEEDDEPENDS% cd %TEMP%
    %NEEDDEPENDS% appveyor DownloadFile https://sourceforge.net/projects/gnuwin32/files/gettext/0.14.4/gettext-0.14.4-lib.zip
    %NEEDDEPENDS% mkdir gettext-0.14.4-lib
    %NEEDDEPENDS% move gettext-0.14.4-lib.zip gettext-0.14.4-lib
    %NEEDDEPENDS% cd gettext-0.14.4-lib
    %NEEDDEPENDS% 7z x gettext-0.14.4-lib.zip > nul
    %NEEDDEPENDS% copy include\* %PREFIX%\include > nul
    %NEEDDEPENDS% copy lib\* %PREFIX%\lib > nul
    %NEEDDEPENDS% cd ..

deploy_script:
  # if tagged commit, build/upload wheel
  - IF "%APPVEYOR_REPO_TAG%"=="true" IF NOT "%TESTENV%"=="check" (
      pip install twine &&
      python setup.py register &&
      twine upload -u %PYPI_USER% -p %PYPI_PASS% dist/*
    )

CMD

before_build:
    - cmd: >-     

        mkdir build

        cd .\build

        set OpenBLAS_HOME=%APPVEYOR_BUILD_FOLDER%/%MXNET_OPENBLAS_DIR%

        set OpenCV_DIR=%APPVEYOR_BUILD_FOLDER%/%MXNET_OPENCV_DIR%/build

        cmake .. -DOPENCV_DIR=%OpenCV_DIR% -DUSE_CUDA=0 -DUSE_CUDNN=0 -DUSE_NVRTC=0 -DUSE_OPENCV=1 -DUSE_OPENMP=1 -DUSE_BLAS=open -DUSE_DIST_KVSTORE=0 -G "Visual Studio 12 2013 Win64"

提示

install:
    - ps: >-

        git submodule init

        git submodule update

        if (!(Test-Path ${env:MXNET_OPENBLAS_FILE})) {

            echo "Downloading openblas from ${env:MXNET_OPENBLAS_PKG} ..."

            appveyor DownloadFile "${env:MXNET_OPENBLAS_PKG}" -FileName ${env:MXNET_OPENBLAS_FILE} -Timeout 1200000
        }

install:
      - ps: |
          Add-Type -AssemblyName System.IO.Compression.FileSystem
          if (!(Test-Path -Path "C:\maven" )) {
            (new-object System.Net.WebClient).DownloadFile('https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip', 'C:\maven-bin.zip')
            [System.IO.Compression.ZipFile]::ExtractToDirectory("C:\maven-bin.zip", "C:\maven")
          }

on_success:
  - ps: |
  if ($true)
  {
    Write-Host "Success"
  }

谢谢提供这些示例。其中哪一个可以在Appveyor中将多个.yml行重新组合成单行命令? - Malvineous
1
在任何命令中,在“IF condition()”内部使用额外的括号时要小心。它会破坏批处理解析并导致错误。我通过删除括号来解决了这个问题。setlocal enabledelayedexpansion在这里也可能有帮助,但我还没有尝试过。 - Steve Chavez

4

请使用双引号

build_script:
- cmd: "
    @echo off
    FOR %%P IN (x86,x64) DO (
      FOR %%C IN (Debug,Release) DO (
        msbuild
          /p:Configuration=%%C
          /p:Platform=%%P ...
        || EXIT 1
      )
    )"

您可以查看YAML参考文档,更准确地说是这个示例
我使用appveyor时遇到了同样的问题(多么奇怪的限制!)。使用双引号让我利用了YAML的微妙之处:
  • 在我的一侧保持易于阅读/编写的多行表达式,
  • 具有单行值,在应用程序中有效存储。
您可以查看我如何使用它并查看构建是否通过
对于其他读者,请注意,该行将被折叠,因此需要使用特殊语法编写才能支持它……例如,无法省略OP给出的表达式中的||

3

CMD 命令总是被分成单独的行,并通过包装成 .cmd 文件逐一运行。将您的代码放入 build.cmd,提交到版本库中,然后调用方式如下:

build_script:
- build.cmd

那么在 appveyor.yml 中没有办法将命令放在单独的行上,但是将它们展开以便在 Appveyor 中执行单个行吗?我更喜欢将所有与 Appveyor 相关的内容保存在单个文件中。 - Malvineous
不好意思,目前唯一的方法是将该代码放入批处理文件中。 - Feodor Fitsner
所以每一行都放在单独的.cmd文件中? - Brecht Machiels
1
可以执行多行 PowerShell 脚本(使用“-ps:|”),因此您可以将 CMD 脚本转换为 PS 并使用它来代替。 - Brecht Machiels

3

除了批处理和PowerShell之外,还有另一个提示需要考虑。在appveyor VM上安装了msys2/mingw32/mingw64,C:\msys64\usr\bin\bash,除此之外还有“Git for Windows”版本的mingw64。

不幸的是,在appveyor中让bash中的多行命令正常工作并不简单:

例如:

像这样的一段代码:

cd /tmp
for server in $(grep '^Server' /etc/pacman.d/mirrorlist.msys | awk '{print $3}' | shuf | arch=x86_64 envsubst); do
  echo Trying ${server}
  curl --connect-timeout 10 -LO ${server}msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz && break
done"

变成

  - >-
    C:\msys64\usr\bin\bash -lc "
    cd /tmp;
    for server in $(grep $'\x5eServer' /etc/pacman.d/mirrorlist.msys | awk '{print $3}' | shuf | arch=x86_64 /usr/bin/envsubst); do
    :;  echo Trying ${server};
    :;  curl --connect-timeout 10 -LO ${server}msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz && break;
    done"

为什么会有这么大的差异?

在Appveyor的批处理中,多行bash字符串会出现许多问题:

  1. 批处理中的多行字符串必须以^结尾。但是这样做
# Literal newlines were not working
- |
  bash -c "echo Not ^
           echo good"

# Double quotes become a literal quote
# Equivalent: echo This is""echo now dangling
#             and "" is an escaped "
- |
  bash -c "echo This is"^
          "echo now dangling"

# Double double quote cancel out, but you're back
# to a literal newline, not working
- |
  bash -c "echo This does not"^
          """echo work either"

# The `|` notation does not seem to be useful here
# It just results in a string that won't run in appveyor 
# as we desired, when using literal newlines.
  1. 折叠功能都需要在同一缩进位置,因此不允许进行额外的缩进
# > vs >- vs >+ isn't important, it just strips extra newlines at the end
- >
  bash -c "echo This
  echo works"

# Indent means literal newline again
- >
  bash -c "echo Does not
           echo work"

# Says "This echo says", not "This" and "says"
- >-
  bash -c "
  echo This
  echo says"

# Basically: bash -c "echo This; echo works"
- >-
  bash -c "
  echo This;
  echo works"

为了获得合适的缩进,我们需要用一些虚拟字符来欺骗yaml。
# You cannot have ; after keywords then, do, etc...
- >-
  bash -c "
  if [ 1 == 1 ]; then
  ;  echo No;
  ;  echo good;
  fi

# Add in a dummy "true" + semicolon, as a no-op
- >-
  bash -c "
  if [ 1 == 1 ]; then
  :;  echo This works;
  fi

# Or if you prefer this style
- >-
  bash -c "if [ 1 == 1 ]; then
  :;         echo ""This also works"";
  :;       fi
  1. 臭名昭著的符号 "caret"。 显然它将 ^ 扩展为 ^^,而且没有好的解决方法。
# echoes ^^
bash -c "echo ^"
bash -c "echo \^"
bash -c "echo \\^"

# echoes ^^^^ 
bash -c "echo ^^"

# Uses a hex notation to get around the issue
bash -c "echo $'\x5e'"
  1. 在mingw64中有一些命令需要小心使用,因为它们在bash中只需要\n,但会引入\r\n
# This is really using /mingw64/bin/envsubst in MINGW64 mode
bash -c "for x in $(echo $'${PWD}\n${OLDPWD}' | envsubst); do
         :;  echo ""${x}"" | xxd;
         done"

# Either need to have MSYSTEM set to MSYS, or
bash -c "for x in $(echo $'${PWD}\n${OLDPWD}' | /usr/bin/envsubst); do
         :;  echo ""${x}"" | xxd;
         done"

# or, as a last resort, use dos2unix
bash -c "for x in $(echo $'${PWD}\n${OLDPWD}' | envsubst | dos2unix); do
         :;  echo ""${x}"" | xxd;
         done"

1

如果您知道AppVeyor的期望(我不知道),让我们假设:

@echo off
FOR %%P IN (x86,x64) DO (
    FOR %%C IN (Debug,Release) DO ( msbuild /p:Configuration=%%C /p:Platform=%%P ... || EXIT 1 )
)

然后,通过从Python中转储它,生成适当的YAML非常容易:

import sys
import ruamel.yaml

appveyor_str = """\
@echo off
FOR %%P IN (x86,x64) DO (
    FOR %%C IN (Debug,Release) DO ( msbuild /p:Configuration=%%C /p:Platform=%%P ... || EXIT 1 )
)
"""

data = dict(build_script=[dict(cmd=appveyor_str)])

ruamel.yaml.round_trip_dump(data, sys.stdout)

给你:
build_script:
- cmd: "@echo off\nFOR %%P IN (x86,x64) DO (\n    FOR %%C IN (Debug,Release) DO (\
    \ msbuild /p:Configuration=%%C /p:Platform=%%P ... || EXIT 1 )\n)\n"

在上面的示例中,任何换行符之前都没有空格。

使用折叠块样式标量(带有>)会使您对标量的折叠几乎没有控制权,正如您所经历的那样。在折叠(或文字)块样式标量中也无法转义序列。

如果您的多行字符串不需要转义,则可以尝试将其转储为块样式标量:

import sys
import ruamel.yaml

appveyor_str = """\
@echo off
FOR %%P IN (x86,x64) DO (
    FOR %%C IN (Debug,Release) DO ( msbuild /p:Configuration=%%C /p:Platform=%%P ... || EXIT 1 )
)
"""

data = dict(build_script=[dict(cmd=ruamel.yaml.scalarstring.PreservedScalarString(appveyor_str))])

ruamel.yaml.round_trip_dump(data, sys.stdout)

which gives:

build_script:
- cmd: |
    @echo off
    FOR %%P IN (x86,x64) DO (
        FOR %%C IN (Debug,Release) DO ( msbuild /p:Configuration=%%C /p:Platform=%%P ... || EXIT 1 )
    )

如果您将所有内容都右对齐,这并不像您希望的那样易读,并且在第一个内容后双击换行符即可获得所需的输出:

(即您放置的内容)

import sys
import ruamel.yaml

yaml_str = """\
build_script:
- cmd: >-
    @echo off

    FOR %%P IN (x86,x64) DO (
    FOR %%C IN (Debug,Release) DO (
    msbuild
    /p:Configuration=%%C
    /p:Platform=%%P ...
    || EXIT 1
    )
    )
"""

data = ruamel.yaml.load(yaml_str)

print(data['build_script'][0]['cmd'])

给出:

@echo off
FOR %%P IN (x86,x64) DO ( FOR %%C IN (Debug,Release) DO ( msbuild /p:Configuration=%%C /p:Platform=%%P ... || EXIT 1 ) )

但您无法缩进(从折叠块样式标量的详细信息中):

以空格字符开头的行(更多缩进的行)不会折叠。


很不幸,你的输出仍然大多数都挤在同一行上,这正是我试图避免的,因为这很难阅读。我可以在 .yml 文件中手动将所有内容放在一行上,但这并不容易阅读,所以我正在尝试找出如何在 .yml 文件中有真正的换行符。Appveyor 希望所有内容都在同一行上(换行符终止命令)。 - Malvineous
@Malvineous 我更新了我的答案,并介绍了如何将您的appveyor字符串转储为文字块样式标量。如果这对您不起作用,请更新您的问题以包括该字符串应如何输入到appveyor(特别是关于换行符和前导/尾随空格)。 - Anthon
谢谢更新。我已经更新了问题以展示我的需求 - 问题在于从FOR到最后一个)必须在读取YAML后出现在同一行,但是在.yml文件中,我希望它们在不同的行上。 - Malvineous
我认为你无法通过缩进来实现这一点,因为那会让代码折叠混乱。如果Appveyor删除了你的“^”,你就必须坚持使用左对齐的代码(请参见我的更新)。 - Anthon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接