import os, copy, sys if sys.version_info[1]>=5: # md5 is deprecated since Python version 2.5 # this will not work in version 3.x of Python but there's some time until then import hashlib use_hashlib = True else: # for compatability reasons for those still using ver <= Python 2.4 import md5 use_hashlib = False from lib import normal_path class RawTarget(object): def __init__(self, inputs=None, tool=None, options=None, mono_options=None, settings_options=None): self.inputs = inputs if inputs is None: self.inputs = [] self.tool = tool self.options = options self.mono_options = mono_options self.settings_options = settings_options def __repr__(self): return 'RawTarget(inputs=%r)' % (self.inputs, ) class RawOptions(object): def __init__(self, overriders=None, appenders=None): self.overriders = overriders self.appenders = appenders if not self.overriders: self.overriders = {} if not self.appenders: self.appenders = {} def affect(self, options): for key, value in self.overriders.iteritems(): options[key] = value for key, value in self.appenders.iteritems(): if isinstance(value, list): options[key] = options.get(key, []) + value elif isinstance(value, str): options[key] = options.get(key, '') + value elif isinstance(value, dict): odict = options.get(key, {}) odict.update(value) options[key] = odict else: raise NotImplementedError() class RawInput(object): def __init__(self, name=None, only_build_dep=False, root_relative=False): self.name = name self.only_build_dep = only_build_dep self.root_relative = root_relative def __repr__(self): return 'RawInput(%s)' % (self.name, ) class Statistics(object): def __init__(self): self.targets = 0 self.made_targets = 0 statistics = Statistics() target_cache = {} class Target(object): def __init__(self, pathname, raw_target, inputs, original_tinput, options): self.pathname = pathname self.raw_target = raw_target self.inputs = inputs self.as_input = original_tinput self.tool = self.raw_target.tool self.hash = None self.built = False self.options = options if not self.tool: from defaults import get_default_tool self.tool = get_default_tool(self) if not self.tool: from tools import Empty self.tool = Empty() def cache(self): if use_hashlib: hasho = hashlib.md5() else: hasho = md5.md5() for inputo in self.inputs: hasho.update(inputo.hash) hasho.update(self.tool.cache(self)) hasho.update(self.pathname) self.hash = hasho.digest() if self.hash in target_cache: return target_cache[self.hash] target_cache[self.hash] = self return self def exists(self): return os.path.exists(self.pathname) def mtime(self): return os.stat(self.pathname).st_mtime def filenames(self): from comake import COMAKE_OUTPUT_DIRECTORY comake_shortname = os.path.basename(self.pathname) + '-' + self.hash.encode('hex') comake_outdir = os.path.join(os.path.dirname(self.pathname), COMAKE_OUTPUT_DIRECTORY) comake_pathname = os.path.join(comake_outdir, comake_shortname) comake_relname = os.path.join(COMAKE_OUTPUT_DIRECTORY, comake_shortname) return (comake_shortname, comake_outdir, comake_pathname, comake_relname) def _make(self, reporter): if os.path.islink(self.pathname): os.unlink(self.pathname) self.tool.make(self, reporter) if self.tool.EMPTY: return statistics.made_targets += 1 comake_shortname, comake_outdir, comake_pathname, comake_relname = self.filenames() if not os.path.exists(comake_outdir): os.mkdir(comake_outdir) os.rename(self.pathname, comake_pathname) os.symlink(comake_relname, self.pathname) def get_ext(self): return os.path.splitext(self.pathname)[1] def get_actual_inputs(self): actual_inputs = [] for tinput in self.inputs: if not tinput.as_input.only_build_dep: actual_inputs.append(tinput) return actual_inputs def build(self, reporter=None): if self.built: return 0 if not self.tool.EMPTY: statistics.targets += 1 if reporter is None: from report import Report reporter = Report() reporter.title(self.pathname) rebuild_target = False rebuild_reason = None builds = 0 for tinput in self.inputs: builds += tinput.build(reporter.sub()) if os.path.islink(self.pathname): link = os.readlink(self.pathname) comake_shortname, comake_outdir, comake_pathname, comake_relname = self.filenames() if comake_relname != link: os.unlink(self.pathname) os.symlink(comake_relname, self.pathname) if not os.path.exists(self.pathname): rebuild_target = True rebuild_reason = "target requires creation" else: my_mtime = self.mtime() others_mtime = [tinput.mtime() for tinput in self.inputs] if others_mtime: max_others_mtime = max(others_mtime) if max_others_mtime > my_mtime: rebuild_target = True rebuild_reason = "target is older than dependencies" if not rebuild_target: if builds: rebuild_target = True rebuild_reason = "dependencies were rebuilt" if not rebuild_target: if self.tool.rebuild_needed(self): rebuild_target = True rebuild_reason = "rebuilding required by tool" if rebuild_target: if not self.tool.EMPTY: reporter.print_title() reporter.print_text("Building: %s" % (rebuild_reason, )) self._make(reporter) builds += 1 self.built = True return builds def dump(self, indent=0): print indent*" " + self.pathname + " { " print indent*" " + " Built: %r" % (self.built, ) print indent*" " + " %x" % (id(self), ) print indent*" " + " " + self.hash.encode('hex') print indent*" " + " %r" % (self.tool.cache(self), ) count = 1 if len(self.inputs) != 0: print indent*" " + " Inputs#: %d" % (len(self.inputs), ) if not self.built: for tinput in self.inputs: count += tinput.dump(indent+1) else: print indent*" " + " [..]" print indent*" " + " %d" % (count, ) print indent*" " + "} " self.built = True return count _per_directory_comake_file = {} class COMakeFile(object): def __init__(self): self.targets = {} def __repr__(self): return 'COMakeFile(%s)' % (repr(self.targets), ) _globals_dict = None class BuildCancelError(Exception): pass def get_global_dict(): global _globals_dict if _globals_dict is None: _globals_dict = {} _globals_dict['Target'] = RawTarget _globals_dict['Input'] = RawInput _globals_dict['Options'] = RawOptions _globals_dict['pathjoin'] = os.path.join _globals_dict['getenv'] = os.getenv _globals_dict['BuildCancelError'] = BuildCancelError from comake.tools import exported_tools for tool in exported_tools: _globals_dict[tool.__name__] = tool return _globals_dict def get_per_directory_comake_file(dirname): comake_file = _per_directory_comake_file.get(dirname) if comake_file: return comake_file comake_file = COMakeFile() from comake import DEFAULT_BUILD_NAME build_name = os.path.join(dirname, DEFAULT_BUILD_NAME) if not os.path.exists(build_name): return comake_file globals_dict = dict(get_global_dict()) def input_list(ext, new_ext): lst = [] for filename in os.listdir(dirname): base_part, ext_part = os.path.splitext(filename) if ext_part == ext: lst.append(RawInput(base_part + new_ext)) return lst def target_pathname(filename): return os.path.join(dirname, filename) def deftarget(filename): from comake.defaults import get_default_raw_target return get_default_raw_target(target_pathname(filename)) globals_dict['input_list'] = input_list globals_dict['target_pathname'] = target_pathname globals_dict['deftarget'] = deftarget globals_dict['current_dirname'] = dirname execfile(build_name, globals_dict, vars(comake_file)) _per_directory_comake_file[dirname] = comake_file return comake_file def get_raw_target(pathname): dirname = os.path.dirname(pathname) basename = os.path.basename(pathname) comake_file = get_per_directory_comake_file(dirname) raw_target = comake_file.targets.get(basename) if not raw_target: from comake.defaults import get_default_raw_target raw_target = get_default_raw_target(pathname) if not raw_target: if os.path.exists(pathname): if not os.path.islink(pathname): return RawTarget() else: print os.readlink(pathname) return raw_target class TargetNotFoundError(Exception): pass def create_target_tree(pathname): def _recur(pathname, original_tinput, options): raw_target = get_raw_target(pathname) if not raw_target: print "Error, target %s not found" % (pathname, ) raise TargetNotFoundError() options = copy.deepcopy(options) if raw_target.options: raw_target.options.affect(options) if raw_target.settings_options: from settings import settings raw_target.settings_options.affect(vars(settings)) inputs = [] for index, tinput in enumerate(raw_target.inputs): if tinput.root_relative: abs_name = tinput.name else: abs_name = os.path.join(os.path.dirname(pathname), tinput.name) abs_name = normal_path(abs_name) try: inputs.append(_recur(abs_name, tinput, options)) except TargetNotFoundError: print "Included from: %s [%d]" % (pathname, index+1) raise if raw_target.mono_options: raw_target.mono_options.affect(options) target = Target(pathname, raw_target, inputs, original_tinput, options) target = target.cache() return target return _recur(pathname, None, {}) def clean(): from comake import COMAKE_OUTPUT_DIRECTORY from comake import build_root def unlink(pathname): pathname_display = pathname[len(build_root)+1:] print "removing file %s" % (pathname_display, ) os.unlink(pathname) def rmdir(pathname): pathname_display = pathname[len(build_root)+1:] print "removing dir %s" % (pathname_display, ) os.rmdir(pathname) def _recur(pathname): comake_dir = os.path.basename(pathname) == COMAKE_OUTPUT_DIRECTORY for filename in os.listdir(pathname): fullname = os.path.join(pathname, filename) if os.path.islink(fullname): link = os.readlink(fullname) if link.startswith(COMAKE_OUTPUT_DIRECTORY): unlink(fullname) if os.path.isdir(fullname): _recur(fullname) if comake_dir: unlink(fullname) if comake_dir: rmdir(pathname) return _recur(build_root)