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: kinsamanka@3750: andrej@1732: import os andrej@1732: import re andrej@1732: import operator andrej@1832: import hashlib edouard@3893: import subprocess edouard@3893: import shlex 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: edouard@3982: def compute_file_md5(filetocheck): edouard@3982: hasher = hashlib.md5() edouard@3982: with open(filetocheck, 'rb') as afile: edouard@3982: while True: edouard@3982: buf = afile.read(65536) edouard@3982: if len(buf) > 0: edouard@3982: hasher.update(buf) edouard@3982: else: edouard@3982: break edouard@3982: return hasher.hexdigest() edouard@3982: edouard@3982: 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: """ edouard@3571: cflags = [self.CTRInstance.GetTarget().getcontent().getCFLAGS()] kinsamanka@3750: if "CFLAGS" in os.environ: edouard@3571: cflags.append(os.environ["CFLAGS"]) kinsamanka@3750: if "SYSROOT" in os.environ: edouard@3571: cflags.append("--sysroot="+os.environ["SYSROOT"]) edouard@3571: return cflags andrej@1731: andrej@1731: def getBuilderLDFLAGS(self): andrej@1731: """ andrej@1731: Returns list of builder specific LDFLAGS andrej@1731: """ edouard@3571: ldflags = self.CTRInstance.LDFLAGS + \ andrej@1767: [self.CTRInstance.GetTarget().getcontent().getLDFLAGS()] kinsamanka@3750: if "LDLAGS" in os.environ: kinsamanka@3750: ldflags.append(os.environ["LDLAGS"]) kinsamanka@3750: if "SYSROOT" in os.environ: edouard@3571: ldflags.append("--sysroot="+os.environ["SYSROOT"]) edouard@3571: return ldflags 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. kinsamanka@3750: return reduce(operator.concat, list(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 edouard@3982: src = os.path.join(self.buildpath, bn) edouard@4012: if not os.path.exists(src): edouard@4012: return False andrej@1731: # compute new hash edouard@3982: newhash = compute_file_md5(src) 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. kinsamanka@3750: return reduce(operator.and_, list(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: edouard@3893: Builder_CFLAGS_str = ' '.join(self.getBuilderCFLAGS()) edouard@3893: Builder_LDFLAGS_str = ' '.join(self.getBuilderLDFLAGS()) edouard@3893: edouard@3893: Builder_CFLAGS = shlex.split(Builder_CFLAGS_str) edouard@3893: Builder_LDFLAGS = shlex.split(Builder_LDFLAGS_str) edouard@3893: edouard@3893: pattern = "{SYSROOT}" edouard@3893: if pattern in Builder_CFLAGS_str or pattern in Builder_LDFLAGS_str: edouard@3893: try: edouard@3893: sysrootb = subprocess.check_output(["arm-unknown-linux-gnueabihf-gcc","-print-sysroot"]) edouard@3893: except subprocess.CalledProcessError: edouard@3893: self.CTRInstance.logger.write("GCC failed with -print-sysroot\n") edouard@3893: return False edouard@3893: except FileNotFoundError: edouard@3893: self.CTRInstance.logger.write("GCC not found\n") edouard@3893: return False edouard@3893: edouard@3893: sysroot = sysrootb.decode().strip() edouard@3893: edouard@3893: replace_sysroot = lambda l:list(map(lambda s:s.replace(pattern, sysroot), l)) edouard@3893: Builder_CFLAGS = replace_sysroot(Builder_CFLAGS) edouard@3893: Builder_LDFLAGS = replace_sysroot(Builder_LDFLAGS) 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, edouard@3893: [self.compiler, edouard@3893: "-c", CFile, edouard@3893: "-o", objectfilename, edouard@3893: "-O2"] edouard@3893: + Builder_CFLAGS edouard@3893: + shlex.split(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@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, edouard@3893: [self.linker] + objs edouard@3893: + ["-o", self.bin_path] edouard@3893: + Builder_LDFLAGS 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@3982: self.md5key = compute_file_md5(self.bin_path) 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