Files
platformio-core/platformio/builder/tools/piomisc.py
2017-03-31 18:55:19 +03:00

283 lines
8.6 KiB
Python

# Copyright 2014-present PlatformIO <contact@platformio.org>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import absolute_import
import atexit
import re
import sys
from os import environ, remove, walk
from os.path import basename, isdir, isfile, join, relpath
from tempfile import mkstemp
from SCons.Action import Action
from SCons.Script import ARGUMENTS
from platformio import util
class InoToCPPConverter(object):
PROTOTYPE_RE = re.compile(r"""^(
(?:template\<.*\>\s*)? # template
([a-z_\d]+\*?\s+){1,2} # return type
([a-z_\d]+\s*) # name of prototype
\([a-z_,\.\*\&\[\]\s\d]*\) # arguments
)\s*\{ # must end with {
""", re.X | re.M | re.I)
DETECTMAIN_RE = re.compile(r"void\s+(setup|loop)\s*\(", re.M | re.I)
PROTOPTRS_TPLRE = r"\([^&\(]*&(%s)[^\)]*\)"
def __init__(self, env):
self.env = env
self._main_ino = None
def is_main_node(self, contents):
return self.DETECTMAIN_RE.search(contents)
def convert(self, nodes):
contents = self.merge(nodes)
if not contents:
return
return self.process(contents)
def merge(self, nodes):
assert nodes
lines = []
for node in nodes:
contents = node.get_text_contents()
_lines = [
'# 1 "%s"' % node.get_path().replace("\\", "/"), contents
]
if self.is_main_node(contents):
lines = _lines + lines
self._main_ino = node.get_path()
else:
lines.extend(_lines)
if not self._main_ino:
self._main_ino = nodes[0].get_path()
return "\n".join(["#include <Arduino.h>"] + lines) if lines else None
def process(self, contents):
out_file = self._main_ino + ".cpp"
assert self._gcc_preprocess(contents, out_file)
with open(out_file) as fp:
contents = fp.read()
contents = self._join_multiline_strings(contents)
with open(out_file, "w") as fp:
fp.write(self.append_prototypes(contents))
return out_file
def _gcc_preprocess(self, contents, out_file):
tmp_path = mkstemp()[1]
with open(tmp_path, "w") as fp:
fp.write(contents)
self.env.Execute(
self.env.VerboseAction(
'$CXX -o "{0}" -x c++ -fpreprocessed -dD -E "{1}"'.format(
out_file, tmp_path), "Converting " + basename(
out_file[:-4])))
atexit.register(_delete_file, tmp_path)
return isfile(out_file)
def _join_multiline_strings(self, contents):
if "\\\n" not in contents:
return contents
newlines = []
linenum = 0
stropen = False
for line in contents.split("\n"):
_linenum = self._parse_preproc_line_num(line)
if _linenum is not None:
linenum = _linenum
else:
linenum += 1
if line.endswith("\\"):
if line.startswith('"'):
stropen = True
newlines.append(line[:-1])
continue
elif stropen:
newlines[len(newlines) - 1] += line[:-1]
continue
elif stropen and line.endswith('";'):
newlines[len(newlines) - 1] += line
stropen = False
newlines.append('#line %d "%s"' %
(linenum, self._main_ino.replace("\\", "/")))
continue
newlines.append(line)
return "\n".join(newlines)
@staticmethod
def _parse_preproc_line_num(line):
if not line.startswith("#"):
return None
tokens = line.split(" ", 3)
if len(tokens) > 2 and tokens[1].isdigit():
return int(tokens[1])
return None
def _parse_prototypes(self, contents):
prototypes = []
reserved_keywords = set(["if", "else", "while"])
for match in self.PROTOTYPE_RE.finditer(contents):
if (set([match.group(2).strip(), match.group(3).strip()]) &
reserved_keywords):
continue
prototypes.append(match)
return prototypes
def _get_total_lines(self, contents):
total = 0
if contents.endswith("\n"):
contents = contents[:-1]
for line in contents.split("\n")[::-1]:
linenum = self._parse_preproc_line_num(line)
if linenum is not None:
return total + linenum
total += 1
return total
def append_prototypes(self, contents):
prototypes = self._parse_prototypes(contents)
if not prototypes:
return contents
prototype_names = set([m.group(3).strip() for m in prototypes])
split_pos = prototypes[0].start()
match_ptrs = re.search(self.PROTOPTRS_TPLRE %
("|".join(prototype_names)),
contents[:split_pos], re.M)
if match_ptrs:
split_pos = contents.rfind("\n", 0, match_ptrs.start()) + 1
result = []
result.append(contents[:split_pos].strip())
result.append("%s;" % ";\n".join([m.group(1) for m in prototypes]))
result.append('#line %d "%s"' %
(self._get_total_lines(contents[:split_pos]),
self._main_ino.replace("\\", "/")))
result.append(contents[split_pos:].strip())
return "\n".join(result)
def ConvertInoToCpp(env):
src_dir = util.glob_escape(env.subst("$PROJECTSRC_DIR"))
ino_nodes = (
env.Glob(join(src_dir, "*.ino")) + env.Glob(join(src_dir, "*.pde")))
if not ino_nodes:
return
c = InoToCPPConverter(env)
out_file = c.convert(ino_nodes)
atexit.register(_delete_file, out_file)
def _delete_file(path):
try:
if isfile(path):
remove(path)
except: # pylint: disable=bare-except
pass
def GetCompilerType(env):
try:
sysenv = environ.copy()
sysenv['PATH'] = str(env['ENV']['PATH'])
result = util.exec_command([env.subst("$CC"), "-v"], env=sysenv)
except OSError:
return None
if result['returncode'] != 0:
return None
output = "".join([result['out'], result['err']]).lower()
if "clang" in output and "LLVM" in output:
return "clang"
elif "gcc" in output:
return "gcc"
return None
def GetActualLDScript(env):
def _lookup_in_ldpath(script):
for d in env.get("LIBPATH", []):
path = join(env.subst(d), script)
if isfile(path):
return path
return None
script = None
for f in env.get("LINKFLAGS", []):
if f.startswith("-Wl,-T"):
script = env.subst(f[6:].replace('"', "").strip())
if isfile(script):
return script
path = _lookup_in_ldpath(script)
if path:
return path
if script:
sys.stderr.write(
"Error: Could not find '%s' LD script in LDPATH '%s'\n" %
(script, env.subst("$LIBPATH")))
env.Exit(1)
if not script and "LDSCRIPT_PATH" in env:
path = _lookup_in_ldpath(env['LDSCRIPT_PATH'])
if path:
return path
sys.stderr.write("Error: Could not find LD script\n")
env.Exit(1)
def VerboseAction(_, act, actstr):
if int(ARGUMENTS.get("PIOVERBOSE", 0)):
return act
else:
return Action(act, actstr)
def PioClean(env, clean_dir):
if not isdir(clean_dir):
print "Build environment is clean"
env.Exit(0)
for root, _, files in walk(clean_dir):
for file_ in files:
remove(join(root, file_))
print "Removed %s" % relpath(join(root, file_))
print "Done cleaning"
util.rmtree_(clean_dir)
env.Exit(0)
def exists(_):
return True
def generate(env):
env.AddMethod(ConvertInoToCpp)
env.AddMethod(GetCompilerType)
env.AddMethod(GetActualLDScript)
env.AddMethod(VerboseAction)
env.AddMethod(PioClean)
return env