summaryrefslogtreecommitdiff
path: root/morphlib/sourceresolver.py
diff options
context:
space:
mode:
Diffstat (limited to 'morphlib/sourceresolver.py')
-rw-r--r--morphlib/sourceresolver.py308
1 files changed, 58 insertions, 250 deletions
diff --git a/morphlib/sourceresolver.py b/morphlib/sourceresolver.py
index cbab0f7f..0b32598f 100644
--- a/morphlib/sourceresolver.py
+++ b/morphlib/sourceresolver.py
@@ -19,8 +19,6 @@ import cPickle
import logging
import os
import pylru
-import shutil
-import tempfile
import yaml
import cliapp
@@ -31,11 +29,9 @@ from morphlib.util import sanitise_morphology_path
tree_cache_size = 10000
tree_cache_filename = 'trees.cache.pickle'
-buildsystem_cache_size = 10000
-buildsystem_cache_filename = 'detected-chunk-buildsystems.cache.pickle'
-class PickleCacheManager(object): # pragma: no cover
+class PickleCacheManager(object):
'''Cache manager for PyLRU that reads and writes to Pickle files.
The 'pickle' format is less than ideal in many ways and is actually
@@ -97,13 +93,13 @@ class SourceResolverError(cliapp.AppException):
pass
-class MorphologyNotFoundError(SourceResolverError): # pragma: no cover
+class MorphologyNotFoundError(SourceResolverError):
def __init__(self, filename):
SourceResolverError.__init__(
self, "Couldn't find morphology: %s" % filename)
-class MorphologyReferenceNotFoundError(SourceResolverError): # pragma: no cover
+class MorphologyReferenceNotFoundError(SourceResolverError):
def __init__(self, filename, reference_file):
SourceResolverError.__init__(self,
"Couldn't find morphology: %s "
@@ -115,7 +111,7 @@ class MorphologyReferenceNotFoundError(SourceResolverError): # pragma: no cover
# InvalidRefError in the definitions.git repo. Currently a separate exception
# type seems the easiest way to do that, but adding enough detail to the
# gitdir.InvalidRefError class may make this class redundant in future.
-class InvalidDefinitionsRefError(SourceResolverError): # pragma: no cover
+class InvalidDefinitionsRefError(SourceResolverError):
def __init__(self, repo_url, ref):
self.repo_url = repo_url
self.ref = ref
@@ -165,17 +161,16 @@ class SourceResolver(object):
'''
def __init__(self, local_repo_cache, remote_repo_cache,
- tree_cache_manager, buildsystem_cache_manager, update_repos,
+ tree_cache_manager, update_repos,
status_cb=None):
self.lrc = local_repo_cache
self.rrc = remote_repo_cache
self.tree_cache_manager = tree_cache_manager
- self.buildsystem_cache_manager = buildsystem_cache_manager
self.update = update_repos
self.status = status_cb
- def _resolve_ref(self, resolved_trees, reponame, ref): # pragma: no cover
+ def _resolve_ref(self, resolved_trees, reponame, ref):
'''Resolves commit and tree sha1s of the ref in a repo and returns it.
If update is True then this has the side-effect of updating or cloning
@@ -230,7 +225,7 @@ class SourceResolver(object):
return absref, tree
def _get_file_contents_from_definitions(self, definitions_checkout_dir,
- filename): # pragma: no cover
+ filename):
fp = os.path.join(definitions_checkout_dir, filename)
if os.path.exists(fp):
with open(fp) as f:
@@ -239,34 +234,9 @@ class SourceResolver(object):
logging.debug("Didn't find %s in definitions", filename)
return None
- def _get_file_contents_from_repo(self, reponame,
- sha1, filename): # pragma: no cover
- if self.lrc.has_repo(reponame):
- self.status(msg="Looking for %(reponame)s:%(filename)s in the "
- "local repo cache.",
- reponame=reponame, filename=filename, chatty=True)
- try:
- repo = self.lrc.get_repo(reponame)
- text = repo.read_file(filename, sha1)
- except IOError:
- text = None
- elif self.rrc is not None:
- self.status(msg="Looking for %(reponame)s:%(filename)s in the "
- "remote repo cache.",
- reponame=reponame, filename=filename, chatty=True)
- try:
- text = self.rrc.cat_file(reponame, sha1, filename)
- except morphlib.remoterepocache.CatFileError:
- text = None
- else: # pragma: no cover
- repo = self.lrc.get_updated_repo(reponame, sha1)
- text = repo.read_file(filename, sha1)
-
- return text
-
def _get_file_contents(self, definitions_checkout_dir, definitions_repo,
definitions_absref, reponame, sha1,
- filename): # pragma: no cover
+ filename):
'''Read the file at the specified location.
Returns None if the file does not exist in the specified commit.
@@ -275,7 +245,7 @@ class SourceResolver(object):
text = None
if reponame == definitions_repo and \
- sha1 == definitions_absref: # pragma: no cover
+ sha1 == definitions_absref:
text = self._get_file_contents_from_definitions(
definitions_checkout_dir, filename)
else:
@@ -283,31 +253,28 @@ class SourceResolver(object):
return text
- def _check_version_file(self, definitions_checkout_dir): # pragma: no cover
+ def _check_version_file(self, definitions_checkout_dir):
version_text = self._get_file_contents_from_definitions(
definitions_checkout_dir, 'VERSION')
return morphlib.definitions_version.check_version_file(version_text)
def _get_morphology(self, resolved_morphologies, definitions_checkout_dir,
- definitions_repo, definitions_absref, morph_loader,
- reponame, sha1, filename): # pragma: no cover
+ morph_loader, filename):
'''Read the morphology at the specified location.
Returns None if the file does not exist in the specified commit.
'''
- key = (reponame, sha1, filename)
- if key in resolved_morphologies:
- return resolved_morphologies[key]
+ if filename in resolved_morphologies:
+ return resolved_morphologies[filename]
- text = self._get_file_contents(definitions_checkout_dir,
- definitions_repo, definitions_absref,
- reponame, sha1, filename)
+ text = self._get_file_contents_from_definitions(
+ definitions_checkout_dir, filename)
morph = morph_loader.load_from_string(text, filename)
if morph is not None:
- resolved_morphologies[key] = morph
+ resolved_morphologies[filename] = morph
return morph
@@ -318,24 +285,20 @@ class SourceResolver(object):
definitions_ref,
definitions_absref,
definitions_tree,
- definitions_version,
morph_loader,
system_filenames,
- visit): # pragma: no cover
+ visit):
definitions_queue = collections.deque(system_filenames)
chunk_queue = set()
- def get_morphology(repo, sha1, filename):
+ def get_morphology(filename):
return self._get_morphology(resolved_morphologies,
- definitions_checkout_dir,
- definitions_repo, definitions_absref,
- morph_loader, repo, sha1, filename)
-
+ definitions_checkout_dir, morph_loader,
+ filename)
while definitions_queue:
filename = definitions_queue.popleft()
- morphology = get_morphology(definitions_repo, definitions_absref,
- filename)
+ morphology = get_morphology(filename)
if morphology is None:
raise MorphologyNotFoundError(filename)
@@ -356,214 +319,68 @@ class SourceResolver(object):
sanitise_morphology_path(s['morph'])
for s in morphology['build-depends'])
for c in morphology['chunks']:
- # This field is only valid in strata from definitions
- # version 6 onwards. Validation is done in morphloader.py.
- buildsystem = c.get('build-system')
- if 'morph' not in c:
- # Autodetect a path if one is not given. This is to
- # support the deprecated approach of putting the chunk
- # .morph file in the toplevel directory of the chunk
- # repo, instead of putting it in the definitions.git
- # repo.
- #
- # All users should be specifying a full path to the
- # chunk morph file, using the 'morph' field, and this
- # code path should be removed.
- path = sanitise_morphology_path(
- c.get('morph', c['name']))
-
- chunk_queue.add((c['repo'], c['ref'], path,
- buildsystem))
- else:
+ if 'morph' in c:
# Now, does this path actually exist?
path = c['morph']
- morphology = get_morphology(definitions_repo,
- definitions_absref,
- path)
+ morphology = get_morphology(path)
if morphology is None:
raise MorphologyReferenceNotFoundError(
path, filename)
- chunk_queue.add((c['repo'], c['ref'], path,
- buildsystem))
+ chunk_queue.add((c['repo'], c['ref'], path, None))
+ else:
+ # We invent a filename here, so that the rest of the
+ # Morph code doesn't need to know about the predefined
+ # build instructions.
+ chunk_name = c['name'] + '.morph'
+ chunk_queue.add((c['repo'], c['ref'], chunk_name,
+ c['build-system']))
return chunk_queue
- @staticmethod
- def _create_morphology_for_build_system(morph_loader, buildsystem,
- morph_name): # pragma: no cover
+ def _create_morphology_for_build_system(self, morph_loader, buildsystem,
+ morph_name):
morph = buildsystem.get_morphology(morph_name)
morph_loader.validate(morph)
morph_loader.set_commands(morph)
morph_loader.set_defaults(morph)
return morph
- @classmethod
- def _generate_morph_and_cache_buildsystem(cls, resolved_morphologies,
- resolved_buildsystems,
- morph_loader,
- definition_key, chunk_key,
- buildsystem,
- morph_name): # pragma: no cover
- logging.debug('Caching build system for chunk with key %s', chunk_key)
-
- resolved_buildsystems[chunk_key] = buildsystem.name
-
- morphology = cls._create_morphology_for_build_system(
- morph_loader, buildsystem, morph_name)
- resolved_morphologies[definition_key] = morphology
- return morphology
-
- def _detect_build_system(self, reponame, sha1, expected_filename):
- '''Attempt to detect buildsystem of the given commit.
-
- Returns None if no known build system was detected.
-
- '''
- self.status(msg="File %s doesn't exist: attempting to infer "
- "chunk morph from repo's build system" %
- expected_filename, chatty=True)
-
- file_list = None
-
- if self.lrc.has_repo(reponame):
- repo = self.lrc.get_repo(reponame)
- try:
- file_list = repo.list_files(ref=sha1, recurse=False)
- except morphlib.gitdir.InvalidRefError: # pragma: no cover
- pass
- elif self.rrc is not None:
- try:
- # This may or may not succeed; if the is repo not
- # hosted on the same Git server as the cache server then
- # it'll definitely fail.
- file_list = self.rrc.ls_tree(reponame, sha1)
- except morphlib.remoterepocache.LsTreeError:
- pass
-
- if not file_list:
- repo = self.lrc.get_updated_repo(reponame, sha1)
- file_list = repo.list_files(ref=sha1, recurse=False)
-
- buildsystem = morphlib.buildsystem.detect_build_system(file_list)
-
- if buildsystem is None:
- # It might surprise you to discover that if we can't autodetect a
- # build system, we raise MorphologyNotFoundError. Users are
- # required to provide a morphology for any chunk where Morph can't
- # infer the build instructions automatically, so this is the right
- # error.
- raise MorphologyNotFoundError(expected_filename)
-
- return buildsystem
-
def process_chunk(self, resolved_morphologies, resolved_trees,
- resolved_buildsystems, definitions_checkout_dir,
- definitions_repo, definitions_absref,
- definitions_version, morph_loader, chunk_repo, chunk_ref,
- filename, chunk_buildsystem, visit): # pragma: no cover
- absref = None
- tree = None
- chunk_key = None
- buildsystem = None
-
- morph_name = os.path.splitext(os.path.basename(filename))[0]
-
- def get_morphology(repo, sha1, filename):
- return self._get_morphology(
- resolved_morphologies, definitions_checkout_dir,
- definitions_repo, definitions_absref, morph_loader,
- repo, sha1, filename)
-
- # Get morphology from definitions repo
- definition_key = (definitions_repo, definitions_absref, filename)
- morphology = get_morphology(*definition_key)
-
- if morphology:
- absref, tree = self._resolve_ref(resolved_trees, chunk_repo,
- chunk_ref)
- visit(chunk_repo, chunk_ref, filename, absref, tree, morphology)
- return
-
+ definitions_checkout_dir, morph_loader, chunk_repo,
+ chunk_ref, filename, chunk_buildsystem,
+ visit):
absref, tree = self._resolve_ref(resolved_trees, chunk_repo, chunk_ref)
- chunk_key = (chunk_repo, absref, filename)
-
- def generate_morph_and_cache_buildsystem(buildsystem):
- return self._generate_morph_and_cache_buildsystem(
- resolved_morphologies, resolved_buildsystems, morph_loader,
- definition_key, chunk_key, buildsystem, morph_name)
-
- if definitions_version >= 6:
- # All build-system information is specified in the definitions from
- # version 6 onwards. Either 'morph' or 'build-system' should be
- # specified for each chunk.
- if chunk_buildsystem is None:
- # The validation done in 'morphloader' should mean that this
- # never happens.
- raise SourceResolverError(
- 'Please specify either "build-system" or "morph" for %s.' %
- chunk_key)
+
+ if chunk_buildsystem is None:
+ # Build instructions defined in a chunk .morph file. An error is
+ # already raised in _process_definitions_with_children() if the
+ # 'morph' field points to a file that doesn't exist.
+ morphology = self._get_morphology(resolved_morphologies,
+ definitions_checkout_dir,
+ morph_loader, filename)
+ else:
+ # Chunk uses one of the predefined build systems. In this case
+ # 'filename' will be faked (name of chunk + '.morph').
buildsystem = morphlib.buildsystem.lookup_build_system(
chunk_buildsystem)
- if definition_key in resolved_morphologies:
- morphology = resolved_morphologies[definition_key]
- else:
- morphology = generate_morph_and_cache_buildsystem(buildsystem)
-
- elif chunk_key in resolved_buildsystems:
- logging.debug('Build system for %s is cached', str(chunk_key))
- self.status(msg='Build system for %(chunk)s is cached',
- chunk=str(chunk_key),
- chatty=True)
- buildsystem_name = resolved_buildsystems[chunk_key]
- buildsystem = morphlib.buildsystem.lookup_build_system(
- buildsystem_name)
-
- # If the build system for this chunk is cached then:
- # * the chunk does not have a chunk morph
- # (so we don't need to look for one)
- #
- # * a suitable (generated) morphology may already be cached.
- #
- # If the morphology is not already cached we can generate it
- # from the build-system and cache it.
- if definition_key in resolved_morphologies:
- morphology = resolved_morphologies[definition_key]
- else:
- morphology = generate_morph_and_cache_buildsystem(buildsystem)
- else:
- logging.debug('Build system for %s is NOT cached', str(chunk_key))
- # build-system not cached, look for morphology in chunk repo
- # this can be slow (we may need to clone the repo from a remote)
- morphology = get_morphology(*chunk_key)
-
- if morphology != None:
- resolved_morphologies[definition_key] = morphology
- else:
- # This chunk doesn't have a chunk morph
- buildsystem = self._detect_build_system(*chunk_key)
-
- if buildsystem is None:
- raise MorphologyNotFoundError(filename)
- else:
- morphology = generate_morph_and_cache_buildsystem(
- buildsystem)
+ morphology = self._create_morphology_for_build_system(
+ morph_loader, buildsystem, filename)
visit(chunk_repo, chunk_ref, filename, absref, tree, morphology)
def traverse_morphs(self, definitions_repo, definitions_ref,
system_filenames,
visit=lambda rn, rf, fn, arf, m: None,
- definitions_original_ref=None): # pragma: no cover
+ definitions_original_ref=None):
resolved_morphologies = {}
with morphlib.util.temp_dir() as definitions_checkout_dir, \
- self.tree_cache_manager.open() as resolved_trees, \
- self.buildsystem_cache_manager.open() as resolved_buildsystems:
+ self.tree_cache_manager.open() as resolved_trees:
# Resolve the repo, ref pair for definitions repo, cache result
try:
@@ -582,9 +399,8 @@ class SourceResolver(object):
definitions_absref, definitions_checkout_dir)
definitions_version = self._check_version_file(
- definitions_checkout_dir)
- morph_loader = morphlib.morphloader.MorphologyLoader(
- definitions_version=definitions_version)
+ definitions_checkout_dir)
+ morph_loader = morphlib.morphloader.MorphologyLoader()
# First, process the system and its stratum morphologies. These
# will all live in the same Git repository, and will point to
@@ -592,22 +408,19 @@ class SourceResolver(object):
chunk_queue = self._process_definitions_with_children(
resolved_morphologies, definitions_checkout_dir,
definitions_repo, definitions_ref, definitions_absref,
- definitions_tree, definitions_version, morph_loader,
+ definitions_tree, morph_loader,
system_filenames, visit)
# Now process all the chunks involved in the build.
for repo, ref, filename, buildsystem in chunk_queue:
self.process_chunk(resolved_morphologies, resolved_trees,
- resolved_buildsystems,
- definitions_checkout_dir,
- definitions_repo, definitions_absref,
- definitions_version, morph_loader, repo,
- ref, filename, buildsystem, visit)
+ definitions_checkout_dir, morph_loader,
+ repo, ref, filename, buildsystem, visit)
def create_source_pool(lrc, rrc, repo, ref, filenames, cachedir,
original_ref=None, update_repos=True,
- status_cb=None): # pragma: no cover
+ status_cb=None):
'''Find all the sources involved in building a given system.
Given a system morphology, this function will traverse the tree of stratum
@@ -634,12 +447,7 @@ def create_source_pool(lrc, rrc, repo, ref, filenames, cachedir,
tree_cache_manager = PickleCacheManager(
os.path.join(cachedir, tree_cache_filename), tree_cache_size)
- buildsystem_cache_manager = PickleCacheManager(
- os.path.join(cachedir, buildsystem_cache_filename),
- buildsystem_cache_size)
-
- resolver = SourceResolver(lrc, rrc, tree_cache_manager,
- buildsystem_cache_manager, update_repos,
+ resolver = SourceResolver(lrc, rrc, tree_cache_manager, update_repos,
status_cb)
resolver.traverse_morphs(repo, ref, filenames,
visit=add_to_pool,