Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/Klipper3d/klipper.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/buildcommands.py')
-rw-r--r--scripts/buildcommands.py313
1 files changed, 313 insertions, 0 deletions
diff --git a/scripts/buildcommands.py b/scripts/buildcommands.py
new file mode 100644
index 000000000..8b9cc9d8b
--- /dev/null
+++ b/scripts/buildcommands.py
@@ -0,0 +1,313 @@
+#!/usr/bin/env python
+# Script to handle build time requests embedded in C code.
+#
+# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
+#
+# This file may be distributed under the terms of the GNU GPLv3 license.
+
+import sys, os, subprocess, optparse, logging, shlex, socket, time
+import json, zlib
+sys.path.append('./klippy')
+import msgproto
+
+FILEHEADER = """
+/* DO NOT EDIT! This is an autogenerated file. See scripts/buildcommands.py. */
+
+#include "board/pgm.h"
+#include "command.h"
+"""
+
+def error(msg):
+ sys.stderr.write(msg + "\n")
+ sys.exit(-1)
+
+# Parser for constants in simple C header files.
+def scan_config(file):
+ f = open(file, 'r')
+ opts = {}
+ for l in f.readlines():
+ parts = l.split()
+ if len(parts) != 3:
+ continue
+ if parts[0] != '#define':
+ continue
+ value = parts[2]
+ if value.isdigit() or (value.startswith('0x') and value[2:].isdigit()):
+ value = int(value, 0)
+ elif value.startswith('"'):
+ value = value[1:-1]
+ opts[parts[1]] = value
+ f.close()
+ return opts
+
+
+######################################################################
+# Command and output parser generation
+######################################################################
+
+def build_parser(parser, iscmd, all_param_types):
+ if parser.name == "#empty":
+ return "\n // Empty message\n .max_size=0,"
+ if parser.name == "#output":
+ comment = "Output: " + parser.msgformat
+ else:
+ comment = parser.msgformat
+ params = '0'
+ types = tuple([t.__class__.__name__ for t in parser.param_types])
+ if types:
+ paramid = all_param_types.get(types)
+ if paramid is None:
+ paramid = len(all_param_types)
+ all_param_types[types] = paramid
+ params = 'command_parameters%d' % (paramid,)
+ out = """
+ // %s
+ .msg_id=%d,
+ .num_params=%d,
+ .param_types = %s,
+""" % (comment, parser.msgid, len(types), params)
+ if iscmd:
+ num_args = (len(types) + types.count('PT_progmem_buffer')
+ + types.count('PT_buffer'))
+ out += " .num_args=%d," % (num_args,)
+ else:
+ max_size = min(msgproto.MESSAGE_MAX
+ , 1 + sum([t.max_length for t in parser.param_types]))
+ out += " .max_size=%d," % (max_size,)
+ return out
+
+def build_parsers(parsers, msg_to_id, all_param_types):
+ pcode = []
+ for msgname, msg in parsers:
+ msgid = msg_to_id[msg]
+ if msgname is None:
+ parser = msgproto.OutputFormat(msgid, msg)
+ else:
+ parser = msgproto.MessageFormat(msgid, msg)
+ parsercode = build_parser(parser, 0, all_param_types)
+ pcode.append("{%s\n}, " % (parsercode,))
+ fmt = """
+const struct command_encoder command_encoders[] PROGMEM = {
+%s
+};
+"""
+ return fmt % ("".join(pcode).strip(),)
+
+def build_param_types(all_param_types):
+ sorted_param_types = sorted([(i, a) for a, i in all_param_types.items()])
+ params = ['']
+ for paramid, argtypes in sorted_param_types:
+ params.append(
+ 'static const uint8_t command_parameters%d[] PROGMEM = {\n'
+ ' %s };' % (
+ paramid, ', '.join(argtypes),))
+ params.append('')
+ return "\n".join(params)
+
+def build_commands(cmd_by_id, messages_by_name, all_param_types):
+ max_cmd_msgid = max(cmd_by_id.keys())
+ index = []
+ parsers = []
+ externs = {}
+ for msgid in range(max_cmd_msgid+1):
+ if msgid not in cmd_by_id:
+ index.append(" 0,")
+ continue
+ funcname, flags, msgname = cmd_by_id[msgid]
+ msg = messages_by_name[msgname]
+ externs[funcname] = 1
+ parsername = 'parser_%s' % (funcname,)
+ index.append(" &%s," % (parsername,))
+ parser = msgproto.MessageFormat(msgid, msg)
+ parsercode = build_parser(parser, 1, all_param_types)
+ parsers.append("const struct command_parser %s PROGMEM = {"
+ " %s\n .flags=%s,\n .func=%s\n};" % (
+ parsername, parsercode, flags, funcname))
+ index = "\n".join(index)
+ externs = "\n".join(["extern void "+funcname+"(uint32_t*);"
+ for funcname in sorted(externs)])
+ fmt = """
+%s
+
+%s
+
+const struct command_parser * const command_index[] PROGMEM = {
+%s
+};
+
+const uint8_t command_index_size PROGMEM = ARRAY_SIZE(command_index);
+"""
+ return fmt % (externs, '\n'.join(parsers), index)
+
+
+######################################################################
+# Identify data dictionary generation
+######################################################################
+
+def build_identify(cmd_by_id, msg_to_id, responses, static_strings
+ , config, version):
+ #commands, messages, static_strings
+ messages = dict((msgid, msg) for msg, msgid in msg_to_id.items())
+ data = {}
+ data['messages'] = messages
+ data['commands'] = sorted(cmd_by_id.keys())
+ data['responses'] = sorted(responses)
+ data['static_strings'] = static_strings
+ configlist = ['MCU', 'CLOCK_FREQ']
+ data['config'] = dict((i, config['CONFIG_'+i]) for i in configlist
+ if 'CONFIG_'+i in config)
+ data['version'] = version
+
+ # Format compressed info into C code
+ data = json.dumps(data)
+ zdata = zlib.compress(data, 9)
+ out = []
+ for i in range(len(zdata)):
+ if i % 8 == 0:
+ out.append('\n ')
+ out.append(" 0x%02x," % (ord(zdata[i]),))
+ fmt = """
+const uint8_t command_identify_data[] PROGMEM = {%s
+};
+
+// Identify size = %d (%d uncompressed)
+const uint32_t command_identify_size PROGMEM = ARRAY_SIZE(command_identify_data);
+"""
+ return fmt % (''.join(out), len(zdata), len(data))
+
+
+######################################################################
+# Version generation
+######################################################################
+
+# Run program and return the specified output
+def check_output(prog):
+ logging.debug("Running %s" % (repr(prog),))
+ try:
+ process = subprocess.Popen(shlex.split(prog), stdout=subprocess.PIPE)
+ output = process.communicate()[0]
+ retcode = process.poll()
+ except OSError:
+ logging.debug("Exception on run: %s" % (traceback.format_exc(),))
+ return ""
+ logging.debug("Got (code=%s): %s" % (retcode, repr(output)))
+ if retcode:
+ return ""
+ try:
+ return output.decode()
+ except UnicodeError:
+ logging.debug("Exception on decode: %s" % (traceback.format_exc(),))
+ return ""
+
+# Obtain version info from "git" program
+def git_version():
+ if not os.path.exists('.git'):
+ logging.debug("No '.git' file/directory found")
+ return ""
+ ver = check_output("git describe --tags --long --dirty").strip()
+ logging.debug("Got git version: %s" % (repr(ver),))
+ return ver
+
+def build_version(extra):
+ version = git_version()
+ if not version:
+ version = "?"
+ btime = time.strftime("%Y%m%d_%H%M%S")
+ hostname = socket.gethostname()
+ version = "%s-%s-%s%s" % (version, btime, hostname, extra)
+ return version
+
+
+######################################################################
+# Main code
+######################################################################
+
+def main():
+ usage = "%prog [options] <cmd section file> <autoconf.h> <output.c>"
+ opts = optparse.OptionParser(usage)
+ opts.add_option("-e", "--extra", dest="extra", default="",
+ help="extra version string to append to version")
+ opts.add_option("-v", action="store_true", dest="verbose",
+ help="enable debug messages")
+
+ options, args = opts.parse_args()
+ if len(args) != 3:
+ opts.error("Incorrect arguments")
+ incmdfile, inheader, outcfile = args
+ if options.verbose:
+ logging.basicConfig(level=logging.DEBUG)
+
+ # Setup
+ commands = {}
+ messages_by_name = dict((m.split()[0], m)
+ for m in msgproto.DefaultMessages.values())
+ parsers = []
+ static_strings = []
+ # Parse request file
+ f = open(incmdfile, 'rb')
+ data = f.read()
+ f.close()
+ for req in data.split('\0'):
+ req = req.lstrip()
+ parts = req.split()
+ if not parts:
+ continue
+ cmd = parts[0]
+ msg = req[len(cmd)+1:]
+ if cmd == '_DECL_COMMAND':
+ funcname, flags, msgname = parts[1:4]
+ if msgname in commands:
+ error("Multiple definitions for command '%s'" % msgname)
+ commands[msgname] = (funcname, flags, msgname)
+ msg = req.split(None, 3)[3]
+ m = messages_by_name.get(msgname)
+ if m is not None and m != msg:
+ error("Conflicting definition for command '%s'" % msgname)
+ messages_by_name[msgname] = msg
+ elif cmd == '_DECL_PARSER':
+ if len(parts) == 1:
+ msgname = msg = "#empty"
+ else:
+ msgname = parts[1]
+ m = messages_by_name.get(msgname)
+ if m is not None and m != msg:
+ error("Conflicting definition for message '%s'" % msgname)
+ messages_by_name[msgname] = msg
+ parsers.append((msgname, msg))
+ elif cmd == '_DECL_OUTPUT':
+ parsers.append((None, msg))
+ elif cmd == '_DECL_STATIC_STR':
+ static_strings.append(req[17:])
+ else:
+ error("Unknown build time command '%s'" % cmd)
+ # Create unique ids for each message type
+ msgid = max(msgproto.DefaultMessages.keys())
+ msg_to_id = dict((m, i) for i, m in msgproto.DefaultMessages.items())
+ for msgname in commands.keys() + [m for n, m in parsers]:
+ msg = messages_by_name.get(msgname, msgname)
+ if msg not in msg_to_id:
+ msgid += 1
+ msg_to_id[msg] = msgid
+ # Create message definitions
+ all_param_types = {}
+ parsercode = build_parsers(parsers, msg_to_id, all_param_types)
+ # Create command definitions
+ cmd_by_id = dict((msg_to_id[messages_by_name.get(msgname, msgname)], cmd)
+ for msgname, cmd in commands.items())
+ cmdcode = build_commands(cmd_by_id, messages_by_name, all_param_types)
+ paramcode = build_param_types(all_param_types)
+ # Create identify information
+ config = scan_config(inheader)
+ version = build_version(options.extra)
+ sys.stdout.write("Version: %s\n" % (version,))
+ responses = [msg_to_id[msg] for msgname, msg in messages_by_name.items()
+ if msgname not in commands]
+ icode = build_identify(cmd_by_id, msg_to_id, responses, static_strings
+ , config, version)
+ # Write output
+ f = open(outcfile, 'wb')
+ f.write(FILEHEADER + paramcode + parsercode + cmdcode + icode)
+ f.close()
+
+if __name__ == '__main__':
+ main()