andrej@1731: #!/usr/bin/env python andrej@1731: # -*- coding: utf-8 -*- andrej@1731: andrej@1731: # This file is part of Beremiz, a Integrated Development Environment for andrej@1731: # programming IEC 61131-3 automates supporting plcopen standard and CanFestival. andrej@1731: # andrej@1731: # Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD andrej@1731: # Copyright (C) 2017: Paul Beltyukov andrej@1731: # andrej@1731: # See COPYING file for copyrights details. andrej@1731: # andrej@1731: # This program is free software; you can redistribute it and/or andrej@1731: # modify it under the terms of the GNU General Public License andrej@1731: # as published by the Free Software Foundation; either version 2 andrej@1731: # of the License, or (at your option) any later version. andrej@1731: # andrej@1731: # This program is distributed in the hope that it will be useful, andrej@1731: # but WITHOUT ANY WARRANTY; without even the implied warranty of andrej@1731: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the andrej@1731: # GNU General Public License for more details. andrej@1731: # andrej@1731: # You should have received a copy of the GNU General Public License andrej@1731: # along with this program; if not, write to the Free Software andrej@1731: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. andrej@1731: andrej@1881: andrej@1881: from __future__ import absolute_import andrej@1732: import os andrej@1732: import re andrej@1732: import operator andrej@1832: import hashlib andrej@2456: from functools import reduce andrej@1731: from util.ProcessLogger import ProcessLogger andrej@1832: andrej@1731: andrej@2439: includes_re = re.compile(r'\s*#include\s*["<]([^">]*)[">].*') andrej@1731: andrej@1736: andrej@1831: class toolchain_gcc(object): andrej@1731: """ andrej@1731: This abstract class contains GCC specific code. andrej@1731: It cannot be used as this and should be inherited in a target specific andrej@1731: class such as target_linux or target_win32 andrej@1731: """ andrej@1731: def __init__(self, CTRInstance): andrej@1731: self.CTRInstance = CTRInstance andrej@1731: self.buildpath = None andrej@1731: self.SetBuildPath(self.CTRInstance._getBuildPath()) andrej@1735: andrej@1731: def getBuilderCFLAGS(self): andrej@1731: """ andrej@1731: Returns list of builder specific CFLAGS andrej@1731: """ andrej@1731: return [self.CTRInstance.GetTarget().getcontent().getCFLAGS()] andrej@1731: andrej@1731: def getBuilderLDFLAGS(self): andrej@1731: """ andrej@1731: Returns list of builder specific LDFLAGS andrej@1731: """ andrej@1731: return self.CTRInstance.LDFLAGS + \ andrej@1767: [self.CTRInstance.GetTarget().getcontent().getLDFLAGS()] andrej@1731: andrej@1731: def getCompiler(self): andrej@1731: """ andrej@1731: Returns compiler andrej@1731: """ andrej@1731: return self.CTRInstance.GetTarget().getcontent().getCompiler() andrej@1735: andrej@1731: def getLinker(self): andrej@1731: """ andrej@1731: Returns linker andrej@1731: """ andrej@1731: return self.CTRInstance.GetTarget().getcontent().getLinker() andrej@1735: Edouard@2463: def GetBinaryPath(self): Edouard@2463: return self.bin_path andrej@1735: andrej@1731: def _GetMD5FileName(self): andrej@1731: return os.path.join(self.buildpath, "lastbuildPLC.md5") andrej@1731: Edouard@2463: def ResetBinaryMD5(self): andrej@1731: self.md5key = None andrej@1731: try: andrej@1731: os.remove(self._GetMD5FileName()) andrej@1846: except Exception: andrej@1731: pass andrej@1735: Edouard@2463: def GetBinaryMD5(self): andrej@1731: if self.md5key is not None: andrej@1731: return self.md5key andrej@1731: else: andrej@1731: try: andrej@1731: return open(self._GetMD5FileName(), "r").read() andrej@1846: except Exception: andrej@1731: return None andrej@1735: andrej@1731: def SetBuildPath(self, buildpath): andrej@1731: if self.buildpath != buildpath: andrej@1731: self.buildpath = buildpath Edouard@2463: self.bin = self.CTRInstance.GetProjectName() + self.extension Edouard@2463: self.bin_path = os.path.join(self.buildpath, self.bin) andrej@1731: self.md5key = None andrej@1731: self.srcmd5 = {} andrej@1735: andrej@1731: def append_cfile_deps(self, src, deps): andrej@1731: for l in src.splitlines(): andrej@1731: res = includes_re.match(l) andrej@1731: if res is not None: andrej@1731: depfn = res.groups()[0] andrej@1731: if os.path.exists(os.path.join(self.buildpath, depfn)): andrej@1731: deps.append(depfn) andrej@1735: andrej@1731: def concat_deps(self, bn): andrej@1731: # read source andrej@1740: src = open(os.path.join(self.buildpath, bn), "r").read() andrej@1731: # update direct dependencies andrej@1731: deps = [] andrej@1731: self.append_cfile_deps(src, deps) andrej@1731: # recurse through deps andrej@1731: # TODO detect cicular deps. andrej@1731: return reduce(operator.concat, map(self.concat_deps, deps), src) andrej@1735: andrej@1731: def check_and_update_hash_and_deps(self, bn): andrej@1731: # Get latest computed hash and deps andrej@1740: oldhash, deps = self.srcmd5.get(bn, (None, [])) andrej@1731: # read source andrej@1731: src = open(os.path.join(self.buildpath, bn)).read() andrej@1731: # compute new hash andrej@1731: newhash = hashlib.md5(src).hexdigest() andrej@1731: # compare andrej@1731: match = (oldhash == newhash) andrej@1731: if not match: andrej@1731: # file have changed andrej@1731: # update direct dependencies andrej@1731: deps = [] andrej@1731: self.append_cfile_deps(src, deps) andrej@1731: # store that hashand deps andrej@1731: self.srcmd5[bn] = (newhash, deps) andrej@1731: # recurse through deps andrej@1731: # TODO detect cicular deps. andrej@1731: return reduce(operator.and_, map(self.check_and_update_hash_and_deps, deps), match) andrej@1735: andrej@1731: def calc_source_md5(self): andrej@1731: wholesrcdata = "" andrej@1847: for _Location, CFilesAndCFLAGS, _DoCalls in self.CTRInstance.LocationCFilesAndCFLAGS: andrej@1731: # Get CFiles list to give it to makefile andrej@1847: for CFile, _CFLAGS in CFilesAndCFLAGS: andrej@1731: CFileName = os.path.basename(CFile) andrej@1731: wholesrcdata += self.concat_deps(CFileName) andrej@1731: return hashlib.md5(wholesrcdata).hexdigest() andrej@1735: andrej@1731: def build(self): andrej@1731: # Retrieve compiler and linker andrej@1731: self.compiler = self.getCompiler() andrej@1731: self.linker = self.getLinker() andrej@1731: andrej@1731: Builder_CFLAGS = ' '.join(self.getBuilderCFLAGS()) andrej@1731: andrej@1753: # ----------------- GENERATE OBJECT FILES ------------------------ andrej@1731: obns = [] andrej@1731: objs = [] Edouard@2463: relink = not os.path.exists(self.bin_path) andrej@1847: for Location, CFilesAndCFLAGS, _DoCalls in self.CTRInstance.LocationCFilesAndCFLAGS: andrej@1731: if CFilesAndCFLAGS: andrej@1739: if Location: andrej@1740: self.CTRInstance.logger.write(".".join(map(str, Location))+" :\n") andrej@1731: else: andrej@1731: self.CTRInstance.logger.write(_("PLC :\n")) andrej@1735: andrej@1731: for CFile, CFLAGS in CFilesAndCFLAGS: andrej@1731: if CFile.endswith(".c"): andrej@1731: bn = os.path.basename(CFile) andrej@1731: obn = os.path.splitext(bn)[0]+".o" andrej@1731: objectfilename = os.path.splitext(CFile)[0]+".o" andrej@1731: andrej@1731: match = self.check_and_update_hash_and_deps(bn) andrej@1735: andrej@1731: if match: andrej@1731: self.CTRInstance.logger.write(" [pass] "+bn+" -> "+obn+"\n") andrej@1731: else: andrej@1731: relink = True andrej@1731: andrej@1731: self.CTRInstance.logger.write(" [CC] "+bn+" -> "+obn+"\n") andrej@1735: andrej@1847: status, _result, _err_result = ProcessLogger( andrej@1777: self.CTRInstance.logger, andrej@2168: "\"%s\" -c \"%s\" -o \"%s\" -O2 %s %s" % andrej@1777: (self.compiler, CFile, objectfilename, Builder_CFLAGS, CFLAGS) andrej@1777: ).spin() andrej@1731: andrej@1739: if status: andrej@1731: self.srcmd5.pop(bn) andrej@1734: self.CTRInstance.logger.write_error(_("C compilation of %s failed.\n") % bn) andrej@1731: return False andrej@1731: obns.append(obn) andrej@1731: objs.append(objectfilename) andrej@1731: elif CFile.endswith(".o"): andrej@1731: obns.append(os.path.basename(CFile)) andrej@1731: objs.append(CFile) andrej@1731: andrej@1753: # ---------------- GENERATE OUTPUT FILE -------------------------- andrej@1731: # Link all the object files into one binary file andrej@1731: self.CTRInstance.logger.write(_("Linking :\n")) andrej@1731: if relink: andrej@1731: # Generate list .o files andrej@1731: listobjstring = '"' + '" "'.join(objs) + '"' andrej@1735: andrej@1731: ALLldflags = ' '.join(self.getBuilderLDFLAGS()) andrej@1735: Edouard@2463: self.CTRInstance.logger.write(" [CC] " + ' '.join(obns)+" -> " + self.bin + "\n") andrej@1735: andrej@1847: status, _result, _err_result = ProcessLogger( andrej@1777: self.CTRInstance.logger, andrej@1777: "\"%s\" %s -o \"%s\" %s" % andrej@1777: (self.linker, andrej@1777: listobjstring, Edouard@2463: self.bin_path, andrej@1777: ALLldflags) andrej@1777: ).spin() andrej@1735: andrej@1739: if status: andrej@1731: return False andrej@1735: andrej@1731: else: Edouard@2463: self.CTRInstance.logger.write(" [pass] " + ' '.join(obns)+" -> " + self.bin + "\n") andrej@1735: andrej@1731: # Calculate md5 key and get data for the new created PLC Edouard@2463: self.md5key = hashlib.md5(open(self.bin_path, "rb").read()).hexdigest() andrej@1731: andrej@1731: # Store new PLC filename based on md5 key andrej@1731: f = open(self._GetMD5FileName(), "w") andrej@1731: f.write(self.md5key) andrej@1731: f.close() andrej@1735: andrej@1731: return True