Let Sublime choose among two similar build systems

2019-06-01 03:08发布

问题:

My current setting is Tools > Build System > Automatic.

I use two build systems: Python.sublime-build and Python64.sublime-build.

How to make that Sublime uses the latter if and only if the .py file begins with #python64?

(so that I don't have to manually switch between Sublime's Tools > Build System > Python 64 and Sublime's Tools > Build System > Python).

or, alternatively, how to make that:

  • CTRL+B uses Python (32 bit)

  • CTRL+SHIFT+B uses Python (64 bit)

(both of them should display the output in Sublime's bottom build output panel).


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

 

# Python64.sublime-build
{
    "cmd": ["c:\\python27-64\\python", "-u", "$file"],
    "file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)"
}

PS: I use Windows, and I need both versions of Python (32 and 64), for reasons that would be out of topic here.

回答1:

In general, Sublime automatically selects the appropriate build system based on the type of the file that you are editing (e.g. a python source file). In Sublime Text 3, it is also possible to make a build system activate based on the presence of a certain file (e.g. a file called Makefile).

Neither of these is a viable solution to the use case presented here, and the second is only available in Sublime Text 3 and not Sublime Text 2.

There are a couple of ways to accomplish this. I'm providing two sets of instructions here, one for Sublime Text 2 and one for Sublime Text 3, so that this is a more broadly useful answer.

Option #1 - Custom Build Target

A build system can have an optional argument named target which specifies the command that sublime should execute in order to perform the build. When this is not specified, the default is the exec command. Most of the contents of the build file are actually just arguments that are directly passed to the exec command itself.

By specifying a custom target you can add extra logic to the build command so that has the power to analyze the current file and act appropriately.

The first part of this is to provide the custom command which will be used to perform the build, which can be done with some simple plugin code. This should be saved in your User package as a python file (e.g. Packages\User\python_build.py).

The second part is to modify the build system that you're using in order to take advantage of the new command to do what we want it to do. The single build file will be used both ways. You would name this Python.sublime-build and enable it either as an override to the existing build in Packages\Python\Python.sublime-build or in your User package as Packages\User\Python.sublime-build.

Sublime Text 2 Plugin:

import sublime, sublime_plugin

class PythonBuildCommand(sublime_plugin.WindowCommand):
    def detect_version(self, filename, python32, python64):
        with open(filename, 'r') as handle:
            line = handle.readline ()
        return python64 if (line.startswith ("#") and "64" in line) else python32

    def execArgs(self, sourceArgs):
        current_file = self.window.active_view ().file_name ()
        args = dict (sourceArgs)

        python32 = args.pop ("python32", "python")
        python64 = args.pop ("python64", "python")
        selected = self.detect_version (current_file, python32, python64)

        if "cmd" in args:
            args["cmd"][0] = selected

        return args

    def run(self, **kwargs):
        self.window.run_command ("exec", self.execArgs (kwargs))

Sublime Text 2 Build File:

{
    "target": "python_build",

    "python32": "python",
    "python64": "c:/python27-64/python",

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

Sublime Text 3 Plugin:

import sublime, sublime_plugin

class PythonBuildCommand(sublime_plugin.WindowCommand):
    def detect_version(self, filename, python32, python64):
        with open(filename, 'r') as handle:
            line = handle.readline ()
        return python64 if (line.startswith ("#") and "64" in line) else python32

    def execArgs(self, sourceArgs):
        current_file = self.window.active_view ().file_name ()
        args = dict (sourceArgs)

        python32 = args.pop ("python32", "python")
        python64 = args.pop ("python64", "python")
        selected = self.detect_version (current_file, python32, python64)

        if "shell_cmd" in args:
            args["shell_cmd"] = args["shell_cmd"].replace ("python", selected)

        return args

    def run(self, **kwargs):
        self.window.run_command ("exec", self.execArgs (kwargs))

Sublime Text 3 Build File:

{
    "target": "python_build",

    "shell_cmd": "python -u \"$file\"",
    "file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
    "selector": "source.python",

    "python32": "python",
    "python64": "c:/python27-64/python",

    "env": {"PYTHONIOENCODING": "utf-8"},

    "variants":
    [
        {
            "name": "Syntax Check",
            "shell_cmd": "python -m py_compile \"${file}\"",
        }
    ]
}

Notice that the plugin code is mostly the same in both versions of the code. Sublime Text 3 supports shell_cmd as well as cmd for specifying the executable, and the default build systems for Python in each version reflect that. If desired, the Sublime Text 2 version should also work in Sublime Text 3, as long as you use the appropriate build file as well.

In either case, the custom command will check the first line of the file to see which of the two versions of python that it should execute, modify the command in the build system as appropriate, and then invoke the exec command to perform the build.

The build file itself needs to specify which version of the python interpreter to use in either case, with the fallback (as determined by the code in the plugin) being python for both if it is not specified.

If you are using Sublime Text 3 and place the build file in your User package, your build menu will contain the Python option twice; once for the built in version and once for your own. In this case you may need to ensure that the proper one is selected.

Option #2 - Using a Key Binding

There is no command in either version of Sublime that can run a build and also specify the build system to use (at least not a documented one that I can find). This is still possible with a key binding in both versions, although in the case of Sublime Text 3 is is a little easier.

Sublime Text 2 Key Bindings:

For Sublime Text 2, the command build will perform a build using the currently selected build system and set_build_system can be used to swap the build system around.

In order to do this with a single key press, you need to install the ChainOfCommand plugin, which allows you to chain multiple commands together. With that package installed, you can set up the following key bindings:

{
    "keys": ["ctrl+b"],
    "command": "chain", "args": {"commands": [
        ["set_build_system", {"file": "Packages/Python/Python.sublime-build"}],
        ["build"]
    ]}
},
{
    "keys": ["ctrl+shift+b"],
    "command": "chain", "args": {"commands": [
        ["set_build_system", {"file": "Packages/Python/Python64.sublime-build"}],
        ["build"]
    ]}
}

The first of these changes the build system to be python and then runs the build, while the second one changes it to be Python64. Modify the paths to the build files as appropriate (e.g. if you stored one or both in your User package instead).

This is a bit sub-optimal because it makes the Ctrl+B key always try to build python even if that is not appropriate.

I don't actively use Sublime Text 2 so I'm unsure of how you would go about making these bindings specific only to a python file. The few things I tried that would work in Sublime Text 2 did not work here.

Sublime Text 3 Key Bindings:

For Sublime Text 3, this is a little easier. This version supports variants in a build system, and the build command can be told to execute a variant of the currently selected build.

To get this working, you need a single build system that looks something like the following version. This is a modified version of the standard Sublime Text 3 python build file, which removes the Syntax Check variant in favor of a Python64 version. This could be modified as desired.

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

    "env": {"PYTHONIOENCODING": "utf-8"},

    "variants":
    [
        {
            "name": "Python64",
            "shell_cmd": "c:/python27-64/python -u \"$file\"",
        }
    ]
}

With this in place, add the following key binding:

{
    "keys": ["ctrl+shift+b"],
    "command": "build", "args": {"variant": "Python64"},
    "context": [
        { "key": "selector", "operator": "equal", "operand": "source.python"},
    ]
},

Now the build system has a variant, so that the regular Python build will use the 32 bit version and Python - Python64 will use the 64 bit version instead.

The key binding here is set to operate only in a python file, since in Sublime Text 3 this key sequence is used to prompt you for the variant of the current build to use.

For some initial setup, once you enable this key binding you should open up a python file and select Tools > Build > Build With... from the menu, then select Python in order to tell Sublime that you want to use the Python build.

From this point forward, while you're editing a python file, Ctrl+B will execute the main build, which is the 32-bit python, and Ctrl+Shift+B will execute the variant that uses the 64-bit version.



回答2:

In addition to OdatNurd's excellent answer, here is what I used, using keymaps and variants.

  • CTRL+B : Python 32 (output log inside Sublime)
  • CTRL+SHIFT+B : Python 64 (output log inside Sublime)
  • ALT+SHIFT+B : Python 32 (in a new terminal window)
  • CTRL+SHIFT+ALT+B : Python 64 (in a new terminal window)
  • CTRL+ALT+B : kill the current Python script

Default (Windows).sublime-keymap

[
{ "keys": ["ctrl+alt+b"], "command": "exec", "args": {"kill": true} },
{ "keys": ["alt+shift+b"], "command": "python_run" },
{ "keys": ["ctrl+shift+alt+b"], "command": "python64_run" }
]

python_run.py

import sublime
import sublime_plugin
import subprocess
class PythonRunCommand(sublime_plugin.WindowCommand):
    def run(self):
        command = 'cmd /k "C:\Python27\python.exe" %s' % sublime.active_window().active_view().file_name()
        subprocess.Popen(command)

python64_run.py

Idem with C:\Python27-64\python.exe

Python.sublime-build

{
    "cmd": ["python", "-u", "$file"],
    "file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
    "selector": "source.python",
    "variants": [ { 
          "name": "Run", 
          "file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)", 
          "cmd": ["C:\\Python27-64\\python.exe", "-u", "$file"] 
    } ] 
}