Files
mlpack/fastlib/trunk/script/dep.py
T

216 lines
7.6 KiB
Python

# Simple abstract dependency system suitable for generating things like
# makefiles.
import util
import os
class DestFile:
def __init__(self, name, simplename, dep_files={}, parameterization={}):
# TODO: Separate "my" parameterization from "dep" parameterization
self.name = name
if simplename:
self.simplename = simplename
else:
self.simplename = name
self.parameterization = parameterization
self.dep_files = dep_files
def __repr__(self):
return self.name
def __hash__(self):
return hash(self.name) ^ hash(self.simplename)
def __cmp__(self, other):
if isinstance(other, DestFile):
return cmp(self.name, other.name)
else:
return NotImplemented
class DepState:
def __init__(self, files, params):
self.files = files
self.params = params
self.parameterization = {}
for file in filemap_to_files(files):
self.parameterization.update(file.parameterization)
class DepSys:
def __init__(self):
self.runs = {}
def begin(self, state):
raise Exception("DepSys not overridden")
class DepSysEntry:
"""Abstract dependency system entry.
You must override this and provide a way of generating filenames.
"""
def __init__(self, sys, state):
"""
sys - a DepSys object
state - a DepState object
"""
self.sys = sys
self.state = state
def file(self, simplename, *extra_parameterization):
parameterization = dict(self.state.parameterization)
my_parameterization = dict([(k, self.state.params[k])
for k in extra_parameterization])
parameterization.update(my_parameterization)
realname = self._make_name(simplename, parameterization)
self.ensure_writable(realname)
return DestFile(realname, simplename, self.state.files, parameterization)
def _make_name(self, simplename, parameterization):
raise Exception("not implemented")
def ensure_writable(realname):
raise Exception("not implemented")
def end(self, files):
raise Exception("not implemented")
def command(self, str):
raise Exception("not implemented")
class BadResult:
pass
class FileCollection:
def __init__(self, pairs):
self.files_by_type = {}
# use seen_pairs to avoid adding same file twice
seen_pairs = {}
for (typename, file) in pairs:
if not (typename, file) in seen_pairs:
seen_pairs[(typename, file)] = 1
self.files_by_type.setdefault(typename, []).append(file)
def single(self, typename):
result = self.files_by_type[typename]
if len(result) != 1:
raise BadResult()
return result[0]
def many(self, typename):
return self.files_by_type.get(typename, [])
def to_pairs(self):
pairs = []
for (classname, files) in self.files_by_type.items():
pairs += [(classname, file) for file in files]
return pairs
def to_files(self):
return util.collapse_once(
[files for (classname, files) in self.files_by_type.items()])
def to_names(self):
return [f.name for f in self.to_files()]
def filemap_to_files(filemap):
return util.collapse_once(
[file_collection.to_files() for file_collection in filemap.values()])
class Metarule:
"""
Generic rule.
This rule takes in several labelled sets of other rules.
TODO: Explain the idea of "meta-rule" better
This is more a meta-rule, which can depend on other meta-rules.
Meta-rules are asked for the exact filenames they produce, which
recursively ask their dependency rules what files they produce. Once a
meta-rule knows what files its dependency rules produce, it can generate
commands to produce its own result files, and tell ITS dependents what
files it generated.
"""
# TODO: (requires thought) If multiple rules depend on a single rule, I
# *think* this code, how it is written it will run "doit" for that rule
# multiple times, rather than once. This might be necessary due to
# parameterization, I haven't thought much about it.
def __init__(self, **dep_map):
self.dep_map = dep_map
def generate(self, sys, params):
def fake_cont(x):
pass
self.run(sys, params, fake_cont)
def run(self, sys, params, continuation):
key = (self, util.dicthash(params))
if key in sys.runs:
continuation(sys.runs[key])
else:
all_deps = []
for (dep_class, deps) in self.dep_map.items():
for dep in deps:
all_deps.append((dep_class, dep))
dep_count = [len(all_deps)]
dep_files = {}
def check_done():
if dep_count[0] == 0:
dep_files_converted = dict(self.dep_map)
for k in self.dep_map:
dep_files_converted[k] = FileCollection(dep_files.get(k, []))
state = DepState(dep_files_converted, params)
# Also memoize here!
action = sys.begin(state)
my_files = self.doit(action, dep_files_converted, params)
action.end(my_files)
sys.runs[key] = my_files
continuation(my_files)
dep_count[0] = -1
def my_continuation_for(dep_class):
def impl(single_dep_files):
dep_files.setdefault(dep_class, []).extend(single_dep_files)
dep_count[0] -= 1
check_done()
return impl
for (dep_class, dep) in all_deps:
dep.run(sys, params, my_continuation_for(dep_class))
check_done()
def doit(self, realrule, dep_files, params):
"""
The doit function is called for all subclasses of this. The doit
function doesn't necessarily perform the action, but is required to
figure out exactly what actions are required. For instance, for the
build system, this generates a Makefile rule, which contains the exact
commands and file names.
The following are sent to an implementors' doit:
- dep_files: the files "generated" by all rules it depends on. The rules
are grouped into the label of the rule (given when you called
Metarule.__init__) and then into the type of the file (like file
extension). The rules you are dependent on were the ones responsible
for assigning the file extension. Each file has a "simple" name and
an "absolute" name. The simple name should be used for generating
result files, and the absolute name is the location of the source
file.
- params: the parameterization of the instantiation; that is, each
file can be created with combinations of different parameters, and the
parameterization is used to identify different "builds" of the same
file. in the case of the build system, this is different operating
systems or compilation modes. certain rules care about certain
parameters, so when they generate destination files, they specify
which parameters they care about.
- the state (an encapsulation of params and input files)
TODO: Remember why this is here, it doesn't seem to be used anywhere
- systentry: a single entry in thie build system. This is equivalent
to a Makefile rule waiting for you to put actions into it.
Unlike this Metarule class, this object wants exact specific commands
for a specific parameterization, like a Makefile rule with real
filenames and commands.
You must return:
- a list of key,val pairs, where key is some indicator of file type.
Example is [("header", "foo.h"), ("source", "foo.cc")].
"""
# TODO: Implement me in subclasses
return util.map_values(FileCollection.to_pairs, dep_files)
class CustomRule(Metarule):
def __init__(self, dep_map, doit_fn):
Metarule.__init__(self, **dep_map)
self.doit_fn = doit_fn
def doit(self, realrule, dep_files, params):
return self.doit_fn(realrule, dep_files, params)