Valentin David pushed to branch valentindavid/cargo_plugin at BuildStream / buildstream
Commits:
-
516e990e
by ctolentino8 at 2018-10-31T11:36:46Z
-
b8a37a63
by Tristan Van Berkom at 2018-11-01T10:16:25Z
-
b27b592a
by Benjamin Schubert at 2018-11-01T10:49:57Z
-
89ace5d7
by Benjamin Schubert at 2018-11-01T11:16:36Z
-
d86daded
by Valentin David at 2018-11-01T11:41:10Z
5 changed files:
- buildstream/_frontend/app.py
- + buildstream/plugins/sources/cargo.py
- doc/source/core_plugins.rst
- setup.py
- tests/frontend/init.py
Changes:
... | ... | @@ -305,7 +305,6 @@ class App(): |
305 | 305 |
directory = self._main_options['directory']
|
306 | 306 |
directory = os.path.abspath(directory)
|
307 | 307 |
project_path = os.path.join(directory, 'project.conf')
|
308 |
- elements_path = os.path.join(directory, element_path)
|
|
309 | 308 |
|
310 | 309 |
try:
|
311 | 310 |
# Abort if the project.conf already exists, unless `--force` was specified in `bst init`
|
... | ... | @@ -335,6 +334,7 @@ class App(): |
335 | 334 |
raise AppError("Error creating project directory {}: {}".format(directory, e)) from e
|
336 | 335 |
|
337 | 336 |
# Create the elements sub-directory if it doesnt exist
|
337 |
+ elements_path = os.path.join(directory, element_path)
|
|
338 | 338 |
try:
|
339 | 339 |
os.makedirs(elements_path, exist_ok=True)
|
340 | 340 |
except IOError as e:
|
1 |
+#
|
|
2 |
+# Copyright (C) 2018 Codethink Limited
|
|
3 |
+#
|
|
4 |
+# This program is free software; you can redistribute it and/or
|
|
5 |
+# modify it under the terms of the GNU Lesser General Public
|
|
6 |
+# License as published by the Free Software Foundation; either
|
|
7 |
+# version 2 of the License, or (at your option) any later version.
|
|
8 |
+#
|
|
9 |
+# This library is distributed in the hope that it will be useful,
|
|
10 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
12 |
+# Lesser General Public License for more details.
|
|
13 |
+#
|
|
14 |
+# You should have received a copy of the GNU Lesser General Public
|
|
15 |
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
16 |
+#
|
|
17 |
+# Authors:
|
|
18 |
+# Valentin David <valentin david codethink co uk>
|
|
19 |
+ |
|
20 |
+"""
|
|
21 |
+cargo - stage files from cargo manifest
|
|
22 |
+=======================================
|
|
23 |
+ |
|
24 |
+`cargo` downloads and stages cargo crates based on a `Cargo.toml`
|
|
25 |
+manifest provided by a previous source.
|
|
26 |
+ |
|
27 |
+`ref` will contain the `Cargo.lock` file. `bst track` should be used
|
|
28 |
+to set it.
|
|
29 |
+ |
|
30 |
+When `keep-lock` is true, tracking will store the current `Cargo.lock`
|
|
31 |
+provided by previous sources. in the `ref`. If `keep-lock` is false or
|
|
32 |
+absent, then `ref` will be created for the latest available crates.
|
|
33 |
+ |
|
34 |
+**Host dependencies:**
|
|
35 |
+ |
|
36 |
+ * cargo
|
|
37 |
+ * cargo-vendor (can be installed with `cargo install cargo-vendor`).
|
|
38 |
+ |
|
39 |
+**Usage:**
|
|
40 |
+ |
|
41 |
+.. code:: yaml
|
|
42 |
+ |
|
43 |
+ # Specify the cargo source kind
|
|
44 |
+ kind: cargo
|
|
45 |
+ |
|
46 |
+ # Optionally give the subdirectory where the `Cargo.toml` manifest
|
|
47 |
+ # can be found.
|
|
48 |
+ subdir: subproject
|
|
49 |
+ |
|
50 |
+ # Optionally disallow rewriting `Cargo.lock`. In this case tracking
|
|
51 |
+ # will just read the existing file. If not used, then tracking
|
|
52 |
+ # will create `Cargo.lock`.
|
|
53 |
+ keep-lock: true
|
|
54 |
+"""
|
|
55 |
+ |
|
56 |
+ |
|
57 |
+import hashlib
|
|
58 |
+import os
|
|
59 |
+import errno
|
|
60 |
+ |
|
61 |
+from buildstream import Consistency, Source, utils, SourceError
|
|
62 |
+ |
|
63 |
+ |
|
64 |
+class CargoSource(Source):
|
|
65 |
+ # pylint: disable=attribute-defined-outside-init
|
|
66 |
+ |
|
67 |
+ BST_REQUIRES_PREVIOUS_SOURCES_TRACK = True
|
|
68 |
+ BST_REQUIRES_PREVIOUS_SOURCES_FETCH = True
|
|
69 |
+ |
|
70 |
+ def configure(self, node):
|
|
71 |
+ self.node_validate(node, ['ref', 'subdir', 'keep-lock'] + Source.COMMON_CONFIG_KEYS)
|
|
72 |
+ self.ref = self.node_get_member(node, str, 'ref', None)
|
|
73 |
+ self.subdir = self.node_get_member(node, str, 'subdir', '.')
|
|
74 |
+ self.keeplock = self.node_get_member(node, bool, 'keep-lock', False)
|
|
75 |
+ self.extra_path = None
|
|
76 |
+ |
|
77 |
+ def preflight(self):
|
|
78 |
+ self.host_cargo = utils.get_host_tool('cargo')
|
|
79 |
+ |
|
80 |
+ try:
|
|
81 |
+ utils.get_host_tool('cargo-vendor')
|
|
82 |
+ except utils.ProgramNotFoundError:
|
|
83 |
+ cargo_home = os.environ.get('CARGO_HOME', os.path.expanduser('~/.cargo'))
|
|
84 |
+ self.extra_path = os.path.join(cargo_home, 'bin')
|
|
85 |
+ |
|
86 |
+ self.call([self.host_cargo, 'vendor', '-V'],
|
|
87 |
+ env=self._environment(),
|
|
88 |
+ fail='Cannot find "cargo vendor". Please install it with "cargo install cargo-vendor".')
|
|
89 |
+ |
|
90 |
+ def get_unique_key(self):
|
|
91 |
+ return [self.subdir, self.ref]
|
|
92 |
+ |
|
93 |
+ def get_ref(self):
|
|
94 |
+ return self.ref
|
|
95 |
+ |
|
96 |
+ def load_ref(self, node):
|
|
97 |
+ self.ref = self.node_get_member(node, str, 'ref', None)
|
|
98 |
+ |
|
99 |
+ def set_ref(self, ref, node):
|
|
100 |
+ node['ref'] = self.ref = ref
|
|
101 |
+ |
|
102 |
+ def _environment(self, *, set_home=False):
|
|
103 |
+ env = {}
|
|
104 |
+ env.update(os.environ)
|
|
105 |
+ if self.extra_path:
|
|
106 |
+ path = env.get('PATH', '').split(':')
|
|
107 |
+ path.append(self.extra_path)
|
|
108 |
+ env['PATH'] = ':'.join(path)
|
|
109 |
+ if set_home:
|
|
110 |
+ home = os.path.join(self.get_mirror_directory(), 'home')
|
|
111 |
+ os.makedirs(home, exist_ok=True)
|
|
112 |
+ env['CARGO_HOME'] = home
|
|
113 |
+ return env
|
|
114 |
+ |
|
115 |
+ def _get_manifest(self, directory):
|
|
116 |
+ projectdir = os.path.join(directory, self.subdir)
|
|
117 |
+ manifest = os.path.join(projectdir, 'Cargo.toml')
|
|
118 |
+ lockfile = os.path.join(projectdir, 'Cargo.lock')
|
|
119 |
+ return manifest, lockfile
|
|
120 |
+ |
|
121 |
+ def track(self, previous_sources_dir):
|
|
122 |
+ manifest, lockfile = self._get_manifest(previous_sources_dir)
|
|
123 |
+ |
|
124 |
+ if not self.keeplock:
|
|
125 |
+ self.call([self.host_cargo, 'generate-lockfile', '--manifest-path', manifest],
|
|
126 |
+ env=self._environment(set_home=True),
|
|
127 |
+ fail="Failed to track cargo packages")
|
|
128 |
+ try:
|
|
129 |
+ with open(lockfile, 'rb') as f:
|
|
130 |
+ lockcontent = f.read().decode('utf-8')
|
|
131 |
+ except OSError as e:
|
|
132 |
+ if self.keeplock and e.errno == errno.ENOENT:
|
|
133 |
+ raise SourceError("{}: Cannot find Cargo.lock".format(self))
|
|
134 |
+ else:
|
|
135 |
+ raise
|
|
136 |
+ |
|
137 |
+ return lockcontent
|
|
138 |
+ |
|
139 |
+ def _get_stamp(self):
|
|
140 |
+ h = hashlib.sha256()
|
|
141 |
+ h.update(self.get_ref().encode('utf-8'))
|
|
142 |
+ return os.path.join(self.get_mirror_directory(), 'stamps', h.hexdigest())
|
|
143 |
+ |
|
144 |
+ def get_consistency(self):
|
|
145 |
+ if not self.ref:
|
|
146 |
+ return Consistency.INCONSISTENT
|
|
147 |
+ if os.path.exists(self._get_stamp()):
|
|
148 |
+ return Consistency.CACHED
|
|
149 |
+ return Consistency.RESOLVED
|
|
150 |
+ |
|
151 |
+ def fetch(self, previous_sources_dir):
|
|
152 |
+ manifest, lockfile = self._get_manifest(previous_sources_dir)
|
|
153 |
+ if not self.keeplock:
|
|
154 |
+ with open(lockfile, 'wb') as f:
|
|
155 |
+ f.write(self.get_ref().encode('utf-8'))
|
|
156 |
+ |
|
157 |
+ self.call([self.host_cargo, 'fetch', '--manifest-path', manifest, '--locked'],
|
|
158 |
+ env=self._environment(set_home=True),
|
|
159 |
+ fail="Failed to fetch cargo packages")
|
|
160 |
+ stamp = self._get_stamp()
|
|
161 |
+ os.makedirs(os.path.dirname(stamp), exist_ok=True)
|
|
162 |
+ with open(stamp, 'w'):
|
|
163 |
+ pass
|
|
164 |
+ |
|
165 |
+ def stage(self, directory):
|
|
166 |
+ manifest, lockfile = self._get_manifest(directory)
|
|
167 |
+ if not self.keeplock:
|
|
168 |
+ with open(lockfile, 'wb') as f:
|
|
169 |
+ f.write(self.ref.encode('utf-8'))
|
|
170 |
+ |
|
171 |
+ config = os.path.join(os.path.dirname(manifest), '.cargo', 'config')
|
|
172 |
+ os.makedirs(os.path.dirname(config), exist_ok=True)
|
|
173 |
+ |
|
174 |
+ vendordir = os.path.join(directory, 'vendor')
|
|
175 |
+ relvendordir = os.path.relpath(vendordir, os.path.dirname(manifest))
|
|
176 |
+ |
|
177 |
+ with utils.save_file_atomic(config, 'wb') as f:
|
|
178 |
+ self.call([self.host_cargo, 'vendor', '--frozen', '--relative-path', relvendordir],
|
|
179 |
+ env=self._environment(set_home=True),
|
|
180 |
+ cwd=os.path.dirname(manifest),
|
|
181 |
+ stdout=f,
|
|
182 |
+ fail="Failed to stage cargo packages")
|
|
183 |
+ |
|
184 |
+ |
|
185 |
+def setup():
|
|
186 |
+ return CargoSource
|
... | ... | @@ -59,6 +59,7 @@ Sources |
59 | 59 |
sources/patch
|
60 | 60 |
sources/deb
|
61 | 61 |
sources/pip
|
62 |
+ sources/cargo
|
|
62 | 63 |
|
63 | 64 |
|
64 | 65 |
External plugins
|
... | ... | @@ -39,6 +39,7 @@ if sys.version_info[0] != REQUIRED_PYTHON_MAJOR or sys.version_info[1] < REQUIRE |
39 | 39 |
try:
|
40 | 40 |
from setuptools import setup, find_packages, Command
|
41 | 41 |
from setuptools.command.easy_install import ScriptWriter
|
42 |
+ from setuptools.command.test import test as TestCommand
|
|
42 | 43 |
except ImportError:
|
43 | 44 |
print("BuildStream requires setuptools in order to build. Install it using"
|
44 | 45 |
" your package manager (usually python3-setuptools) or via pip (pip3"
|
... | ... | @@ -219,9 +220,48 @@ class BuildGRPC(Command): |
219 | 220 |
f.write(code)
|
220 | 221 |
|
221 | 222 |
|
223 |
+#####################################################
|
|
224 |
+# Pytest command #
|
|
225 |
+#####################################################
|
|
226 |
+class PyTest(TestCommand):
|
|
227 |
+ """Defines a pytest command class to run tests from setup.py"""
|
|
228 |
+ |
|
229 |
+ user_options = TestCommand.user_options + [
|
|
230 |
+ ("addopts=", None, "Arguments to pass to pytest"),
|
|
231 |
+ ('index-url=''build_grpc': BuildGRPC,
|
|
264 |
+ 'pytest': PyTest,
|
|
225 | 265 |
}
|
226 | 266 |
cmdclass.update(versioneer.get_cmdclass())
|
227 | 267 |
return cmdclass
|
... | ... | @@ -305,6 +345,5 @@ setup(name='BuildStream', |
305 | 345 |
'grpcio >= 1.10',
|
306 | 346 |
],
|
307 | 347 |
entry_points=bst_install_entry_points,
|
308 |
- setup_requires=['pytest-runner'],
|
|
309 | 348 |
tests_require=dev_requires,
|
310 | 349 |
zip_safe=False)
|
... | ... | @@ -3,6 +3,7 @@ import pytest |
3 | 3 |
from tests.testutils import cli
|
4 | 4 |
|
5 | 5 |
from buildstream import _yaml
|
6 |
+from buildstream._frontend.app import App
|
|
6 | 7 |
from buildstream._exceptions import ErrorDomain, LoadErrorReason
|
7 | 8 |
from buildstream._versions import BST_FORMAT_VERSION
|
8 | 9 |
|
... | ... | @@ -98,3 +99,34 @@ def test_bad_element_path(cli, tmpdir, element_path): |
98 | 99 |
'init', '--project-name', 'foo', '--element-path', element_path
|
99 | 100 |
])
|
100 | 101 |
result.assert_main_error(ErrorDomain.APP, 'invalid-element-path')
|
102 |
+ |
|
103 |
+ |
|
104 |
+@pytest.mark.parametrize("element_path", [('foo'), ('foo/bar')])
|
|
105 |
+def test_element_path_interactive(cli, tmp_path, monkeypatch, element_path):
|
|
106 |
+ project = tmp_path
|
|
107 |
+ project_conf_path = project.joinpath('project.conf')
|
|
108 |
+ |
|
109 |
+ class DummyInteractiveApp(App):
|
|
110 |
+ def __init__(self, *args, **kwargs):
|
|
111 |
+ super().__init__(*args, **kwargs)
|
|
112 |
+ self.interactive = True
|
|
113 |
+ |
|
114 |
+ @classmethod
|
|
115 |
+ def create(cls, *args, **kwargs):
|
|
116 |
+ return DummyInteractiveApp(*args, **kwargs)
|
|
117 |
+ |
|
118 |
+ def _init_project_interactive(self, *args, **kwargs):
|
|
119 |
+ return ('project_name', '0', element_path)
|
|
120 |
+ |
|
121 |
+ monkeypatch.setattr(App, 'create', DummyInteractiveApp.create)
|
|
122 |
+ |
|
123 |
+ result = cli.run(project=str(project), args=['init'])
|
|
124 |
+ result.assert_success()
|
|
125 |
+ |
|
126 |
+ full_element_path = project.joinpath(element_path)
|
|
127 |
+ assert full_element_path.exists()
|
|
128 |
+ |
|
129 |
+ project_conf = _yaml.load(str(project_conf_path))
|
|
130 |
+ assert project_conf['name'] == 'project_name'
|
|
131 |
+ assert project_conf['format-version'] == '0'
|
|
132 |
+ assert project_conf['element-path'] == element_path
|