diff options
author | Valentin David <valentin.david@codethink.co.uk> | 2018-09-12 22:13:34 +0200 |
---|---|---|
committer | Valentin David <valentin.david@codethink.co.uk> | 2018-11-01 12:41:10 +0100 |
commit | d86daded81f2e5cc39d5811601e65fbff3f85c69 (patch) | |
tree | bc1e780910f2afc5c87e08e385103ae914dd44ad | |
parent | 89ace5d74de5f01dc7164fc30dae5e666bf3b592 (diff) | |
download | buildstream-d86daded81f2e5cc39d5811601e65fbff3f85c69.tar.gz |
Add source plugin for cargo.valentindavid/cargo_plugin
Fixes #123.
-rw-r--r-- | buildstream/plugins/sources/cargo.py | 186 | ||||
-rw-r--r-- | doc/source/core_plugins.rst | 1 |
2 files changed, 187 insertions, 0 deletions
diff --git a/buildstream/plugins/sources/cargo.py b/buildstream/plugins/sources/cargo.py new file mode 100644 index 000000000..1075e7fb0 --- /dev/null +++ b/buildstream/plugins/sources/cargo.py @@ -0,0 +1,186 @@ +# +# Copyright (C) 2018 Codethink Limited +# +# 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: +# Valentin David <valentin.david@codethink.co.uk> + +""" +cargo - stage files from cargo manifest +======================================= + +`cargo` downloads and stages cargo crates based on a `Cargo.toml` +manifest provided by a previous source. + +`ref` will contain the `Cargo.lock` file. `bst track` should be used +to set it. + +When `keep-lock` is true, tracking will store the current `Cargo.lock` +provided by previous sources. in the `ref`. If `keep-lock` is false or +absent, then `ref` will be created for the latest available crates. + +**Host dependencies:** + + * cargo + * cargo-vendor (can be installed with `cargo install cargo-vendor`). + +**Usage:** + +.. code:: yaml + + # Specify the cargo source kind + kind: cargo + + # Optionally give the subdirectory where the `Cargo.toml` manifest + # can be found. + subdir: subproject + + # Optionally disallow rewriting `Cargo.lock`. In this case tracking + # will just read the existing file. If not used, then tracking + # will create `Cargo.lock`. + keep-lock: true +""" + + +import hashlib +import os +import errno + +from buildstream import Consistency, Source, utils, SourceError + + +class CargoSource(Source): + # pylint: disable=attribute-defined-outside-init + + BST_REQUIRES_PREVIOUS_SOURCES_TRACK = True + BST_REQUIRES_PREVIOUS_SOURCES_FETCH = True + + def configure(self, node): + self.node_validate(node, ['ref', 'subdir', 'keep-lock'] + Source.COMMON_CONFIG_KEYS) + self.ref = self.node_get_member(node, str, 'ref', None) + self.subdir = self.node_get_member(node, str, 'subdir', '.') + self.keeplock = self.node_get_member(node, bool, 'keep-lock', False) + self.extra_path = None + + def preflight(self): + self.host_cargo = utils.get_host_tool('cargo') + + try: + utils.get_host_tool('cargo-vendor') + except utils.ProgramNotFoundError: + cargo_home = os.environ.get('CARGO_HOME', os.path.expanduser('~/.cargo')) + self.extra_path = os.path.join(cargo_home, 'bin') + + self.call([self.host_cargo, 'vendor', '-V'], + env=self._environment(), + fail='Cannot find "cargo vendor". Please install it with "cargo install cargo-vendor".') + + def get_unique_key(self): + return [self.subdir, self.ref] + + def get_ref(self): + return self.ref + + def load_ref(self, node): + self.ref = self.node_get_member(node, str, 'ref', None) + + def set_ref(self, ref, node): + node['ref'] = self.ref = ref + + def _environment(self, *, set_home=False): + env = {} + env.update(os.environ) + if self.extra_path: + path = env.get('PATH', '').split(':') + path.append(self.extra_path) + env['PATH'] = ':'.join(path) + if set_home: + home = os.path.join(self.get_mirror_directory(), 'home') + os.makedirs(home, exist_ok=True) + env['CARGO_HOME'] = home + return env + + def _get_manifest(self, directory): + projectdir = os.path.join(directory, self.subdir) + manifest = os.path.join(projectdir, 'Cargo.toml') + lockfile = os.path.join(projectdir, 'Cargo.lock') + return manifest, lockfile + + def track(self, previous_sources_dir): + manifest, lockfile = self._get_manifest(previous_sources_dir) + + if not self.keeplock: + self.call([self.host_cargo, 'generate-lockfile', '--manifest-path', manifest], + env=self._environment(set_home=True), + fail="Failed to track cargo packages") + try: + with open(lockfile, 'rb') as f: + lockcontent = f.read().decode('utf-8') + except OSError as e: + if self.keeplock and e.errno == errno.ENOENT: + raise SourceError("{}: Cannot find Cargo.lock".format(self)) + else: + raise + + return lockcontent + + def _get_stamp(self): + h = hashlib.sha256() + h.update(self.get_ref().encode('utf-8')) + return os.path.join(self.get_mirror_directory(), 'stamps', h.hexdigest()) + + def get_consistency(self): + if not self.ref: + return Consistency.INCONSISTENT + if os.path.exists(self._get_stamp()): + return Consistency.CACHED + return Consistency.RESOLVED + + def fetch(self, previous_sources_dir): + manifest, lockfile = self._get_manifest(previous_sources_dir) + if not self.keeplock: + with open(lockfile, 'wb') as f: + f.write(self.get_ref().encode('utf-8')) + + self.call([self.host_cargo, 'fetch', '--manifest-path', manifest, '--locked'], + env=self._environment(set_home=True), + fail="Failed to fetch cargo packages") + stamp = self._get_stamp() + os.makedirs(os.path.dirname(stamp), exist_ok=True) + with open(stamp, 'w'): + pass + + def stage(self, directory): + manifest, lockfile = self._get_manifest(directory) + if not self.keeplock: + with open(lockfile, 'wb') as f: + f.write(self.ref.encode('utf-8')) + + config = os.path.join(os.path.dirname(manifest), '.cargo', 'config') + os.makedirs(os.path.dirname(config), exist_ok=True) + + vendordir = os.path.join(directory, 'vendor') + relvendordir = os.path.relpath(vendordir, os.path.dirname(manifest)) + + with utils.save_file_atomic(config, 'wb') as f: + self.call([self.host_cargo, 'vendor', '--frozen', '--relative-path', relvendordir], + env=self._environment(set_home=True), + cwd=os.path.dirname(manifest), + stdout=f, + fail="Failed to stage cargo packages") + + +def setup(): + return CargoSource diff --git a/doc/source/core_plugins.rst b/doc/source/core_plugins.rst index c82ffe52f..241a03569 100644 --- a/doc/source/core_plugins.rst +++ b/doc/source/core_plugins.rst @@ -59,6 +59,7 @@ Sources sources/patch sources/deb sources/pip + sources/cargo External plugins |