Source code for bouwer.plugins.CCompiler

#
# Copyright (C) 2012 Niek Linnenbank
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

import os
import os.path
import glob
import copy
import subprocess
from bouwer.plugin import *
from bouwer.builder import *
from bouwer.config import *
import bouwer.util

[docs]class Object(Plugin): """ Build an executable object for a program. """
[docs] def config_input(self): """ Configuration input items """ return [ 'CC', 'USE_LIBRARIES', 'LIBRARIES', 'CONFIG' ]
[docs] def config_output(self): """ Configuration output items """ return [ 'OBJECTS' ]
[docs] def execute_source(self, source, item = None, depends = []): """ Build an executable object given its `source` file """ CCompiler.Instance().c_object(source, item, depends)
[docs] def execute_config(self, item, sources, depends = []): """ Build executable objects if configurion item `item` is True """ if item.value(): if type(sources) is str: sources = [ sources ] for source in sources: self.execute_source(source, item, depends)
[docs]class Program(Plugin): """ Build an executable program """
[docs] def config_input(self): """ Configuration input items """ return [ 'CC', 'LIBRARIES', 'USE_LIBRARIES', 'CHECK', 'CONFIG', 'OBJECTS' ]
[docs] def execute_config(self, item, sources, libraries = [], depends = [], name = None): """ Build a program given a :class:`.Config` `item` and `sources` list. """ if not name: name = item.name.lower() # TODO: also support the program = keyword, like library = if item.value(): self.execute_target(TargetPath(name), sources, libraries, depends, item)
[docs] def execute_source(self, source): """ Build an executable object given its `source` file """ self.execute_target(TargetPath(source.absolute.replace('.c', '')), [source])
[docs] def execute_target(self, target, sources, libraries = [], depends = [], item = None): """ Build an program given its `target` name and `sources` list """ for src in sources: CCompiler.Instance().use_library(libraries, TargetPath(os.path.basename(src.absolute).replace('.c', '.o'))) # TODO: this goes wrong with BUILDROOT etc CCompiler.Instance().use_library(libraries, target) CCompiler.Instance().c_program(target, sources, item=item, depends=depends)
[docs]class Library(Plugin): """ Build a software library """
[docs] def config_input(self): """ Configuration input items """ return [ 'CC', 'CHECK', 'CONFIG', 'LIBRARY_OBJECTS' ]
[docs] def config_output(self): """ Configuration output items """ return [ 'LIBRARIES' ]
[docs] def execute_config(self, item, sources, depends = []): """ Build a library using a :class:`.Config` and list of `sources` """ if not item.value(): return target = item.get_key('library', item.name.lower()) CCompiler.Instance().c_library(TargetPath(target), sources, item, depends)
[docs] def execute_target(self, target, sources, depends = []): """ Build a library using a `target` name and list of `sources` """ CCompiler.Instance().c_library(target, sources, depends)
[docs]class LibraryObject(Plugin): """ Specify an additional object for a library """
[docs] def config_input(self): """ Configuration input items """ return [ 'CC', 'CONFIG' ]
[docs] def config_output(self): """ Configuration output items """ return [ 'LIBRARY_OBJECTS' ]
[docs] def execute_source(self, source, depends = []): """ Build an executable object given its `source` file """ CCompiler.Instance().c_object(source, depends = depends)
[docs] def execute_config(self, item, sources, depends = []): """ Build executable objects if configurion item `item` is True """ if item.value(): for source in sources: CCompiler.Instance().c_object(source, item, depends)
[docs]class UseLibrary(Plugin): """ Build and link a program against a library """
[docs] def config_input(self): """ Configuration input items """ return [ 'CC', 'LIBRARIES' ]#, 'OBJECTS' ]
[docs] def config_output(self): """ Configuration output items """ return [ 'USE_LIBRARIES' ]
[docs] def execute_config_params(self, item, libraries): if item.value(): self.execute_any(libraries)
[docs] def execute_any(self, libraries): """ Build against a :py:`list` of `libraries` The library target for linking will be discovered internally. """ if type(libraries) is str: libraries = [ libraries ] CCompiler.Instance().use_library(libraries)
[docs]class Include(Plugin): """ Extend the C/C++ include path """
[docs] def config_inputz___(self): """ Configuration input items """ return [ 'CC', 'USE_LIBRARIES', 'LIBRARIES', 'CONFIG' ]
[docs] def config_output(self): """ Configuration output items """ return [ 'CONFIG' ]
[docs] def execute_config_params(self, conf, includes): """ Append includes to the incpath for CC. """ if not conf.value(): return if type(includes) is str: includes = [ includes ] # Introduce an CC override if self.conf.active_dir not in self.conf.active_tree.subitems.get('CC', {}): clist = ConfigList('CC', None, self.conf.active_tree.name) clist._keywords['incpath'] = self.conf.get('CC')['incpath'] self.conf.put(clist, self.conf.active_tree.name, self.conf.active_dir) cc = self.conf.get('CC') # Append to the incpath of the override for inc in includes: if inc not in cc._keywords.get('incpath', '').split(':'): if 'incpath' in cc._keywords: cc._keywords['incpath'] += ':' + inc else: cc._keywords['incpath'] = inc
[docs]class CCompiler(bouwer.util.Singleton): def __init__(self): """ Constructor """ self.conf = Configuration.Instance() self.build = BuilderManager.Instance() self.c_object_list = [] self.libraries = {} self.use_libraries = {} self.objects_for_items = {} def _find_headers(self, source, incflags, cc): """ Find headers included by a C file using the C preprocessor. Return them as a list. """ # TODO: make this recursive # Retrieve cache and file stat cache = bouwer.util.Cache.Instance('c_headers') st = os.stat(source.absolute) headers = [] headers_str = [] # Do we have the file still in an up-to-date cache? if cache.timestamp() >= st.st_mtime and cache.get(source.absolute) is not None: for hdr in cache.get(source.absolute): sp = SourcePath('') sp.absolute = hdr headers.append(sp) return headers # Invoke the preprocessor to determine header dependencies try: cpp_command=cc['cpp'] + ' ' + incflags + ' ' + cc['cppflags'] + ' ' + source.absolute result = str(subprocess.check_output(cpp_command, stderr=subprocess.PIPE, shell=True)) header_list = result.replace(' \\\n', '').strip().split(' ') headers_str = header_list[2:] for header in headers_str: if header: sp = SourcePath('') sp.absolute = header headers.append(sp) except subprocess.CalledProcessError: pass cache.put(source.absolute, headers_str) return headers def _find_config_deps(self, item): dep_list = [ item ] for dep in item.get_key('depends', []): dep_list += self._find_config_deps(self.conf.get(dep)) return dep_list def _register_config_deps(self, outfile, item): """ Make a list of objects to output as dependency for the given item Used in e.g. if the item is set for c_library/c_program. Then the outfile is automatically added as dependency of the library/program. """ deps = self._find_config_deps(item) tree_name = self.conf.active_tree.name for dep in deps: slot_name = tree_name + '.' + dep.name if slot_name not in self.objects_for_items: self.objects_for_items[slot_name] = [] self.objects_for_items[slot_name].append(outfile) return deps def _lookup_config_deps(self, item): if item is not None: tree_name = self.conf.active_tree.name slot_name = tree_name + '.' + item.name return self.objects_for_items.get(slot_name, []) else: return [] def _get_libraries_for_target(self, target): """ Return a list of libraries for the given target/item """ treedict = self.use_libraries.get(self.conf.active_tree, {}) dirdict = treedict.get(self.conf.active_dir, {}) use_libs = dirdict.get(target.absolute, []) use_libs += dirdict.get(None, []) return use_libs
[docs] def c_object(self, source, item = None, depends = [], **extra_tags): """ Compile a C source file into an object file """ chain = self.conf.active_tree.get('CC') cc = self.conf.active_tree.get(chain.value()) splitfile = os.path.splitext(source.relative) incflags = '' # Translate source and target paths relative from project-root outfile = TargetPath(splitfile[0] + '.o') # Fill compiler command if splitfile[1] == '.c' or splitfile[1] == '.S': compiler = cc['cc'] + ' ' + str(outfile) + ' ' + cc['ccflags'] + ' ' + cc['cppflags'] elif splitfile[1] == '.cpp': compiler = cc['c++'] + ' ' + str(outfile) + ' ' + cc['c++flags'] + ' ' + cc['cppflags'] else: raise Exception('not a C source file: ' + source) # Link the config item and its parents to this target file. if item is not None: self._register_config_deps(outfile, item) elif not extra_tags.get('standalone', False): self.c_object_list.append(outfile) # Add C preprocessor paths incpath = cc.get_key('incpath', '').split(':') + chain.get_key('incpath', '').split(':') for path in incpath: if path: incflags += cc['incflag'] + path + ' ' # Add C preprocessor paths from libraries for libname in self._get_libraries_for_target(outfile): try: incflags += cc['incflag'] + self.libraries[self.conf.active_tree][libname][1] + ' ' except KeyError: pass # Determine dependencies to build output file. deps = self._find_headers(source, incflags, cc) + depends deps.append(source) # Set our pretty name if 'pretty_name' not in extra_tags: extra_tags['pretty_name'] = 'CC' # Register compile action self.build.action(outfile, deps, compiler + ' ' + incflags + str(source), **extra_tags ) return outfile
[docs] def c_program(self, target, sources, item = None, depends = [], **extra_tags): """ Build an program given its `target` name and `sources` list """ # Retrieve compiler chain chain = self.conf.get('CC') cc = self.conf.get(chain.value()) ldpath = '' incpath = '' objects = self._lookup_config_deps(item) + self.c_object_list extra_deps = copy.deepcopy(depends) # C or C++ program? if not sources or sources[0].absolute.endswith('.c'): link = cc['clink'] ldflags = cc['clinkflags'] elif sources[0].absolute.endswith('.cpp'): link = cc['c++link'] ldflags = cc['c++linkflags'] # Add linker paths for path in cc['ldpath'].split(':'): if path: ldpath += cc['ldflag'] + path + ' ' # Use libraries for libname in self._get_libraries_for_target(target): # Local library? try: lib = self.libraries[self.conf.active_tree][libname][0] ldpath += cc['ldflag'] + './' + os.path.dirname(lib.absolute) + ' ' extra_deps.append(lib) except KeyError: pass # Link with the library if libname[:3] == 'lib': libname = libname[3:] ldflags += ' -l' + libname # Traverse all source files given for source in sources: objects.append(self.c_object(source, **extra_tags)) # Set our pretty name if 'pretty_name' not in extra_tags: extra_tags['pretty_name'] = 'LINK' # Link the program self.build.action(target, objects + extra_deps, link + ' ' + str(target) + ' ' + (' '.join([str(o) for o in objects])) + ' ' + ldpath + ldflags + ' ' + cc['ldscript'], **extra_tags) # Clear list of objects self.c_object_list = [] # TODO: do we still need this???
[docs] def c_library(self, target, sources, item = None, depends = []): """ Build a library using a `target` name and list of `sources` """ chain = self.conf.get('CC') cc = self.conf.get(chain.value()) libname = str(target.relative) # TODO: do we really need c_object_list here.... we already know the sources. Only Object() stuff # needs to be discovered by the config deps lookup... target.append('.a') # Generate actions to build the library objects for src in sources: self.c_object(src) extra_deps = depends + self._lookup_config_deps(item) + self.c_object_list # Generate action for linking the library self.build.action(target, extra_deps, cc['ar'] + ' ' + cc['arflags'] + ' ' + str(target) + ' ' + (' '.join([str(o) for o in extra_deps])), pretty_name='LIB') # Clear C object list self.c_object_list = [] # Publish ourselves to the libraries list if self.conf.active_tree not in self.libraries: self.libraries[self.conf.active_tree] = {} self.libraries[self.conf.active_tree][libname] = (target, self.conf.active_dir)
[docs] def use_library(self, libraries, target = None): """ Build against a :py:`list` of `libraries` The library target for linking will be discovered by searching the generated :class:`.Action` objects in the actions layer. """ if target: if isinstance(target, Config): target = TargetPath(target.name.lower()) elif isinstance(target, str): target = TargetPath(target) target = target.absolute use_lib_tree = self.use_libraries.setdefault(self.conf.active_tree, {}) use_lib_dir = use_lib_tree.setdefault(self.conf.active_dir, {}) use_lib_target = use_lib_dir.setdefault(target, []) use_lib_target += libraries