Permalink
Browse files

cli: stage command migrated to docopts

This also introduces recursive lifecycle execution to properly
handle dependencies.

Signed-off-by: Sergio Schvezov <sergio.schvezov@canonical.com>
  • Loading branch information...
1 parent 02c1d08 commit f4c5430b17bc7860b0c976d6cbfbea2860db52df @sergiusens committed Dec 3, 2015
Showing with 170 additions and 129 deletions.
  1. +0 −58 snapcraft/cmds.py
  2. +39 −0 snapcraft/commands/stage.py
  3. +48 −0 snapcraft/lifecycle.py
  4. +0 −46 snapcraft/tests/test_cmds.py
  5. +73 −20 snapcraft/tests/test_lifecycle.py
  6. +10 −5 snapcraft/yaml.py
View
@@ -1,58 +0,0 @@
-# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
-#
-# Copyright (C) 2015 Canonical Ltd
-#
-# 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 filecmp
-import logging
-import os
-
-logger = logging.getLogger(__name__)
-
-
-def _check_for_collisions(parts):
- parts_files = {}
- for part in parts:
- # Gather our own files up
- part_files, _ = part.migratable_fileset_for('stage')
-
- # Scan previous parts for collisions
- for other_part_name in parts_files:
- common = part_files & parts_files[other_part_name]['files']
- conflict_files = []
- for f in common:
- this = os.path.join(part.installdir, f)
- other = os.path.join(
- parts_files[other_part_name]['installdir'],
- f)
- if os.path.islink(this) and os.path.islink(other):
- continue
- if not filecmp.cmp(this, other, shallow=False):
- conflict_files.append(f)
-
- if conflict_files:
- logger.error('Error: parts %s and %s have the following file '
- 'paths in common which have different '
- 'contents:\n %s',
- other_part_name,
- part.name,
- '\n '.join(sorted(conflict_files)))
-
- return False
-
- # And add our files to the list
- parts_files[part.name] = {'files': part_files,
- 'installdir': part.installdir}
-
- return True
@@ -0,0 +1,39 @@
+# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
+#
+# Copyright (C) 2015 Canonical Ltd
+#
+# 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/>.
+
+"""
+snapcraft stage
+
+Stage parts.
+
+Usage:
+ stage [options] [PART ...]
+
+Options:
+ -h --help show this help message and exit.
+
+"""
+
+from docopt import docopt
+
+from snapcraft import lifecycle
+
+
+def main(argv=None):
+ argv = argv if argv else []
+ args = docopt(__doc__, argv=argv)
+
+ lifecycle.execute('stage', args['PART'])
View
@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import contextlib
+import filecmp
import glob
import importlib
import logging
@@ -48,15 +49,30 @@ def execute(step, part_names=None):
parts = {p for p in config.all_parts if p.name in part_names}
else:
parts = config.all_parts
+ part_names = config.part_names
step_index = common.COMMAND_ORDER.index(step) + 1
for step in common.COMMAND_ORDER[0:step_index]:
+ if step == 'stage':
+ _check_for_collisions(config.all_parts)
for part in parts:
+ prereqs = config.part_prereqs(part.name)
+ if prereqs and not prereqs.issubset(part_names):
+ raise RuntimeError(
+ 'Requested {!r} of {!r} but there are prerequisites: '
@chipaca

chipaca Dec 3, 2015

unsatisfied prerequisites?

+ '{!r}'.format(step, part.name, ' '.join(prereqs)))
+ elif prereqs:
+ # prerequisites need to build all the way to the staging
+ # step to be able to share the common assets that make them
+ # a dependency.
+ execute('stage', prereqs)
@chipaca

chipaca Dec 3, 2015

do you check that you haven't set yourself as a prerequisite?

@sergiusens

sergiusens Dec 4, 2015

Owner

should be rather impossible. (config is considered sane). But as always, I can check just in case of bugs

common.env = config.build_env_for_part(part)
if step == 'pull':
part.pull()
elif step == 'build':
part.build()
+ elif step == 'stage':
+ part.stage()
class PluginError(Exception):
@@ -420,3 +436,35 @@ def _validate_relative_paths(files):
for d in files:
if os.path.isabs(d):
raise PluginError('path "{}" must be relative'.format(d))
+
+
+def _check_for_collisions(parts):
+ parts_files = {}
+ for part in parts:
+ # Gather our own files up
+ part_files, _ = part.migratable_fileset_for('stage')
+
+ # Scan previous parts for collisions
+ for other_part_name in parts_files:
+ common = part_files & parts_files[other_part_name]['files']
+ conflict_files = []
+ for f in common:
+ this = os.path.join(part.installdir, f)
+ other = os.path.join(
+ parts_files[other_part_name]['installdir'],
+ f)
+ if os.path.islink(this) and os.path.islink(other):
+ continue
+ if not filecmp.cmp(this, other, shallow=False):
+ conflict_files.append(f)
+
+ if conflict_files:
+ raise EnvironmentError(
+ 'Parts {!r} and {!r} have the following file paths in '
+ 'common which have different contents:\n{}'.format(
+ other_part_name, part.name,
+ '\n'.join(sorted(conflict_files))))
+
+ # And add our files to the list
+ parts_files[part.name] = {'files': part_files,
+ 'installdir': part.installdir}
@@ -16,16 +16,13 @@
import logging
import os
-import tempfile
import fixtures
import snapcraft.yaml
from snapcraft import (
- cmds,
common,
- lifecycle,
tests
)
@@ -37,49 +34,6 @@ def setUp(self):
common.set_schemadir(os.path.join(__file__,
'..', '..', '..', 'schema'))
- def test_check_for_collisions(self):
- fake_logger = fixtures.FakeLogger(level=logging.ERROR)
- self.useFixture(fake_logger)
-
- tmpdirObject = tempfile.TemporaryDirectory()
- self.addCleanup(tmpdirObject.cleanup)
- tmpdir = tmpdirObject.name
-
- part1 = lifecycle.load_plugin('part1', 'jdk', {'source': '.'})
- part1.code.installdir = tmpdir + '/install1'
- os.makedirs(part1.installdir + '/a')
- open(part1.installdir + '/a/1', mode='w').close()
-
- part2 = lifecycle.load_plugin('part2', 'jdk', {'source': '.'})
- part2.code.installdir = tmpdir + '/install2'
- os.makedirs(part2.installdir + '/a')
- with open(part2.installdir + '/1', mode='w') as f:
- f.write('1')
- open(part2.installdir + '/2', mode='w').close()
- with open(part2.installdir + '/a/2', mode='w') as f:
- f.write('a/2')
-
- part3 = lifecycle.load_plugin('part3', 'jdk', {'source': '.'})
- part3.code.installdir = tmpdir + '/install3'
- os.makedirs(part3.installdir + '/a')
- os.makedirs(part3.installdir + '/b')
- with open(part3.installdir + '/1', mode='w') as f:
- f.write('2')
- with open(part2.installdir + '/2', mode='w') as f:
- f.write('1')
- open(part3.installdir + '/a/2', mode='w').close()
-
- self.assertTrue(cmds._check_for_collisions([part1, part2]))
- self.assertEqual('', fake_logger.output)
-
- self.assertFalse(cmds._check_for_collisions([part1, part2, part3]))
- self.assertEqual(
- 'Error: parts part2 and part3 have the following file paths in '
- 'common which have different contents:\n'
- ' 1\n'
- ' a/2\n',
- fake_logger.output)
-
def test_load_config_with_invalid_plugin_exits_with_error(self):
fake_logger = fixtures.FakeLogger(level=logging.ERROR)
self.useFixture(fake_logger)
Oops, something went wrong.

0 comments on commit f4c5430

Please sign in to comment.