Skip to content

Commit

Permalink
Add new plugin: rust (#579)
Browse files Browse the repository at this point in the history
LP: #1606812
  • Loading branch information
mariogrip authored and sergiusens committed Jul 27, 2016
1 parent c20779f commit 24f1def
Show file tree
Hide file tree
Showing 12 changed files with 270 additions and 8 deletions.
2 changes: 2 additions & 0 deletions .bzrignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ dist
htmlcov
__pycache__
docs/**.html
Cargo.lock
target
*.swp
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ env:
matrix:
- TEST_SUITE=static DEPENDENCIES="apt install -y python3-flake8 python3-mccabe"
- TEST_SUITE=unit DEPENDENCIES="apt build-dep -y ./ && apt install -y python3-coverage"
- TEST_SUITE=integration DEPENDENCIES="apt build-dep -y ./ && apt install -y bzr git mercurial python3-pexpect subversion sudo"
- TEST_SUITE=integration DEPENDENCIES="apt build-dep -y ./ && apt install -y bzr git mercurial python3-pexpect subversion sudo curl"

install:
- sudo apt-get -qq update
Expand Down
2 changes: 2 additions & 0 deletions debian/tests/control
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ Restrictions: allow-stderr, isolation-container, rw-build-tree
Depends: @,
bzr,
git,
curl,
mercurial,
subversion,
lxd,
Expand All @@ -19,6 +20,7 @@ Tests: snapstests
Restrictions: allow-stderr, isolation-container, rw-build-tree
Depends: @,
git,
curl,
python3-fixtures,
python3-pexpect,
python3-testscenarios,
Expand Down
6 changes: 6 additions & 0 deletions integration_tests/snaps/simple-rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "simple-rust"
version = "0.1.0"
authors = ["Marius Gripsgard <mariogrip@ubuntu.com>"]

[dependencies]
10 changes: 10 additions & 0 deletions integration_tests/snaps/simple-rust/snapcraft.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: test-package
version: 0.1
summary: A simple rust project.
description: A simple rust project.
confinement: strict

parts:
simple-rust:
plugin: rust
source: .
3 changes: 3 additions & 0 deletions integration_tests/snaps/simple-rust/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
println!("There is rust on snaps!");
}
25 changes: 25 additions & 0 deletions integration_tests/test_rust_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright (C) 2016 Marius Gripsgard (mariogrip@ubuntu.com)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
# published by the Free Software Foundation.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import os, integration_tests

class RustPluginTestCase(integration_tests.TestCase):

def test_stage_rust_plugin(self):
project_dir = 'simple-rust'
self.run_snapcraft('stage', project_dir)

binary_output = self.get_output_ignoring_non_zero_exit(
os.path.join('stage', 'bin', 'simple-rust'), cwd=project_dir)
self.assertEqual("There is rust on snaps!\n", binary_output)
17 changes: 15 additions & 2 deletions snapcraft/internal/sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@

import logging
import os
import stat
import os.path
import requests
import shutil
Expand Down Expand Up @@ -108,9 +109,21 @@ def download(self):
request = requests.get(self.source, stream=True, allow_redirects=True)
request.raise_for_status()

file_path = os.path.join(
self.file = os.path.join(
self.source_dir, os.path.basename(self.source))
download_requests_stream(request, file_path)
download_requests_stream(request, self.file)


class Script(FileBase):

def __init__(self, source, source_dir, source_tag=None,
source_branch=None):
super().__init__(source, source_dir, source_tag, source_branch)

def download(self):
super().download()
st = os.stat(self.file)
os.chmod(self.file, st.st_mode | stat.S_IEXEC)


class Bazaar(Base):
Expand Down
106 changes: 106 additions & 0 deletions snapcraft/plugins/rust.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Copyright (C) 2016 Marius Gripsgard (mariogrip@ubuntu.com)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
# published by the Free Software Foundation.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""This rust plugin is useful for building rust based parts.
Rust uses cargo to drive the build.
This plugin uses the common plugin keywords as well as those for "sources".
For more information check the 'plugins' topic for the former and the
'sources' topic for the latter.
Additionally, this plugin uses the following plugin-specific keywords:
- rust-channel
(string)
select rust channel (stable, beta, nightly)
- rust-revision
(string)
select rust version
"""

import os
import shutil

import snapcraft
from snapcraft import sources

_RUSTUP = "https://static.rust-lang.org/rustup.sh"


class RustPlugin(snapcraft.BasePlugin):

@classmethod
def schema(cls):
schema = super().schema()
schema['properties']['rust-channel'] = {
'type': 'string',
}
schema['properties']['rust-revision'] = {
'type': 'string',
}
return schema

def __init__(self, name, options, project):
super().__init__(name, options, project)
self._rustpath = os.path.join(self.partdir, "rust")
self._rustc = os.path.join(self._rustpath, "bin", "rustc")
self._rustdoc = os.path.join(self._rustpath, "bin", "rustdoc")
self._cargo = os.path.join(self._rustpath, "bin", "cargo")
self._rustlib = os.path.join(self._rustpath, "lib")
self._rustup_get = sources.Script(_RUSTUP, self._rustpath)
self._rustup = os.path.join(self._rustpath, "rustup.sh")

def build(self):
super().build()
self.run([self._cargo, "install",
"-j{}".format(self.project.parallel_build_count),
"--root", self.installdir], env=self._build_env())

def _build_env(self):
env = os.environ.copy()
env.update({"RUSTC": self._rustc,
"RUSTDOC": self._rustdoc,
"RUST_PATH": self._rustlib})
return env

def pull(self):
super().pull()
self._fetch_rust()

def clean_pull(self):
super().clean_pull()

# Remove the rust path (if any)
if os.path.exists(self._rustpath):
shutil.rmtree(self._rustpath)

def _fetch_rust(self):
options = []

if self.options.rust_revision:
options.append("--revision=%s" % self.options.rust_revision)

if self.options.rust_channel:
if self.options.rust_channel in ["stable", "beta", "nightly"]:
options.append("--channel=%s" % self.options.rust_channel)
else:
raise EnvironmentError("%s is not a valid rust channel"
% self.options.rust_channel)
if not os.path.exists(self._rustpath):
os.makedirs(self._rustpath)
self._rustup_get.download()
self.run(["%s" % self._rustup,
"--prefix=%s" % self._rustpath,
"--disable-sudo", "--save"])
2 changes: 2 additions & 0 deletions snapcraft/sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.


from snapcraft.internal.sources import Script # noqa
from snapcraft.internal.sources import Bazaar # noqa
from snapcraft.internal.sources import Git # noqa
from snapcraft.internal.sources import Mercurial # noqa
Expand Down
10 changes: 5 additions & 5 deletions snapcraft/tests/test_commands_list_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ class ListPluginsCommandTestCase(tests.TestCase):
# plugin list when wrapper at MAX_CHARACTERS_WRAP
default_plugin_output = (
'ant catkin copy gradle jdk kernel maven '
'nodejs python2 qmake tar-content\n'
'nodejs python2 qmake scons \n'
'autotools cmake go gulp kbuild make nil '
'plainbox-provider python3 scons\n')
'plainbox-provider python3 rust tar-content\n')

def test_list_plugins_non_tty(self):
fake_terminal = fixture_setup.FakeTerminal(isatty=False)
Expand All @@ -47,11 +47,11 @@ def test_list_plugins_small_terminal(self):
self.useFixture(fake_terminal)

expected_output = (
'ant go kernel plainbox-provider tar-content\n'
'autotools gradle make python2 \n'
'ant go kernel plainbox-provider scons \n'
'autotools gradle make python2 tar-content\n'
'catkin gulp maven python3 \n'
'cmake jdk nil qmake \n'
'copy kbuild nodejs scons \n')
'copy kbuild nodejs rust \n')

main(['list-plugins'])
self.assertEqual(fake_terminal.getvalue(), expected_output)
93 changes: 93 additions & 0 deletions snapcraft/tests/test_plugin_rust.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Copyright (C) 2016 Marius Gripsgard (mariogrip@ubuntu.com)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
# published by the Free Software Foundation.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import os

from unittest import mock

import snapcraft
from snapcraft import tests
from snapcraft.plugins import rust


class RustPluginTestCase(tests.TestCase):

def setUp(self):
super().setUp()

class Options:
makefile = None
make_parameters = []

self.options = Options()
self.project_options = snapcraft.ProjectOptions()

def test_schema(self):
schema = rust.RustPlugin.schema()

properties = schema['properties']
self.assertTrue('rust-channel' in properties,
'Expected "rust-channel" to be included in properties')
self.assertTrue('rust-revision' in properties,
'Expected "rust-revision to be included in properties')

rust_channel = properties['rust-channel']
self.assertTrue('type' in rust_channel,
'Expected "type" to be included in "rust-channel"')

rust_channel_type = rust_channel['type']
self.assertEqual(rust_channel_type, 'string',
'Expected "rust-channel" "type" to be "string", '
'but it was "{}"'.format(rust_channel_type))

rust_revision = properties['rust-revision']
self.assertTrue('type' in rust_revision,
'Expected "type" to be included in "rust-revision"')

rust_revision_type = rust_revision['type']
self.assertEqual(rust_revision_type, 'string',
'Expected "rust-revision" "type" to be "string", '
'but it was "{}"'.format(rust_revision_type))

@mock.patch.object(rust.RustPlugin, 'run')
def test_build(self, run_mock):
plugin = rust.RustPlugin('test-part', self.options,
self.project_options)
os.makedirs(plugin.sourcedir)

plugin.build()

self.assertEqual(1, run_mock.call_count)
run_mock.assert_has_calls([
mock.call([plugin._cargo, 'install',
'-j{}'.format(plugin.project.parallel_build_count),
'--root', plugin.installdir], env=plugin._build_env())
])

@mock.patch.object(rust.RustPlugin, 'run')
def test_pull(self, run_mock):
plugin = rust.RustPlugin('test-part', self.options,
self.project_options)
os.makedirs(plugin.sourcedir)
plugin.options.rust_revision = []
plugin.options.rust_channel = []

plugin.pull()

self.assertEqual(1, run_mock.call_count)
run_mock.assert_has_calls([
mock.call(["%s" % plugin._rustup,
"--prefix=%s" % plugin._rustpath,
"--disable-sudo", "--save"])
])

0 comments on commit 24f1def

Please sign in to comment.