65.9K
CodeProject 正在变化。 阅读更多。
Home

MSBuild 用于编程和非编程任务

2023年9月29日

MIT

8分钟阅读

viewsIcon

9569

downloadIcon

60

MSBuild 是一个灵活的工具,它不仅可用于 Microsoft 工具链,甚至可用于非编程任务。

目录

为什么选择 MSBuild?
跨平台
为什么选择 .NET?
代码示例
为什么选择 FFmpeg?
实现
项目
属性
目标和并行执行
自定义属性
使用 Visual Studio Code
兼容性和测试
结论

为什么选择 MSBuild?

将 MSBuild 用于非编程工作,难道不是显得过于深奥了吗?好吧,我们仔细看看。

我曾使用过各种脚本系统来开发各种语言或软件开发工具链:Microsoft “CMD” 批处理、Python、JavaScript (搭配 node.js) 甚至一些过时但仍可运行的 WSF、PowerShell、bash,我从未真正倾向于偏爱某一种系统。

我也使用脚本来处理许多非编程任务。我一项需要大量脚本工作的常态化活动是处理不同平台上的各种媒体文件:视频、音频、视频字幕、照片和图形。最近,我决定尝试 MSBuild。原因之一是我最近比平时更深入地研究了 BSBuild。特别是,这与我最近在 GitHub 仓库 dotnet-solution-structure 中发现的开发相关。

我很快意识到 MSBuild 在几个方面提供了显著的好处。归根结底,它们主要归结为 MSBuild 提供了 声明式编程,这与通常的脚本系统提供的 命令式编程 不同。

当您需要处理位于不同目录中、没有任何固定目录结构的文件夹时,这一点非常有益。其扩展的文件掩码语法非常有用。另一个强大的功能是 增量构建。对于软件开发工具链来说,增量构建是常见的,甚至是必需的。但通常的脚本并没有提供这样的功能。同时,许多非编程开发都是增量的。而且某些处理过程耗时比典型的软件构建要长得多。一个臭名昭著的例子是使用现代视频编解码器进行转码。如今,技术的延迟越低,编码过程就越慢。

但使用 MSBuild 最重要的好处可能是它能够以跨平台的方式使用。

跨平台

此处提供的示例项目是跨平台的。请参阅 下文的解释,说明为什么它是跨平台的

关于这一点的一个说明:每次提到 OS “Linux” 时,实际上指的是“非 Windows”。解决方案中为 Linux 设计的所有元素也应该适用于 Windows 以外的任何其他平台。因此,所有与平台相关的条件都计算为 $(OS.StartsWith('Windows') — 我希望这不言自明。

为什么选择 .NET?

MSBuild 可以通过几种方式调用。首先,命令是“MSBuild”。MSBuild 也用于 .NET 项目和解决方案,那时它就是“dotnet build”。

但我推荐使用“dotnet msbuild”。“dotnet build”不适用于非 .NET 解决方案的原因很清楚,这个命令甚至没有“target”选项。但是,为什么是“dotnet msbuild”而不是“MSBuild”呢?

第一个原因是部署:我不知道您的 MSBuild 实例位于何处。开发者通常会同时安装多个 MSBuild 实例,不同版本。至于 .NET,它通常被安装以便应用程序路径“dotnet”在任何地方都可以访问,它知道随其提供的 MSBuild 版本,并会启动最合适的版本。

还有另一个更微妙的原因:使用“dotnet msbuild”,您可以在 MSBuild 属性定义中使用非常方便的 .NET API 函数。有关此功能的详细说明,请参阅 Microsoft 关于 属性函数 的文档。

当然,通过 .NET 执行 MSBuild 的一个主要好处是这种方法是 跨平台的。它适用于安装了 .NET 的任何平台。

“run.cmd”

dotnet msbuild a.project -t:Transcode

这个简单的命令行旨在适用于 Windows 和其他系统。对于 Linux,在使用之前也应该使其可执行。

chmod -x run.cmd

代码示例

为了评估 MSBuild 的价值,我创建了一个代码示例,用于转码 FFmpeg 支持的任何媒体。目标是获取位于某个目录下任意位置的一组媒体文件,统一转码所有文件,并将它们放在一个目录中。它是可自定义的:输入、文件类型和转码选项放在一个“Custom”文件中,最终用户可以修改。

它涵盖了 FFmpeg 的大部分基本功能,其中我们需要一对一的转码。它不支持非常棘手的情况,例如输入和输出元素之间的复杂映射,多遍处理(其中每一遍都应该使用前一遍获得的中间文件),等等。但是,我的代码示例可以通过使用本文提供的相同代码库的附加项目文件来解决这些高级问题。

为什么选择 FFmpeg?

FFmpeg 可能是最通用、最强大的套件,用于处理各种各样的媒体文件。对于视频来说,它可能是最基础的工具。许多人认为他们不使用 FFmpeg,但实际上他们在使用它,因为它构成了大多数媒体编辑器、生成器和其他媒体软件的基础。

对于摄影和其他视觉艺术来说,这一点不太真实,对于其他所有东西,尤其是视频来说,这一点则更加真实。

实现

项目

在这个特定的项目中,只有一个项目文件。而且,它从未被更改。

“a.project”

<Project Sdk="Microsoft.NET.Sdk">

    <ItemGroup>
        <InputFiles Include="$(InputMask)"/>
    </ItemGroup>

</Project>

这是最通用的项目。它不需要更改,因为它只是透明地传递在其他地方定义的输入掩码并生成一组项。

MSBuild 的 概念非常强大。它取代了其他脚本系统所需的那些笨拙的循环,以及遍历任何目录(递归或不递归)的操作。

实际的定义集来自三个通用文件。其中,“Custom.props”特定于项目和处理类型。另外两个应用于所有下游子目录中的所有项目:“Directory.Build.props”“Directory.Build.targets”

这种共享属性和目标的用法类似于 Microsoft 工具链的定义:通用的预定义属性和目标集随 MSBuild、Visual Studio 和其他产品一起提供。这样,就可以添加更多使用通用定义的项目文件。

属性

“Directory.Build.props”

<Project>
    <Import Project="Custom.props"/>

    <PropertyGroup>

        <OutputPath>$(ProjectDir)output</OutputPath>

        <Tool>ffmpeg</Tool> <!-- Linux -->
        <!-- For Windows: modify the path to access ffmpeg: -->
        <Tool
            Condition="$(OS.StartsWith('Windows'))"
            >C:/app/Media/ffmpeg/bin/ffmpeg.exe</Tool>

        <Multitasking
            Condition="$(OS.StartsWith('Windows'))"
            >start</Multitasking>

        <Continue>%5C</Continue> <!-- backslash for Linux -->
        <Continue Condition="$(OS.StartsWith('Windows'))">^</Continue>

    </PropertyGroup>

</Project>

目标和并行执行

“Directory.Build.targets”

<Project>

    <Target Name="Transcode">

        <PropertyGroup>
            <!-- MBSBuild item transform: -->
            <Commands>@(InputFiles ->
                    '$(Multitasking) $(Tool) -y $(Continue)
                    $(InputOptions) $(Continue)
                    -i %(Identity) $(Scale) $(Continue)
                    $(OutputOptions) $(Continue)
                    $(OutputPath)%(Filename).$(OutputFileType)',
                ' %26 ')
            </Commands>
        </PropertyGroup>

        <MakeDir Directories="$(OutputPath)" />
        <Exec Command="$(Commands)"/>

    </Target>

</Project>

使用“@”和“%”的子句是一个非常有趣且强大的功能,MSBuild 的项会进行转换。请参阅 Microsoft 关于 MSBuild 转换 的文档以获取更多信息。在此子句中,我们将项列表转换为执行 FFmpeg 的命令列表。

命令列表使用不同的方式连接,以并行执行 FFmpeg。它在不同的系统上工作方式不同。什么是“%26”?这是字符“&”。MSBuild 不允许直接使用此字符,因此需要使用十六进制转义符号 进行转义。对于 Linux,“&”在命令之间表示并行执行。对于 Windows,此字符也使用,但表示连续执行命令。要在 Windows 上并行执行,每个命令都应以命令“start”作为前缀,该命令定义为属性 Multitasking

如果出于任何原因需要将并行执行转换为连续执行,该如何操作?对于 Windows,$(Multitasking) 的值“start”应变为空字符串。对于 Linux,命令分隔符“&”(“%26”)应加倍,变成“&”。在这种情况下,对于 Windows 也可以是双重的。

还有一个奇怪的属性 Continue。它被定义为将命令显示在多行中,主要是为了清晰起见,尤其是在本文发布时。它与 Linux 和 Windows 的不同字符,“\”和“^”分别对应,而且“\”也是需要转义的字符。请参阅 “Directory.Build.props”

其他输入信息是自定义的,并在单独的“Custom.props”文件中定义,该文件特定于特定的转码任务。它提供了项和属性 @(InputFiles)、$(Options)、$(OutputPath) 和 $(OutputFileType)。所以,最后,让我们看看这个文件。

自定义属性

首先,请注意“**”通配符的使用。它将输入文件集扩展到所有嵌套级别的路径。

“Custom.props”

<Project>

    <PropertyGroup>
        <InputMask>../**/*.png</InputMask>
        <OutputFileType>webp</OutputFileType>
    </PropertyGroup>

    <PropertyGroup>
        <Scale>-vf scale=256:-1</Scale>
        <Scale>-vf scale=-1:128</Scale>
        <Scale></Scale>
    </PropertyGroup>

    <PropertyGroup>
        <InputOptions></InputOptions>
    </PropertyGroup>

    <PropertyGroup>
        <OutputOptions>-lossless 0</OutputOptions>
        <OutputOptions>-lossless 1</OutputOptions>
        <OutputOptions>-quality 0</OutputOptions>
        <OutputOptions>-quality 100</OutputOptions>
        <OutputOptions>-preset none</OutputOptions>
        <OutputOptions>-preset default</OutputOptions>
        <OutputOptions>-preset picture</OutputOptions>
        <OutputOptions>-preset photo</OutputOptions>
        <OutputOptions>-preset drawing</OutputOptions>
        <OutputOptions>-preset icon</OutputOptions>
        <OutputOptions>-preset text</OutputOptions>
        <OutputOptions></OutputOptions>
    </PropertyGroup>

</Project>

在这些定义中,显示了许多不同的选项。一如既往,较低处定义的属性会覆盖上方定义的属性,因此许多定义是冗余的。它们在此处是为了方便和自文档化目的。在此示例中,属性 ScaleOptions 为空,但可以通过在最后一行下方放置另一个属性定义来更改它们。

Scale 的定义中,使用了格式 -vf scale=<width>:<height>,而 -1 表示宽度或高度会自动计算,以确保纵横比保持不变。

使用 Visual Studio Code

这一部分对于软件开发使用更为重要,特别是对于使用边缘化且不太流行的编程系统。其中一些系统没有任何 IDE、调试器或构建工具链,只提供一些命令行工具,如编译器、链接器等。如何在不使用 Visual Studio Code 扩展的情况下处理它们?

这很简单。首先,在“launch.json”的某个配置中添加一个成员 preLaunchTask。这是一个例子

“launch.json”

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "pwa-chrome",
            "request": "launch",
            "name": "Transcode",
            "preLaunchTask": "Build",
            "runtimeArgs": ["--incognito"],
            "file": "${workspaceFolder}/.vscode/index.html",
            "webRoot": "${workspaceFolder}"
        }
    ]
}

这里,“Build”是“tasks.json”文件中的一个任务。

“tasks.json”

{
    "version": "2.0.0",
    "tasks": [{
        "label": "Build",
        "command": "${workspaceFolder}/run.cmd",
        "type": "shell"
    }]
}

这样,启动配置“Transcode”将执行我们的“run.cmd”,然后报告“MSBuild work is complete”。有关更多信息,请参阅示例 “.vscode/*” 文件。

兼容性和测试

该解决方案应与所有安装了 .NET 的平台兼容。

已在 Linux 和 Windows 上进行了彻底测试。

结论

该解决方案在不同平台上运行顺畅。它非常容易升级到更复杂的解决方案。它展示了对于开发各种在不同输入和输出文件之间工作且没有固定目录结构的系统来说,最重要的细节。

© . All rights reserved.