Search code examples
sublimetext3sublime-build

Set project dependent build system variables


I have written a build system that calls a bash or batch build script. For now I copy and paste the build script in any new project and change the project related properties.

To avoid having to modify the script every time (or use only one that would be set globally for example) I would like to be able to set some variables in the sublime project settings and get them in the build system, then send them to the script as arguments.

Is this doable without having to define a per-project build system ?

In case it is relevant here are some simplified build script

#!/bin/bash
exe=some_defined_exe

mkdir -p build
cd build
cmake ..
make -j4 
make install
cd ../bin
$exe
cd ..

And build system

{
    "cmd": ["build.sh"],
    "working_dir": "${project_path:${folder}}",
    "shell": false,
}

And I would like them to be something like

#!/bin/bash
exe=$1

mkdir -p build
cd build
cmake ..
make -j4 
make install
cd ../bin
$exe
cd ..

and

{
    "cmd": ["build.sh", "${some_project_defined_variable}"],
    "working_dir": "${project_path:${folder}}",
    "shell": false,
}

Solution

  • Since you're already using a custom build system, you can pull something like this off using a bit of plugin code and some modifications to your custom build system.

    Custom build systems allow a property named target which specifies the command to execute to run the build. When you don't provide this (most build systems don't) the default is the exec command. You could create your own custom command that can perform the expansions for you.

    As a simple example, see the following python source code, which you can save as something like custom_build.py in your User package. This implements a new command named my_custom_build. When this command is executed it will transform it's arguments by expanding out variables and then executing the default exec command with them.

    import sublime, sublime_plugin
    
    # List of variable names we want to support
    custom_var_list = ["proj_var_1"]
    
    class MyCustomBuildCommand(sublime_plugin.WindowCommand):
        def createExecDict(self, sourceDict):
            global custom_var_list
    
            # Get the project specific settings
            project_data = self.window.project_data ()
            project_settings = (project_data or {}).get ("settings", {})
    
            # Get the view specific settings
            view_settings = self.window.active_view ().settings ()
    
            # Variables to expand; start with defaults, then add ours.
            variables = self.window.extract_variables ()
            for custom_var in custom_var_list:
                variables[custom_var] = view_settings.get (custom_var,
                    project_settings.get (custom_var, ""))
    
            # Create arguments to return by expanding variables in the
            # arguments given.
            args = sublime.expand_variables (sourceDict, variables)
    
            # Rename the command paramter to what exec expects.
            args["cmd"] = args.pop ("command", [])
    
            return args
    
        def run(self, **kwargs):
            self.window.run_command ("exec", self.createExecDict (kwargs))
    

    Specifically, it looks for variables first in the settings specified in the current view, and if they aren't found there, in the project specific settings. Any variables still not found default to the empty string.

    You would need to specify a custom build system such as:

    {
        "target": "my_custom_build",
        "command": ["build.sh", "${proj_var_1}"],
    
        "working_dir": "${project_path:${folder}}",
        "shell": false
    }
    

    Note that now there is a target field that specifies that the custom command should be executed in place of exec. Note also that instead of specifying the command line as cmd, we specify it as command here instead.

    This is because Sublime seems to pre-expand the values of known keys in build systems before it runs your custom command. As a result of that, it will try to expand the variables in the cmd key, causing ${proj_var_1} to be expanded to an empty string before our command even sees it, which strips it away on us.

    To get around this, we change the name of the key to something Sublime will leave alone and then swap it back in the code.

    As written the code above will try to expand any variables it sees anywhere in the build system, but it only takes special care to protect the cmd key in this way. You need to do something similar if you wanted to use your custom variables in the working_dir key, for example.