diff --git a/buildstream/_artifactcache.py b/buildstream/_artifactcache.py
index 04089f9a8363ac0a8fbe32fb6812494d30da513b..f272d7f1b6a7e77d69666c31a9c5b520aa969ec7 100644
--- a/buildstream/_artifactcache.py
+++ b/buildstream/_artifactcache.py
@@ -18,8 +18,6 @@
 #        Tristan Maat <tristan.maat@codethink.co.uk>
 
 import os
-from collections.abc import Mapping
-import string
 
 from ._basecache import BaseCache
 from .types import _KeyStrength
diff --git a/buildstream/_cas/cascache.py b/buildstream/_cas/cascache.py
index fe25efce6c0a1aa6cddb2e644811cc5160d0067a..c9bd92f70526a80b3202e47fa2de3e770be27858 100644
--- a/buildstream/_cas/cascache.py
+++ b/buildstream/_cas/cascache.py
@@ -144,21 +144,22 @@ class CASCache():
     #     ref (str): The ref whose directory to extract
     #     path (str): The destination path
     #     subdir (str): Optional specific dir to extract
+    #     hash_dir (bool): whether to extract to a directory named the directories hash
     #
     # Raises:
     #     CASCacheError: In cases there was an OSError, or if the ref did not exist.
     #
     # Returns: path to extracted directory
     #
-    def extract(self, ref, path, subdir=None):
+    def extract(self, ref, path, subdir=None, hash_dir=True):
         tree = self.resolve_ref(ref, update_mtime=True)
 
-        originaldest = dest = os.path.join(path, tree.hash)
+        originaldest = dest = os.path.join(path, tree.hash) if hash_dir else str(path)
 
         # If artifact is already extracted, check if the optional subdir
         # has also been extracted. If the artifact has not been extracted
         # a full extraction would include the optional subdir
-        if os.path.isdir(dest):
+        if os.path.isdir(dest) and hash_dir is True:
             if subdir:
                 if not os.path.isdir(os.path.join(dest, subdir)):
                     dest = os.path.join(dest, subdir)
diff --git a/buildstream/_context.py b/buildstream/_context.py
index 369a05a0533b909655902507b4c6f6ac2b53ef09..1588a86d48467c05c6891097808ad409ee8ded99 100644
--- a/buildstream/_context.py
+++ b/buildstream/_context.py
@@ -31,6 +31,7 @@ from ._exceptions import LoadError, LoadErrorReason, BstError
 from ._message import Message, MessageType
 from ._profile import Topics, profile_start, profile_end
 from ._artifactcache import ArtifactCache
+from ._sourcecache import SourceCache
 from ._cas import CASCache, CASQuota, CASCacheUsage
 from ._workspaces import Workspaces, WorkspaceProjectCache
 from .plugin import _plugin_lookup
@@ -64,6 +65,9 @@ class Context():
         # The directory where various sources are stored
         self.sourcedir = None
 
+        # specs for source cache remotes
+        self.source_cache_specs = None
+
         # The directory where build sandboxes will be created
         self.builddir = None
 
@@ -151,6 +155,7 @@ class Context():
         self._message_handler = None
         self._message_depth = deque()
         self._artifactcache = None
+        self._sourcecache = None
         self._projects = []
         self._project_overrides = {}
         self._workspaces = None
@@ -168,6 +173,7 @@ class Context():
     # Args:
     #    config (filename): The user specified configuration file, if any
     #
+
     # Raises:
     #   LoadError
     #
@@ -253,6 +259,9 @@ class Context():
         # Load artifact share configuration
         self.artifact_cache_specs = ArtifactCache.specs_from_config_node(defaults)
 
+        # Load source cache config
+        self.source_cache_specs = SourceCache.specs_from_config_node(defaults)
+
         self.remote_execution_specs = SandboxRemote.specs_from_config_node(defaults)
 
         # Load pull build trees configuration
@@ -334,6 +343,13 @@ class Context():
     def get_cache_usage(self):
         return CASCacheUsage(self.get_casquota())
 
+    @property
+    def sourcecache(self):
+        if not self._sourcecache:
+            self._sourcecache = SourceCache(self)
+
+        return self._sourcecache
+
     # add_project():
     #
     # Add a project to the context.
diff --git a/buildstream/_exceptions.py b/buildstream/_exceptions.py
index 8728f6e6904e4c46ec576558711cef5d790bc378..f2d34bcba2a8206d731971a2a95df718e802f6d8 100644
--- a/buildstream/_exceptions.py
+++ b/buildstream/_exceptions.py
@@ -272,6 +272,15 @@ class SandboxError(BstError):
         super().__init__(message, detail=detail, domain=ErrorDomain.SANDBOX, reason=reason)
 
 
+# SourceCacheError
+#
+# Raised when errors are encountered in the source caches
+#
+class SourceCacheError(BstError):
+    def __init__(self, message, detail=None, reason=None):
+        super().__init__(message, detail=detail, domain=ErrorDomain.SANDBOX, reason=reason)
+
+
 # ArtifactError
 #
 # Raised when errors are encountered in the artifact caches
diff --git a/buildstream/_loader/loader.py b/buildstream/_loader/loader.py
index 17d7c63fb2592f9f33d4400d4aa8eef3957ccbd4..7c2ba1d5592e6a5ef6dfc35a61f8e727cccdb1f5 100644
--- a/buildstream/_loader/loader.py
+++ b/buildstream/_loader/loader.py
@@ -507,6 +507,7 @@ class Loader():
         element = Element._new_from_meta(meta_element)
         element._preflight()
 
+        last_source = None
         sources = list(element.sources())
         for idx, source in enumerate(sources):
             # Handle the case where a subproject needs to be fetched
@@ -529,6 +530,12 @@ class Loader():
                 raise LoadError(LoadErrorReason.SUBPROJECT_INCONSISTENT,
                                 "Subproject has no ref for junction: {}".format(filename),
                                 detail=detail)
+            last_source = source
+
+        if last_source:
+            previous_sources = list(sources)
+            previous_sources.pop()
+            last_source._cache(previous_sources)
 
         workspace = element._get_workspace()
         if workspace:
diff --git a/buildstream/_project.py b/buildstream/_project.py
index 92ac8cab5ebda2a2ca51178d54338757d5bc7e79..0c49aff55663e381afdb89f655b73abfa1b7b883 100644
--- a/buildstream/_project.py
+++ b/buildstream/_project.py
@@ -31,6 +31,7 @@ from ._profile import Topics, profile_start, profile_end
 from ._exceptions import LoadError, LoadErrorReason
 from ._options import OptionPool
 from ._artifactcache import ArtifactCache
+from ._sourcecache import SourceCache
 from .sandbox import SandboxRemote
 from ._elementfactory import ElementFactory
 from ._sourcefactory import SourceFactory
@@ -137,6 +138,7 @@ class Project():
         self._shell_host_files = []   # A list of HostMount objects
 
         self.artifact_cache_specs = None
+        self.source_cache_specs = None
         self.remote_execution_specs = None
         self._sandbox = None
         self._splits = None
@@ -236,7 +238,7 @@ class Project():
             'artifacts', 'options',
             'fail-on-overlap', 'shell', 'fatal-warnings',
             'ref-storage', 'sandbox', 'mirrors', 'remote-execution',
-            'sources', '(@)'
+            'sources', 'source-caches', '(@)'
         ])
 
     # create_element()
@@ -572,6 +574,9 @@ class Project():
             parent = self.junction._get_project()
             self.artifact_cache_specs = parent.artifact_cache_specs + self.artifact_cache_specs
 
+        # Load source caches with pull/push config
+        self.source_cache_specs = SourceCache.specs_from_config_node(config, self.directory)
+
         # Load remote-execution configuration for this project
         project_specs = SandboxRemote.specs_from_config_node(config, self.directory)
         override_specs = SandboxRemote.specs_from_config_node(
diff --git a/buildstream/_scheduler/queues/fetchqueue.py b/buildstream/_scheduler/queues/fetchqueue.py
index fc11fd1d19f21540bfab9a75e71b37972d3fb85b..92cd9fb3beb9f1bc98c0039375ae869cd51d9ca7 100644
--- a/buildstream/_scheduler/queues/fetchqueue.py
+++ b/buildstream/_scheduler/queues/fetchqueue.py
@@ -62,7 +62,7 @@ class FetchQueue(Queue):
 
         # This will automatically skip elements which
         # have no sources.
-        if element._get_consistency() == Consistency.CACHED:
+        if element._get_consistency() == Consistency.STAGED:
             return QueueStatus.SKIP
 
         return QueueStatus.READY
@@ -74,5 +74,5 @@ class FetchQueue(Queue):
 
         element._update_state()
 
-        # Successful fetch, we must be CACHED now
-        assert element._get_consistency() == Consistency.CACHED
+        # Successful fetch, we must be CACHED or STAGED now
+        assert element._get_consistency() >= Consistency.CACHED
diff --git a/buildstream/_sourcecache.py b/buildstream/_sourcecache.py
new file mode 100644
index 0000000000000000000000000000000000000000..961f17a3c3a361073529d9b85df363e6c8bb2b66
--- /dev/null
+++ b/buildstream/_sourcecache.py
@@ -0,0 +1,95 @@
+#
+#  Copyright (C) 2019 Bloomberg Finance LP
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU Lesser General Public
+#  License as published by the Free Software Foundation; either
+#  version 2 of the License, or (at your option) any later version.
+#
+#  This library 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
+#  Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public
+#  License along with this library. If not, see <http://www.gnu.org/licenses/>.
+#
+#  Authors:
+#        Raoul Hidalgo Charman <raoul.hidalgocharman@codethink.co.uk>
+#
+from ._cas import CASRemoteSpec
+from .storage._casbaseddirectory import CasBasedDirectory
+from ._basecache import BaseCache
+from ._exceptions import CASCacheError, SourceCacheError
+from . import utils
+from ._cachekey import generate_key
+
+
+class SourceCacheSpec(CASRemoteSpec):
+    """Holds configuration for a remote used for the source cache.
+
+    Args:
+        url (str): Location of the remote source cache
+        push (bool): Whether we should attempt to push sources to this cache,
+                     in addition to pulling from it.
+        instance-name (str): Name if any, of instance of server
+    """
+
+
+class SourceCache(BaseCache):
+    """SourceCache()
+
+    Class that keeps config of remotes and deals with caching of sources.
+    """
+
+    spec_class = SourceCacheSpec
+    spec_name = "source_cache_specs"
+    spec_error = SourceCacheError
+    config_node_name = "source-caches"
+
+    def get_source_fullname(self, source):
+        """Get a string for the sources CAS ref"""
+        # Get previous sources
+        return "{}/{}/{}".format(
+            'source',
+            source.get_kind(),
+            source.get_key)
+
+    def contains(self, source):
+        """Checks the local CAS for source"""
+        ref = self.get_source_fullname(source)
+        return self.cas.contains(ref)
+
+    def commit(self, source, previous_sources):
+        """Stages and captures a source into the local CAS"""
+        ref = self.get_source_fullname(source)
+
+        with utils._tempdir(dir=self.context.tmpdir) as tmpdir:
+            for previous_source in previous_sources:
+                previous_source.stage(tmpdir)
+            source.stage(tmpdir)
+            self.cas.commit([ref], tmpdir)
+
+    def export(self, source):
+        """Exports a source in the CAS to a virtual directory
+
+        Args:
+            source (Source): source we want to export
+            previous_sources ([Source]): previous sources
+        Returns:
+            CASBasedDirectory
+        """
+        ref = self.get_source_fullname(source)
+
+        try:
+            digest = self.cas.resolve_ref(ref)
+        except CASCacheError as e:
+            raise SourceCacheError("Error with ref: {}".format(e))
+
+        return CasBasedDirectory(self.cas, ref=digest)
+
+    def fetch(self, source, previous_sources, *, progress=None):
+        """Tries to fetch source from remote CAS's"""
+
+    def push(self, source):
+        """Pushes a source to configured remote CAS's"""
diff --git a/buildstream/_stream.py b/buildstream/_stream.py
index caaa48908c8de45181a117e3950135a3cf091df7..8e4095a828813b9a1afde68ec90ec9257bcf1242 100644
--- a/buildstream/_stream.py
+++ b/buildstream/_stream.py
@@ -616,7 +616,7 @@ class Stream():
                 raise StreamError("Element '{}' already has workspace defined at: {}"
                                   .format(target.name, workspace.get_absolute_path()))
 
-            if not no_checkout and target._get_consistency() != Consistency.CACHED:
+            if not no_checkout and target._get_consistency() != Consistency.STAGED:
                 raise StreamError("Could not stage uncached source. For {} ".format(target.name) +
                                   "Use `--track` to track and " +
                                   "fetch the latest version of the " +
diff --git a/buildstream/element.py b/buildstream/element.py
index 74f97df6f7ff901448e9562529224183f78d8118..7386e3df85f74d828b890e31640e77b3dc405d29 100644
--- a/buildstream/element.py
+++ b/buildstream/element.py
@@ -944,11 +944,16 @@ class Element(Plugin):
         element = meta.project.create_element(meta, first_pass=meta.first_pass)
         cls.__instantiated_elements[meta] = element
 
-        # Instantiate sources
+        # Instantiate sources and generate their keys
+        previous_sources = []
         for meta_source in meta.sources:
             meta_source.first_pass = meta.kind == "junction"
             source = meta.project.create_source(meta_source,
                                                 first_pass=meta.first_pass)
+
+            source._generate_key(previous_sources)
+            previous_sources.append(source)
+
             redundant_ref = source._load_ref()
             element.__sources.append(source)
 
@@ -1068,7 +1073,7 @@ class Element(Plugin):
     #    (bool): Whether this element can currently be built
     #
     def _buildable(self):
-        if self._get_consistency() != Consistency.CACHED:
+        if self._get_consistency() < Consistency.CACHED:
             return False
 
         for dependency in self.dependencies(Scope.BUILD):
@@ -1476,9 +1481,24 @@ class Element(Plugin):
                     detail = "Element type either does not expect a buildtree or it was explictily cached without one."
                     self.warn("WARNING: {} Artifact contains an empty buildtree".format(self.name), detail=detail)
             else:
-                # No workspace or cached buildtree, stage source directly
-                for source in self.sources():
-                    source._stage(temp_staging_directory)
+                # stage last source if that's staged
+                last_source = None
+                for last_source in self.sources():
+                    pass
+                if last_source and last_source._get_consistency() == Consistency.STAGED:
+                    self.info("new fangled staging")
+                    import_dir = self._get_context().sourcecache.export(last_source)
+
+                    # Cant import CasBasedDirectories into FileBasedDirectories
+                    if isinstance(vdirectory, FileBasedDirectory):
+                        import_dir.export_files(temp_staging_directory)
+                        import_dir = temp_staging_directory
+
+                # No workspace, cached buildtree or staged sources, stage source directly
+                else:
+                    self.warn("old fangled staging should no longer be called")
+                    for source in self.sources():
+                        source._stage(temp_staging_directory)
 
             vdirectory.import_files(import_dir)
 
@@ -2091,11 +2111,17 @@ class Element(Plugin):
     #
     def _fetch(self):
         previous_sources = []
+        source = None
         for source in self.sources():
-            if source._get_consistency() < Consistency.CACHED:
+            if source._get_consistency() < Consistency.STAGED:
                 source._fetch(previous_sources)
             previous_sources.append(source)
 
+        # cache the last source
+        if source:
+            previous_sources.pop()
+            source._cache(previous_sources)
+
     # _calculate_cache_key():
     #
     # Calculates the cache key
@@ -2173,11 +2199,16 @@ class Element(Plugin):
         else:
 
             # Determine overall consistency of the element
+            source_consistency = None
             for source in self.__sources:
                 source._update_state()
                 source_consistency = source._get_consistency()
                 self.__consistency = min(self.__consistency, source_consistency)
 
+            # If the last source consistency is staged it's all good
+            if source_consistency == Consistency.STAGED:
+                    self.__consistency = Consistency.STAGED
+
     # __can_build_incrementally()
     #
     # Check if the element can be built incrementally, this
diff --git a/buildstream/source.py b/buildstream/source.py
index 97995a8da8a307f37c531f47949980f33b7b8ef3..94044123980ae6eb1169d626469afb2d05b26490 100644
--- a/buildstream/source.py
+++ b/buildstream/source.py
@@ -170,6 +170,10 @@ from . import _yaml, utils
 from ._exceptions import BstError, ImplError, ErrorDomain
 from ._loader.metasource import MetaSource
 from ._projectrefs import ProjectRefStorage
+from .storage._casbaseddirectory import CasBasedDirectory
+from ._cachekey import generate_key
+
+from ._message import Message, MessageType
 
 
 class SourceError(BstError):
@@ -289,6 +293,9 @@ class Source(Plugin):
         super().__init__("{}-{}".format(meta.element_name, meta.element_index),
                          context, project, provenance, "source")
 
+        self.__source_cache = context.sourcecache
+        self.__key = None  # Cache key used for the source cache
+
         self.__element_name = meta.element_name         # The name of the element owning this source
         self.__element_index = meta.element_index       # The index of the source in the owning element's source list
         self.__element_kind = meta.element_kind         # The kind of the element owning this source
@@ -461,7 +468,7 @@ class Source(Plugin):
         Implementors should raise :class:`.SourceError` when encountering
         some system error.
         """
-        self.stage(directory)
+        self._stage(directory)
 
     def get_source_fetchers(self):
         """Get the objects that are used for fetching
@@ -667,7 +674,7 @@ class Source(Plugin):
     #
     def _update_state(self):
 
-        if self.__consistency < Consistency.CACHED:
+        if self.__consistency < Consistency.STAGED:
 
             # Source consistency interrogations are silent.
             context = self._get_context()
@@ -679,6 +686,10 @@ class Source(Plugin):
                 if self.__consistency == Consistency.CACHED:
                     self.validate_cache()
 
+                if (self.__consistency < Consistency.STAGED and
+                        self.__source_cache.contains(self)):
+                    self.__consistency = Consistency.STAGED
+
     # Return cached consistency
     #
     def _get_consistency(self):
@@ -688,8 +699,14 @@ class Source(Plugin):
     #
     # Args:
     #   previous_sources (list): List of Sources listed prior to this source
+    #   fetch_original (bool): whether to fetch full source, or use local CAS
     #
-    def _fetch(self, previous_sources):
+    def _fetch(self, previous_sources, fetch_original=False):
+
+        # return if we've got the source
+        if self.__source_cache.contains(self) and fetch_original is False:
+            self._get_context().message(Message(None, MessageType.INFO, "source cached"))
+            return
 
         if self.BST_REQUIRES_PREVIOUS_SOURCES_FETCH:
             self.__ensure_previous_sources(previous_sources)
@@ -700,6 +717,11 @@ class Source(Plugin):
         else:
             self.__do_fetch()
 
+    def _cache(self, previous_sources):
+        # stage the source into the source cache
+        self.info("Commiting to source cache")
+        self.__source_cache.commit(self, previous_sources)
+
     # Wrapper for stage() api which gives the source
     # plugin a fully constructed path considering the
     # 'directory' option
@@ -707,7 +729,8 @@ class Source(Plugin):
     def _stage(self, directory):
         staging_directory = self.__ensure_directory(directory)
 
-        self.stage(staging_directory)
+        cdir = self.__source_cache.export(self)
+        cdir.export_file(staging_directory)
 
     # Wrapper for init_workspace()
     def _init_workspace(self, directory):
@@ -956,6 +979,18 @@ class Source(Plugin):
         else:
             return None
 
+    def _generate_key(self, previous_sources):
+        keys = [self._get_unique_key(True)]
+
+        for previous_source in previous_sources:
+            keys.append(previous_sources._get_unique_key(True))
+
+        self.__key = generate_key(keys)
+
+    @property
+    def get_key(self):
+        return self.__key
+
     #############################################################
     #                   Local Private Methods                   #
     #############################################################
diff --git a/buildstream/types.py b/buildstream/types.py
index 23d78b08cdde67716e508376ef096eed61bdbb8f..8955394c82aa80b779b63000facf08c2ea33271d 100644
--- a/buildstream/types.py
+++ b/buildstream/types.py
@@ -80,6 +80,13 @@ class Consistency():
     source cache. Only cached sources can be staged.
     """
 
+    STAGED = 3
+    """STAGED
+
+    Sources are staged in the local CAS, but are not present unstaged in the
+    source cache.
+    """
+
 
 class CoreWarnings():
     """CoreWarnings()
diff --git a/tests/sourcecache/__init__.py b/tests/sourcecache/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tests/sourcecache/config.py b/tests/sourcecache/config.py
new file mode 100644
index 0000000000000000000000000000000000000000..8f70b537407ba296e5e57a77fe87465805a329f0
--- /dev/null
+++ b/tests/sourcecache/config.py
@@ -0,0 +1,57 @@
+#
+#  Copyright (C) 2019 Bloomberg Finance L.P.
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU Lesser General Public
+#  License as published by the Free Software Foundation; either
+#  version 2 of the License, or (at your option) any later version.
+#
+#  This library 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
+#  Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public
+#  License along with this library. If not, see <http://www.gnu.org/licenses/>.
+#
+#  Authors:
+#        Raoul Hidalgo Chraman <raoul.hidalgocharman@codethink.co.uk>
+import os
+import pytest
+
+from buildstream import _yaml
+from buildstream._exceptions import ErrorDomain, LoadErrorReason
+
+from buildstream.plugintestutils.runcli import cli
+
+DATA_DIR = os.path.dirname(os.path.realpath(__file__))
+
+
+# Assert that if either the client key or client cert is specified
+# without specifying its counterpart, we get a comprehensive LoadError
+# instead of an unhandled exception.
+@pytest.mark.datafiles(DATA_DIR)
+@pytest.mark.parametrize('config_key, config_value', [
+    ('client-cert', 'client.crt'),
+    ('client-key', 'client.key')
+])
+def test_missing_certs(cli, datafiles, config_key, config_value):
+    project = os.path.join(datafiles.dirname, datafiles.basename, 'missing-certs')
+
+    project_conf = {
+        'name': 'test',
+
+        'source-caches': {
+            'url': 'https://cache.example.com:12345',
+            'push': 'true',
+            config_key: config_value
+        }
+    }
+    project_conf_file = os.path.join(project, 'project.conf')
+    _yaml.dump(project_conf, project_conf_file)
+
+    # Use `pull` here to ensure we try to initialize the remotes, triggering the error
+    #
+    # This does not happen for a simple `bst show`.
+    result = cli.run(project=project, args=['source', 'fetch', 'element.bst'])
+    result.assert_main_error(ErrorDomain.LOAD, LoadErrorReason.INVALID_DATA)
diff --git a/tests/sourcecache/consistency.py b/tests/sourcecache/consistency.py
new file mode 100644
index 0000000000000000000000000000000000000000..19bd7e5f5bedbc35863f6ad1795a7b0f272cdd14
--- /dev/null
+++ b/tests/sourcecache/consistency.py
@@ -0,0 +1,76 @@
+#
+#  Copyright (C) 2019 Bloomberg Finance LP
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU Lesser General Public
+#  License as published by the Free Software Foundation; either
+#  version 2 of the License, or (at your option) any later version.
+#
+#  This library 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
+#  Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public
+#  License along with this library. If not, see <http://www.gnu.org/licenses/>.
+#
+#  Authors:
+#        Raoul Hidalgo Charman <raoul.hidalgocharman@codethink.co.uk>
+#
+import filecmp
+import os
+import pytest
+
+from buildstream._context import Context
+from buildstream._project import Project
+from buildstream.source import Source
+from buildstream import _yaml
+
+from buildstream.plugintestutils.runcli import cli
+from tests.testutils.artifactshare import create_artifact_share
+
+DATA_DIR = os.path.dirname(os.path.realpath(__file__))
+
+
+def dummy_message_handler(message, context):
+    pass
+
+
+@pytest.mark.datafiles(DATA_DIR)
+def test_source_staged(tmpdir, cli, datafiles):
+    project_dir = os.path.join(datafiles.dirname, datafiles.basename, 'project')
+    casdir = os.path.join(datafiles.dirname, datafiles.basename, 'cache', 'cas')
+    cachedir = os.path.join(str(tmpdir), 'cache')
+
+    cli.configure({
+        'cachedir': cachedir
+    })
+
+    # set up minimal context
+    context = Context()
+    context.load()
+
+    # load project and sourcecache
+    project = Project(project_dir, context)
+    project.ensure_fully_loaded()
+    context.cachedir = cachedir
+    context.set_message_handler(dummy_message_handler)
+    sourcecache = context.sourcecache
+    cas = context.get_cascache()
+
+    cli.run(project=project_dir, args=["build", "import-bin.bst"])
+
+    # now check that the source is in the refs file, this is pretty messy but
+    # seems to be the only way to get the sources?
+    source = list(project.load_elements(["import-bin.bst"])[0].sources())[0]
+    assert sourcecache.contains(source)
+
+    # Extract the file and check it's the same as the one we imported
+    refname = sourcecache.get_source_fullname(source)
+    extractdir = os.path.join(str(tmpdir), "extract")
+    extract = cas.extract(refname, extractdir)
+    hellopath = os.path.join("usr", "bin", "hello")
+    file1 = os.path.join(extract, hellopath)
+    file2 = os.path.join(project_dir, "files", "bin-files", hellopath)
+
+    assert filecmp.cmp(file1, file2) is True
diff --git a/tests/sourcecache/missing-certs/certificates/client.crt b/tests/sourcecache/missing-certs/certificates/client.crt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tests/sourcecache/missing-certs/certificates/client.key b/tests/sourcecache/missing-certs/certificates/client.key
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tests/sourcecache/missing-certs/element.bst b/tests/sourcecache/missing-certs/element.bst
new file mode 100644
index 0000000000000000000000000000000000000000..3c29b4ea1334556c03b724ce505bc234e80a1892
--- /dev/null
+++ b/tests/sourcecache/missing-certs/element.bst
@@ -0,0 +1 @@
+kind: autotools
diff --git a/tests/sourcecache/project/elements/compose-all.bst b/tests/sourcecache/project/elements/compose-all.bst
new file mode 100644
index 0000000000000000000000000000000000000000..ba47081b3ce496804ca4a236a6c021451c4c076e
--- /dev/null
+++ b/tests/sourcecache/project/elements/compose-all.bst
@@ -0,0 +1,12 @@
+kind: compose
+
+depends:
+- filename: import-bin.bst
+  type: build
+- filename: import-dev.bst
+  type: build
+
+config:
+  # Dont try running the sandbox, we dont have a
+  # runtime to run anything in this context.
+  integrate: False
diff --git a/tests/sourcecache/project/elements/import-bin.bst b/tests/sourcecache/project/elements/import-bin.bst
new file mode 100644
index 0000000000000000000000000000000000000000..a847c0c23de84a3b792fccf8506cb9cebc5aa1ea
--- /dev/null
+++ b/tests/sourcecache/project/elements/import-bin.bst
@@ -0,0 +1,4 @@
+kind: import
+sources:
+- kind: local
+  path: files/bin-files
diff --git a/tests/sourcecache/project/elements/import-dev.bst b/tests/sourcecache/project/elements/import-dev.bst
new file mode 100644
index 0000000000000000000000000000000000000000..152a54667fe9de84c37971819fbb7fafb5df23c1
--- /dev/null
+++ b/tests/sourcecache/project/elements/import-dev.bst
@@ -0,0 +1,4 @@
+kind: import
+sources:
+- kind: local
+  path: files/dev-files
diff --git a/tests/sourcecache/project/elements/target.bst b/tests/sourcecache/project/elements/target.bst
new file mode 100644
index 0000000000000000000000000000000000000000..ba489f1e856633bd929db86bdac09dfca00f6415
--- /dev/null
+++ b/tests/sourcecache/project/elements/target.bst
@@ -0,0 +1,9 @@
+kind: stack
+description: |
+
+  Main stack target for the bst build test
+
+depends:
+- import-bin.bst
+- import-dev.bst
+- compose-all.bst
diff --git a/tests/sourcecache/project/files/bin-files/usr/bin/hello b/tests/sourcecache/project/files/bin-files/usr/bin/hello
new file mode 100755
index 0000000000000000000000000000000000000000..f534a40837ced35eed6c6079228387302a4c9d65
--- /dev/null
+++ b/tests/sourcecache/project/files/bin-files/usr/bin/hello
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+echo "Hello !"
diff --git a/tests/sourcecache/project/files/dev-files/usr/include/pony.h b/tests/sourcecache/project/files/dev-files/usr/include/pony.h
new file mode 100644
index 0000000000000000000000000000000000000000..40bd0c2e768fbd9238ce3a7f332c245fd950b64f
--- /dev/null
+++ b/tests/sourcecache/project/files/dev-files/usr/include/pony.h
@@ -0,0 +1,12 @@
+#ifndef __PONY_H__
+#define __PONY_H__
+
+#define PONY_BEGIN "Once upon a time, there was a pony."
+#define PONY_END "And they lived happily ever after, the end."
+
+#define MAKE_PONY(story)  \
+  PONY_BEGIN \
+  story \
+  PONY_END
+
+#endif /* __PONY_H__ */
diff --git a/tests/sourcecache/project/project.conf b/tests/sourcecache/project/project.conf
new file mode 100644
index 0000000000000000000000000000000000000000..854e38693f391e65b8f9af10ee35679e4907ccb0
--- /dev/null
+++ b/tests/sourcecache/project/project.conf
@@ -0,0 +1,4 @@
+# Project config for frontend build test
+name: test
+
+element-path: elements
diff --git a/tests/sourcecache/project/project/elements/compose-all.bst b/tests/sourcecache/project/project/elements/compose-all.bst
new file mode 100644
index 0000000000000000000000000000000000000000..ba47081b3ce496804ca4a236a6c021451c4c076e
--- /dev/null
+++ b/tests/sourcecache/project/project/elements/compose-all.bst
@@ -0,0 +1,12 @@
+kind: compose
+
+depends:
+- filename: import-bin.bst
+  type: build
+- filename: import-dev.bst
+  type: build
+
+config:
+  # Dont try running the sandbox, we dont have a
+  # runtime to run anything in this context.
+  integrate: False
diff --git a/tests/sourcecache/project/project/elements/import-bin.bst b/tests/sourcecache/project/project/elements/import-bin.bst
new file mode 100644
index 0000000000000000000000000000000000000000..a847c0c23de84a3b792fccf8506cb9cebc5aa1ea
--- /dev/null
+++ b/tests/sourcecache/project/project/elements/import-bin.bst
@@ -0,0 +1,4 @@
+kind: import
+sources:
+- kind: local
+  path: files/bin-files
diff --git a/tests/sourcecache/project/project/elements/import-dev.bst b/tests/sourcecache/project/project/elements/import-dev.bst
new file mode 100644
index 0000000000000000000000000000000000000000..152a54667fe9de84c37971819fbb7fafb5df23c1
--- /dev/null
+++ b/tests/sourcecache/project/project/elements/import-dev.bst
@@ -0,0 +1,4 @@
+kind: import
+sources:
+- kind: local
+  path: files/dev-files
diff --git a/tests/sourcecache/project/project/elements/target.bst b/tests/sourcecache/project/project/elements/target.bst
new file mode 100644
index 0000000000000000000000000000000000000000..ba489f1e856633bd929db86bdac09dfca00f6415
--- /dev/null
+++ b/tests/sourcecache/project/project/elements/target.bst
@@ -0,0 +1,9 @@
+kind: stack
+description: |
+
+  Main stack target for the bst build test
+
+depends:
+- import-bin.bst
+- import-dev.bst
+- compose-all.bst
diff --git a/tests/sourcecache/project/project/files/bin-files/usr/bin/hello b/tests/sourcecache/project/project/files/bin-files/usr/bin/hello
new file mode 100755
index 0000000000000000000000000000000000000000..f534a40837ced35eed6c6079228387302a4c9d65
--- /dev/null
+++ b/tests/sourcecache/project/project/files/bin-files/usr/bin/hello
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+echo "Hello !"
diff --git a/tests/sourcecache/project/project/files/dev-files/usr/include/pony.h b/tests/sourcecache/project/project/files/dev-files/usr/include/pony.h
new file mode 100644
index 0000000000000000000000000000000000000000..40bd0c2e768fbd9238ce3a7f332c245fd950b64f
--- /dev/null
+++ b/tests/sourcecache/project/project/files/dev-files/usr/include/pony.h
@@ -0,0 +1,12 @@
+#ifndef __PONY_H__
+#define __PONY_H__
+
+#define PONY_BEGIN "Once upon a time, there was a pony."
+#define PONY_END "And they lived happily ever after, the end."
+
+#define MAKE_PONY(story)  \
+  PONY_BEGIN \
+  story \
+  PONY_END
+
+#endif /* __PONY_H__ */
diff --git a/tests/sourcecache/project/project/project.conf b/tests/sourcecache/project/project/project.conf
new file mode 100644
index 0000000000000000000000000000000000000000..854e38693f391e65b8f9af10ee35679e4907ccb0
--- /dev/null
+++ b/tests/sourcecache/project/project/project.conf
@@ -0,0 +1,4 @@
+# Project config for frontend build test
+name: test
+
+element-path: elements