Sublime Forum

A Custom Sublime Build Around Keil uVision4 Compiler/IDE

#1

Hi all,
I’m trying to create a custom .sublime-build in order to make calls to Keil uVision IDE to compile, debug, and flash code.

The problem with Keil compiler is that it does not write to STDOUT. The only way to get the output is to use the -o switch and direct it to a file.

Is there a way to get the content of that output file to the output panel?

I’m using the following as a guide;
Build Systems
Along with the following Keil Command Line options;
UV4 Command Line Options

So far I have the following;
keil.sublime-build

{
    "target": "keil_build",
    "selector": "source.c, source.cpp, source.c++",
    "variants": [
        {
            "name": "build",
            "build": true
        },
        {
            "name": "Build Clean",
            "clean_build": true

        {        },
        {
            "name": "Clean All",
            "clean_all": true
        },
            "name": "Debug",
            "debug": true
        }
    ]
}

keil_build.py

import sublime
import sublime_plugin

import subprocess
import threading
import os


class KeilBuildCommand(sublime_plugin.WindowCommand):

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

    def is_enabled(self, build=False, clean_build=False, clean_all=False, debug=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, build=False, clean_build=False, clean_all=False, debug=False, kill=False):
        if kill:
            if self.proc:
                self.killed = True
                self.proc.terminate()
            return

        vars = self.window.extract_variables()
        print (vars)
        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 = ['C:\\Keil\\UV4\\UV4.exe']
        if build:
            args.append('-b')
        elif clean_build:
            args.append('-r')
        elif clean_all:
            args.append('-c')
        elif debug:
            args.append('-d')

        args.append(vars['project_path'] + "\\TxProj\\keilproj.uvproj")
        self.output_file = vars['project_path'] + "\\TxProj\\buildOutput.txt"
        args.append("-o")
        args.append("buildOutput.txt")

        print(args)

        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:
                print("Reading output file")
                print(self.output_file)
                fd = os.open(self.output_file, os.O_RDWR)
                data = os.read(fd, chunk_size)
                print(data)
                # 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})

you will notice that in the def read_handle I have changed it to read the output file. This doesn’t work :frowning:

Cheers,
Simon

0 Likes

#2

Are you running on some sort of Unix? Then just redirect stderr to stdout on the command line, if that’s where it’s writing the output to.

0 Likes

#3

Would you be able to give me an example on how to do it pls?

Running on Windows 10. The issue is that this Keil Compiler does not write stdout. It write to an output file when the -o is included in the commandline

0 Likes

#4

Where does it write to without the “-o”?

0 Likes

#5

It launches the IDE and writes to the output field!

0 Likes

#6

A shell_command like "uv4 -o something.txt ; type something.txt" might be a quick expedient using a standard build. That would theoretically output the data to the file, then display it to stdout for the standard build system to see.

0 Likes

#7

Why are you doing a custom build script? ( ie keil_build.py)

0 Likes

#8

because using a shell_command as @OdatNurd suggested didn’t work.
Initially I had something like ;

"shell_cmd": "C:\\Keil\\UV4\\UV4.exe -b $project_path\\TxProj\\keilproj.uvproj -o buildOutput.txt"

Which then successfully compiles and write to file.
Adding the read of buildOutput.txt such as;

    "shell_cmd": "C:\\Keil\\UV4\\UV4.exe -b $project_path\\TxProj\\keilproj.uvproj -o buildOutput.txt ; type $project_path\\TxProj\\buildOutput.txt"

That broke the build since the “type” command was being executed before the uv4 has finished compiling.

0 Likes

#9

Ahh, I forgot that Windows doesn’t behave the way Unix systems do; sorry about that.

It might work better if you use an & instead of ; to split the commands.

1 Like

#10

once again you come to my rescue!
This worked really well for me :slight_smile:
Now I have to figure out the bloody file_regex to extract the errors and warnings.

Thanks heaps.

0 Likes