构建系统
版本版本

Sublime Text 提供了*构建系统*,允许用户运行外部程序。构建系统的常见用途包括:编译、转译、代码检查和执行测试。

构建系统通过 JSON 指定,并保存在扩展名为 .sublime-build 的文件中。可以通过 工具 ▶ 构建系统 ▶ 新建构建系统… 菜单项或 构建: 新建 构建 系统 命令面板条目创建新的构建系统。

构建系统可以通过多种方式与文件和项目相关联。利用这些信息,Sublime Text 可以智能地只向用户显示可行的构建系统。内置的 exec 目标提供了一些常用选项,可以快速启动和运行。对于更复杂的需求,构建系统可以以用 Python 编写的自定义 Sublime Text 命令为目标。

基本示例🔗

以下是一个构建系统的基本示例。此构建系统将执行当前打开的 Python 文件。

{
    "cmd": ["python", "$file"],
    "selector": "source.python",
    "file_regex": "^\\s*File \"(...*?)\", line ([0-9]*)"
}

用法选项 部分将讨论如何使用和自定义构建系统。

用法🔗

构建系统包括以下功能

  • 根据文件类型自动选择构建系统

  • 记住上次使用的构建系统

  • 导航构建系统结果

  • 能够取消构建

运行构建🔗

可以通过以下方法之一运行构建

键盘

菜单

Windows/Linux

Mac

所有

工具 ▶ 构建

Ctrl+B

+B

F7

输出将显示在 Sublime Text 窗口底部显示的输出面板中。

选择构建系统🔗

默认情况下,Sublime Text 使用自动选择构建系统。当用户调用构建时,将使用当前文件的语法和文件名来选择合适的构建系统。

如果有多个构建系统与当前文件类型匹配,则会提示用户选择要使用的构建系统。选择构建系统后,Sublime Text 会记住它,直到用户更改选择。

要手动选择构建系统,请使用:工具 ▶ 构建系统

要更改构建系统,请在可行选项中使用以下方法之一

键盘

菜单

命令面板

Windows/Linux

Mac

工具 ▶ 使用…构建

构建 使用:

Ctrl+Shift+B

++B

取消构建🔗

可以通过以下方式取消正在进行的构建

键盘

菜单

命令面板

Windows/Linux

Mac

工具 ▶ 取消构建

构建: 取消

Ctrl+Break

Ctrl+C

选项🔗

所有构建系统都可以在 .sublime-build 文件中使用以下顶级键

"selector" string🔗

一个 选择器,用于匹配此构建系统应启用的语法。

示例:"source.python"

"file_patterns" 字符串 数组🔗

构建系统应启用的文件名模式

例如: ["*.py"]

"keyfiles" 字符串 数组🔗

如果在打开的文件夹中存在这些文件名,则将启用构建系统。

例如: ["Makefile"]

"variants" 对象 数组🔗

将从顶级构建系统继承选项的子级构建系统。每个变体都需要指定一个 "name" 键,并且可以覆盖或添加顶级构建系统的选项。

例如

[
    {
        "name": "Debug Symbols",
        "cmd": ["my_command", "-D", "$file"]
    }
]
"cancel" 字符串, 字符串 数组🔗

字符串命令名称或字符串选项数组。

如果指定了字符串,则将使用指定的命令取消构建。

如果指定了字符串数组,则将调用主 "target",并将这些选项添加进去。仅在使用自定义 "target" 时才需要指定此项。

例如: "cancel_my_build"{ "kill": true }

"target" 字符串🔗

调用构建系统时要运行的命令。默认值 exec 允许使用 exec 目标选项 中指定的附加选项。

如果指定了 "exec" 以外的值,则 exec 目标选项 中的任何选项都不会起作用。

有关完整示例,请参见高级示例

例如: "my_build"

"windows" 对象🔗

在 Windows 机器上执行构建系统时要使用的选项。

例如

{
    "cmd": ["my_command.exe", "/D", "$file"]
}
"osx" 对象🔗

在 Mac 机器上执行构建系统时要使用的选项。

例如

{
    "cmd": ["/Applications/MyProgram.app/Contents/MacOS/my_command", "-d", "$file"]
}
"linux" 对象🔗

在 Linux 机器上执行构建系统时要使用的选项。

例如

{
    "cmd": ["/usr/local/bin/my_command", "-d", "$file"]
}

exec 目标选项🔗

默认的 target exec 被大多数构建系统使用。它提供了以下选项来控制要执行的程序以及如何显示结果。

"cmd" 字符串 数组🔗

要运行的可执行文件,以及要传递给它的任何参数。不支持诸如管道和重定向之类的 Shell 结构 - 请参见 "shell_cmd"

可以使用变量

例如: ["my_command", "-d", "$file"]

"shell_cmd" 字符串🔗

要执行的 shell 命令。 与 "cmd" 选项不同,这允许管道和重定向。 将在 Mac 和 Linux 机器上使用 bash,在 Windows 上使用 cmd.exe

这优先于 "cmd"。 如果要使用 "cmd" 覆盖构建变体中的 "shell_cmd",则还要将 "shell_cmd" 设置为 null

可以使用变量

示例:"my_command \"$file\" | other_command"

"working_dir" 字符串🔗

执行 "cmd""shell_cmd" 的目录。

可以使用变量

示例:"$file_path"

"file_regex" 字符串🔗

要在构建输出上运行以匹配文件信息的正则表达式。 匹配的文件信息用于启用结果导航。 正则表达式应捕获 2、3 或 4 个组。

捕获组应该是

  1. 文件名

  2. 行号

  3. 列号

  4. 消息

示例:"^\s*(\\S[^:]*)\\((\\d+):(\\d+)\\): ([^\\n]+)"

"line_regex" 字符串🔗

要在构建输出上运行以匹配行信息的正则表达式。 匹配的文件信息用于启用结果导航。 正则表达式应捕获 1、2 或 3 个组。

这些组应该捕获

  1. 行号

  2. 列号

  3. 错误信息

仅当某些结果严格包含行号、行号和列号,或带有消息的行号和列号时,才需要此正则表达式。 当进行此类匹配时,将使用 "file_regex" 选项向后搜索以找到相应的文件名。

示例:"^\s*line (\\d+) col (\\d+): ([^\\n]+)"

"encoding" 字符串🔗

构建系统输出的编码。 使用 Python 编解码器名称。 默认为 "utf-8"

示例:"iso-8859-1"

"env" 对象🔗

运行 "cmd""shell_cmd" 时要使用的环境变量值。

例如

{
    "PYTHONIOENCODING": "utf-8"
}
"quiet" 布尔值🔗

减少有关构建系统调用的输出量。

示例: true

"word_wrap" boolean🔗

在构建系统输出面板中启用自动换行。

示例: true

"syntax" string🔗

用于高亮显示构建系统输出面板的语法文件。

示例: "Packages/JavaScript/JSON.sublime-syntax"

自定义选项🔗

当实现一个命令作为构建系统目标时,该命令的关键字参数可以通过 .sublime-build 文件中的选项来使用。但是,某些参数名称将不起作用,因为它们与内置的构建系统功能冲突。

以下名称将不会作为参数传递给命令。这也适用于其他情况,例如在 "cancel""linux""osx""windows" 选项中指定的选项。

  • "cancel"

  • "file_patterns"

  • "keyfile"

  • "keyfiles"

  • "linux"

  • "osx"

  • "save_untitled_files"

  • "selector"

  • "target"

  • "variants"

  • "windows"

变量🔗

以下变量将在 "cmd""shell_cmd""working_dir" 选项中指定的任何字符串内展开。

如果需要在其中一个选项中指定一个字面量 $,则必须使用 \ 对其进行转义。由于 JSON 也使用反斜杠进行转义,因此 $ 需要写成 \\$

请注意,此替换将发生在任何 <span class=”key”>“target”</span> 中。如果使用自定义目标,它可以通过对 sublime.expand_variables() 使用 self.window.extract_variables() 的结果来实现对其他选项的变量扩展。 </p>

变量

描述

$packages

Packages/ 文件夹的路径。

$platform

Sublime Text 运行的操作系统:"windows""osx""linux"

$file

活动视图中文件的完整路径,包括文件夹。

$file_path

包含活动视图中文件的文件夹的路径。

$file_name

活动视图中文件的文件名(不含文件夹路径)。

$file_base_name

活动视图中文件的文件名,不包括扩展名。

$file_extension

活动视图中文件的文件名的扩展名。

$folder

侧边栏中列出的第一个文件夹的完整路径。

$project

当前项目文件的完整路径。

$project_path

包含当前项目文件的文件夹的路径。

$project_name

当前项目文件的文件名(不含文件夹路径)。

$project_base_name

当前项目文件的文件名,不包括扩展名。

$project_extension

当前项目文件的扩展名。

高级示例🔗

以下示例显示了一个自定义的 target 命令,它能够取消构建并导航结果。

构建系统的 target 应该是一个 sublime_plugin.WindowCommand。这将提供 self.window 的实例变量,以便与当前项目、窗口和活动视图进行交互。

请注意,以下示例的实现方式有些简单,它不会处理许多常见的边缘情况。

以下 Python 代码可以保存到名为 Package/User/my_example_build.py 的文件中

import sublime
import sublime_plugin

import subprocess
import threading
import os


class MyExampleBuildCommand(sublime_plugin.WindowCommand):

    encoding = 'utf-8'
    killed = False
    proc = None
    panel = None
    panel_lock = threading.Lock()

    def is_enabled(self, lint=False, integration=False, kill=False):
        # The Cancel build option should only be available
        # when the process is still running
        if kill:
            return self.proc is not None and self.proc.poll() is None
        return True

    def run(self, lint=False, integration=False, kill=False):
        if kill:
            if self.proc:
                self.killed = True
                self.proc.terminate()
            return

        vars = self.window.extract_variables()
        working_dir = vars['file_path']

        # A lock is used to ensure only one thread is
        # touching the output panel at a time
        with self.panel_lock:
            # Creating the panel implicitly clears any previous contents
            self.panel = self.window.create_output_panel('exec')

            # Enable result navigation. The result_file_regex does
            # the primary matching, but result_line_regex is used
            # when build output includes some entries that only
            # contain line/column info beneath a previous line
            # listing the file info. The result_base_dir sets the
            # path to resolve relative file names against.
            settings = self.panel.settings()
            settings.set(
                'result_file_regex',
                r'^File "([^"]+)" line (\d+) col (\d+)'
            )
            settings.set(
                'result_line_regex',
                r'^\s+line (\d+) col (\d+)'
            )
            settings.set('result_base_dir', working_dir)

            self.window.run_command('show_panel', {'panel': 'output.exec'})

        if self.proc is not None:
            self.proc.terminate()
            self.proc = None

        args = ['my_cli']
        if lint:
            args.append('-l')
        elif integration:
            args.append('-i')
        args.append(vars['file_name'])
        self.proc = subprocess.Popen(
            args,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            cwd=working_dir
        )
        self.killed = False

        threading.Thread(
            target=self.read_handle,
            args=(self.proc.stdout,)
        ).start()

    def read_handle(self, handle):
        chunk_size = 2 ** 13
        out = b''
        while True:
            try:
                data = os.read(handle.fileno(), chunk_size)
                # If exactly the requested number of bytes was
                # read, there may be more data, and the current
                # data may contain part of a multibyte char
                out += data
                if len(data) == chunk_size:
                    continue
                if data == b'' and out == b'':
                    raise IOError('EOF')
                # We pass out to a function to ensure the
                # timeout gets the value of out right now,
                # rather than a future (mutated) version
                self.queue_write(out.decode(self.encoding))
                if data == b'':
                    raise IOError('EOF')
                out = b''
            except (UnicodeDecodeError) as e:
                msg = 'Error decoding output using %s - %s'
                self.queue_write(msg  % (self.encoding, str(e)))
                break
            except (IOError):
                if self.killed:
                    msg = 'Cancelled'
                else:
                    msg = 'Finished'
                self.queue_write('\n[%s]' % msg)
                break

    def queue_write(self, text):
        sublime.set_timeout(lambda: self.do_write(text), 1)

    def do_write(self, text):
        with self.panel_lock:
            self.panel.run_command('append', {'characters': text})

自定义的 MyExampleBuildCommand 可以配置为构建系统,使用以下 JSON 保存到名为 Packages/User/My Example Build.sublime-build 的文件中

{
    "target": "my_example_build",
    "selector": "source.mylang",
    "cancel": {"kill": true},
    "variants": [
        {
            "name": "Lint",
            "lint": true
        },
        {
            "name": "Integration Tests",
            "integration": true
        }
    ]
}