def test_dirty_stage_reprimes(self): self.make_snapcraft_yaml( textwrap.dedent("""\ parts: part1: plugin: nil """)) # Strip it. lifecycle.execute('prime', self.project_options) # Reset logging since we only care about the following self.fake_logger = fixtures.FakeLogger(level=logging.INFO) self.useFixture(self.fake_logger) def _fake_dirty_report(self, step): if step == 'stage': return pluginhandler.DirtyReport({'foo'}, {'bar'}) return None # Should automatically clean and re-stage if that step is dirty # for the part. with mock.patch.object(pluginhandler.PluginHandler, 'get_dirty_report', _fake_dirty_report): lifecycle.execute('prime', self.project_options) self.assertThat( self.fake_logger.output, Equals( 'Skipping pull part1 (already ran)\n' 'Skipping build part1 (already ran)\n' 'Cleaning priming area for part1 (out of date)\n' 'Cleaning staging area for part1 (out of date)\n' 'Staging part1 \n' 'Priming part1 \n'))
def test_core_setup_skipped_if_not_classic(self): self.useFixture(fixtures.EnvironmentVariable("SNAPCRAFT_SETUP_CORE", "1")) project_config = self.make_snapcraft_project(confinement="strict") lifecycle.execute(steps.PULL, project_config) self.assertThat(self.witness_path, Not(FileExists()))
def test_dirty_pull_raises(self): self.make_snapcraft_yaml("""parts: part1: plugin: nil """) # Pull it. lifecycle.execute('pull', self.project_options) # Reset logging since we only care about the following self.fake_logger = fixtures.FakeLogger(level=logging.INFO) self.useFixture(self.fake_logger) def _fake_dirty_report(self, step): if step == 'pull': return pluginhandler.DirtyReport(set(), {'foo', 'bar'}) return None # Should catch that the part needs to be re-pulled and raise an error. with mock.patch.object(pluginhandler.PluginHandler, 'get_dirty_report', _fake_dirty_report): raised = self.assertRaises( RuntimeError, lifecycle.execute, 'pull', self.project_options) self.assertEqual('', self.fake_logger.output) self.assertEqual( "The 'pull' step of 'part1' is out of date:\n\n" "The 'bar' and 'foo' project options appear to have changed.\n\n" "In order to continue, please clean that part's 'pull' step " "by running: snapcraft clean part1 -s pull\n", str(raised))
def test_prime_with_build_info_records_manifest(self): self.useFixture(fixtures.EnvironmentVariable( 'SNAPCRAFT_BUILD_INFO', '1')) self.make_snapcraft_yaml( textwrap.dedent("""\ parts: test-part: plugin: nil """)) lifecycle.execute('prime', self.project_options) expected = textwrap.dedent("""\ name: test version: 0 summary: test description: test confinement: strict grade: stable parts: test-part: build-packages: [] installed-packages: [] installed-snaps: [] plugin: nil prime: [] stage: [] stage-packages: [] uname: Linux test uname 4.10 x86_64 architectures: [{}] build-packages: [] build-snaps: [] """.format(self.project_options.deb_arch)) self.assertThat( os.path.join('prime', 'snap', 'manifest.yaml'), FileContains(expected))
def run(args, project_options): lifecycle_command = _get_lifecycle_command(args) argless_command = _get_command_from_arg(args) if lifecycle_command: lifecycle.execute( lifecycle_command, project_options, args['<part>']) elif argless_command: argless_command() elif args['clean']: step = args['--step'] if step == 'strip': logger.warning('DEPRECATED: Use `prime` instead of `strip` ' 'as the step to clean') step = 'prime' lifecycle.clean(project_options, args['<part>'], step) elif args['upload']: snapcraft.upload(args['<snap-file>']) elif args['cleanbuild']: lifecycle.cleanbuild(project_options), # disable until the tour command is activated # elif args['tour']: # _scaffold_examples(args['<directory>'] or _SNAPCRAFT_TOUR_DIR) elif args['help']: snapcraft.topic_help(args['<topic>'] or args['<plugin>'], args['--devel'], args['topics']) else: # snap by default: lifecycle.snap(project_options, args['<directory>'], args['--output']) return project_options
def test_prime_with_build_info_records_snapcraft_yaml(self): self.useFixture(fixtures.EnvironmentVariable( 'SNAPCRAFT_BUILD_INFO', '1')) self.make_snapcraft_yaml( textwrap.dedent("""\ parts: test-part: plugin: nil """), snap_type='type: app') lifecycle.execute('prime', self.project_options) expected = textwrap.dedent("""\ name: test version: 0 summary: test description: test confinement: strict grade: stable type: app parts: test-part: plugin: nil """) self.assertThat( os.path.join('prime', 'snap', 'snapcraft.yaml'), FileContains(expected))
def test_core_setup_if_docker_env(self, dockerenv_fake, download_mock): dockerenv_file = os.path.join(self.tempdir, "dockerenv") os.makedirs(self.tempdir) open(dockerenv_file, "w").close() dockerenv_fake.return_value = dockerenv_file project_config = self.make_snapcraft_project(confinement="classic") core_snap = self.create_core_snap(project_config.project.deb_arch) core_snap_hash = calculate_sha3_384(core_snap) download_mock.return_value = core_snap_hash self.tempdir_mock.side_effect = self._setup_tempdir_side_effect(core_snap) lifecycle.execute(steps.PULL, project_config) regex = (".*mkdir -p {}\nunsquashfs -d {} .*{}\n").format( os.path.dirname(self.core_path), self.core_path, core_snap_hash ) self.assertThat( self.witness_path, FileContains(matcher=MatchesRegex(regex, flags=re.DOTALL)), ) download_mock.assert_called_once_with( "core", "stable", os.path.join(self.tempdir, "core.snap"), project_config.project.deb_arch, "", )
def test_no_exception_when_dependency_is_required_but_already_staged(self): self.make_snapcraft_yaml("""parts: part1: plugin: nil part2: plugin: nil after: - part1 """) def _fake_should_step_run(self, step, force=False): return self.name != 'part1' with mock.patch.object(pluginhandler.PluginHandler, 'should_step_run', _fake_should_step_run): lifecycle.execute('pull', self.project_options, part_names=['part2']) self.assertEqual( 'Skipping pull part1 (already ran)\n' 'Skipping build part1 (already ran)\n' 'Skipping stage part1 (already ran)\n' 'Skipping prime part1 (already ran)\n' 'Preparing to pull part2 \n' 'Pulling part2 \n', self.fake_logger.output)
def run(args, project_options): # noqa lifecycle_command = _get_lifecycle_command(args) argless_command = _get_command_from_arg(args) if lifecycle_command: lifecycle.execute( lifecycle_command, project_options, args['<part>']) elif argless_command: argless_command() elif args['clean']: _run_clean(args, project_options) elif args['cleanbuild']: lifecycle.cleanbuild(project_options), elif _is_store_command(args): _run_store_command(args) elif args['tour']: _scaffold_examples(args['<directory>'] or _SNAPCRAFT_TOUR_DIR) elif args['help']: snapcraft.topic_help(args['<topic>'] or args['<plugin>'], args['--devel'], args['topics']) elif args['update']: parts.update() elif args['define']: parts.define(args['<part-name>']) elif args['search']: parts.search(' '.join(args['<query>'])) else: # snap by default: lifecycle.snap(project_options, args['<directory>'], args['--output']) return project_options
def test_pull_is_dirty_if_target_arch_changes( self, mock_install_build_packages, mock_enable_cross_compilation): self.make_snapcraft_yaml("""parts: part1: plugin: nil """) # Pull it with amd64 lifecycle.execute('pull', snapcraft.ProjectOptions( target_deb_arch='amd64')) # Reset logging since we only care about the following self.fake_logger = fixtures.FakeLogger(level=logging.INFO) self.useFixture(self.fake_logger) # Pull it again with armhf. Should catch that the part needs to be # re-pulled due to the change in target architecture and raise an # error. raised = self.assertRaises( RuntimeError, lifecycle.execute, 'pull', snapcraft.ProjectOptions( target_deb_arch='armhf')) self.assertEqual("Setting target machine to 'armhf'\n", self.fake_logger.output) self.assertEqual( "The 'pull' step of 'part1' is out of date:\n\n" "The 'deb_arch' project option appears to have changed.\n\n" "In order to continue, please clean that part's 'pull' step " "by running: snapcraft clean part1 -s pull\n", str(raised))
def test_dirty_stage_restrips(self): self.make_snapcraft_yaml("""parts: part1: plugin: nil """) # Strip it. lifecycle.execute('strip', self.project_options) # Reset logging since we only care about the following self.fake_logger = fixtures.FakeLogger(level=logging.INFO) self.useFixture(self.fake_logger) def _fake_is_dirty(self, step): return step == 'stage' # Should automatically clean and re-stage if that step is dirty # for the part. with mock.patch.object(pluginhandler.PluginHandler, 'is_dirty', _fake_is_dirty): lifecycle.execute('strip', self.project_options) self.assertEqual( 'Skipping pull part1 (already ran)\n' 'Skipping build part1 (already ran)\n' 'Cleaning snapping area for part1 (out of date)\n' 'Cleaning staging area for part1 (out of date)\n' 'Staging part1 \n' 'Stripping part1 \n', self.fake_logger.output)
def test_dirty_stage_part_with_unbuilt_dependent(self): self.make_snapcraft_yaml("""parts: part1: plugin: nil part2: plugin: nil after: [part1] """) # Stage dependency (dependent is unbuilt) lifecycle.execute('stage', self.project_options, part_names=['part1']) # Reset logging since we only care about the following self.fake_logger = fixtures.FakeLogger(level=logging.INFO) self.useFixture(self.fake_logger) def _fake_is_dirty(self, step): return step == 'stage' # Should automatically clean and re-stage if that step is dirty # for the part. with mock.patch.object(pluginhandler.PluginHandler, 'is_dirty', _fake_is_dirty): lifecycle.execute('stage', self.project_options, part_names=['part1']) self.assertEqual( 'Skipping pull part1 (already ran)\n' 'Skipping build part1 (already ran)\n' 'Skipping cleaning priming area for part1 (out of date) ' '(already clean)\n' 'Cleaning staging area for part1 (out of date)\n' 'Staging part1 \n', self.fake_logger.output)
def test_dirty_build_raises(self): self.make_snapcraft_yaml("""parts: part1: plugin: nil """) # Build it. lifecycle.execute('build', self.project_options) # Reset logging since we only care about the following self.fake_logger = fixtures.FakeLogger(level=logging.INFO) self.useFixture(self.fake_logger) def _fake_dirty_report(self, step): if step == 'build': return pluginhandler.DirtyReport({'foo', 'bar'}, set()) return None # Should catch that the part needs to be rebuilt and raise an error. with mock.patch.object(pluginhandler.PluginHandler, 'get_dirty_report', _fake_dirty_report): raised = self.assertRaises( RuntimeError, lifecycle.execute, 'build', self.project_options) self.assertEqual( 'Skipping pull part1 (already ran)\n', self.fake_logger.output) self.assertEqual( "The 'build' step of 'part1' is out of date:\n\n" "The 'bar' and 'foo' part properties appear to have changed.\n\n" "Please clean that part's 'build' step in order to continue", str(raised))
def test_dirty_pull_raises(self): self.make_snapcraft_yaml("""parts: part1: plugin: nil """) # Pull it. lifecycle.execute('pull', self.project_options) # Reset logging since we only care about the following self.fake_logger = fixtures.FakeLogger(level=logging.INFO) self.useFixture(self.fake_logger) def _fake_is_dirty(self, step): return step == 'pull' # Should catch that the part needs to be re-pulled and raise an error. with mock.patch.object(pluginhandler.PluginHandler, 'is_dirty', _fake_is_dirty): with self.assertRaises(RuntimeError) as raised: lifecycle.execute('pull', self.project_options) self.assertEqual('', self.fake_logger.output) self.assertEqual( "The 'pull' step of 'part1' is out of date. Please clean that " "part's 'pull' step in order to rebuild", str(raised.exception))
def test_core_setup_with_env_var(self, download_mock): self.useFixture(fixtures.EnvironmentVariable("SNAPCRAFT_SETUP_CORE", "1")) project_config = self.make_snapcraft_project(confinement="classic") core_snap = self.create_core_snap(project_config.project.deb_arch) core_snap_hash = calculate_sha3_384(core_snap) download_mock.return_value = core_snap_hash self.tempdir_mock.side_effect = self._setup_tempdir_side_effect(core_snap) lifecycle.execute(steps.PULL, project_config) regex = (".*mkdir -p {}\nunsquashfs -d {} .*{}\n").format( os.path.dirname(self.core_path), self.core_path, core_snap_hash ) self.assertThat( self.witness_path, FileContains(matcher=MatchesRegex(regex, flags=re.DOTALL)), ) download_mock.assert_called_once_with( "core", "stable", os.path.join(self.tempdir, "core.snap"), project_config.project.deb_arch, "", )
def test_prime_with_build_info_records_snapcraft_yaml(self): self.useFixture(fixtures.EnvironmentVariable("SNAPCRAFT_BUILD_INFO", "1")) project_config = self.make_snapcraft_project( textwrap.dedent( """\ parts: test-part: plugin: nil """ ), snap_type="type: app", ) lifecycle.execute(steps.PRIME, project_config) expected = textwrap.dedent( """\ name: test version: 0 summary: test description: test confinement: strict grade: stable type: app parts: test-part: plugin: nil """ ) self.assertThat( os.path.join(steps.PRIME.name, "snap", "snapcraft.yaml"), FileContains(expected), )
def test_dependency_is_staged_when_required(self): project_config = self.make_snapcraft_project( textwrap.dedent( """\ parts: part1: plugin: nil part2: plugin: nil after: - part1 """ ) ) lifecycle.execute(steps.PULL, project_config, part_names=["part2"]) self.assertThat( self.fake_logger.output, Equals( "'part2' has dependencies that need to be staged: part1\n" "Pulling part1 \n" "Building part1 \n" "Staging part1 \n" "Pulling part2 \n" ), )
def test_prime_with_image_info_records_manifest(self): self.useFixture(fixtures.EnvironmentVariable("SNAPCRAFT_BUILD_INFO", "1")) test_image_info = ( '{"architecture": "test-architecture", ' '"created_at": "test-created-at", ' '"fingerprint": "test-fingerprint"}' ) self.useFixture( fixtures.EnvironmentVariable("SNAPCRAFT_IMAGE_INFO", test_image_info) ) project_config = self.make_snapcraft_project( textwrap.dedent( """\ parts: test-part: plugin: nil """ ) ) lifecycle.execute(steps.PRIME, project_config) expected = textwrap.dedent( """\ snapcraft-version: '3.0' snapcraft-os-release-id: ubuntu snapcraft-os-release-version-id: '16.04' name: test version: 0 summary: test description: test confinement: strict grade: stable parts: test-part: build-packages: [] installed-packages: - patchelf=0.9 installed-snaps: [] plugin: nil prime: [] stage: [] stage-packages: [] uname: Linux test uname 4.10 x86_64 architectures: - {} image-info: architecture: test-architecture created_at: test-created-at fingerprint: test-fingerprint build-packages: [] build-snaps: [] """.format( project_config.project.deb_arch ) ) self.assertThat( os.path.join(steps.PRIME.name, "snap", "manifest.yaml"), FileContains(expected), )
def test_prime_with_virtual_build_package(self, _): self.useFixture(fixtures.EnvironmentVariable("SNAPCRAFT_BUILD_INFO", "1")) self.fake_apt_cache.add_package( fixture_setup.FakeAptCachePackage( "test-provider-package", "test-version", provides=["test-virtual-package"], ) ) project_config = self.make_snapcraft_project( textwrap.dedent( """\ parts: test-part: plugin: nil build-packages: ['test-virtual-package'] """ ) ) lifecycle.execute(steps.PRIME, project_config) expected = textwrap.dedent( """\ snapcraft-version: '3.0' snapcraft-os-release-id: ubuntu snapcraft-os-release-version-id: '16.04' name: test version: 0 summary: test description: test confinement: strict grade: stable parts: test-part: build-packages: - test-virtual-package installed-packages: - patchelf=0.9 installed-snaps: [] plugin: nil prime: [] stage: [] stage-packages: [] uname: Linux test uname 4.10 x86_64 architectures: - {} build-packages: - test-provider-package=test-version build-snaps: [] """.format( project_config.project.deb_arch ) ) self.assertThat( os.path.join(steps.PRIME.name, "snap", "manifest.yaml"), FileContains(expected), )
def test_core_setup_skipped_if_core_exists(self): os.makedirs(self.core_path) open(os.path.join(self.core_path, 'fake-content'), 'w').close() self._create_classic_confined_snapcraft_yaml() lifecycle.execute('pull', self.project_options) self.assertThat(self.witness_path, Not(FileExists()))
def _execute(command, parts, **kwargs): project_options = get_project_options(**kwargs) container_config = env.get_container_config() if container_config.use_container: lifecycle.containerbuild(command, project_options, container_config, parts) else: lifecycle.execute(command, project_options, parts) return project_options
def test_prime_excludes_internal_snapcraft_dir(self): self.make_snapcraft_yaml( textwrap.dedent("""\ parts: test-part: plugin: nil """)) lifecycle.execute('prime', self.project_options) self.assertThat( os.path.join('prime', 'snap', '.snapcraft'), Not(DirExists()))
def test_prime_with_installed_snaps(self): self.useFixture(fixtures.EnvironmentVariable("SNAPCRAFT_BUILD_INFO", "1")) self.fake_snapd.snaps_result = [ {"name": "test-snap-1", "revision": "test-snap-1-revision"}, {"name": "test-snap-2", "revision": "test-snap-2-revision"}, ] project_config = self.make_snapcraft_project( textwrap.dedent( """\ parts: test-part: plugin: nil """ ) ) lifecycle.execute(steps.PRIME, project_config) expected = textwrap.dedent( """\ snapcraft-version: '3.0' snapcraft-os-release-id: ubuntu snapcraft-os-release-version-id: '16.04' name: test version: 0 summary: test description: test confinement: strict grade: stable parts: test-part: build-packages: [] installed-packages: - patchelf=0.9 installed-snaps: - test-snap-1=test-snap-1-revision - test-snap-2=test-snap-2-revision plugin: nil prime: [] stage: [] stage-packages: [] uname: Linux test uname 4.10 x86_64 architectures: - {} build-packages: [] build-snaps: [] """.format( project_config.project.deb_arch ) ) self.assertThat( os.path.join(steps.PRIME.name, "snap", "manifest.yaml"), FileContains(expected), )
def test_clean_removes_global_state(self): self.make_snapcraft_yaml( textwrap.dedent("""\ parts: test-part: plugin: nil """)) lifecycle.execute('pull', self.project_options) lifecycle.clean(self.project_options, parts=None) self.assertThat( os.path.join('snap', '.snapcraft'), Not(DirExists()))
def test_dirty_stage_restages_multiple_parts(self): self.make_snapcraft_yaml( textwrap.dedent("""\ parts: part1: plugin: nil part2: plugin: nil """)) # Stage it. lifecycle.execute('stage', self.project_options) # Reset logging since we only care about the following self.fake_logger = fixtures.FakeLogger(level=logging.INFO) self.useFixture(self.fake_logger) def _fake_dirty_report(self, step): if step == 'stage': return pluginhandler.DirtyReport({'foo'}, {'bar'}) return None # Should automatically clean and re-stage if that step is dirty # for the part. with mock.patch.object(pluginhandler.PluginHandler, 'get_dirty_report', _fake_dirty_report): lifecycle.execute('stage', self.project_options) output = self.fake_logger.output.split('\n') part1_output = [line.strip() for line in output if 'part1' in line] part2_output = [line.strip() for line in output if 'part2' in line] self.assertThat( part2_output, Equals([ 'Skipping pull part2 (already ran)', 'Skipping build part2 (already ran)', 'Skipping cleaning priming area for part2 (out of date) ' '(already clean)', 'Cleaning staging area for part2 (out of date)', 'Staging part2', ])) self.assertThat( part1_output, Equals([ 'Skipping pull part1 (already ran)', 'Skipping build part1 (already ran)', 'Skipping cleaning priming area for part1 (out of date) ' '(already clean)', 'Cleaning staging area for part1 (out of date)', 'Staging part1', ]))
def test_clean_removes_global_state(self): project_config = self.make_snapcraft_project( textwrap.dedent( """\ parts: test-part: plugin: nil """ ) ) lifecycle.execute(steps.PULL, project_config) lifecycle.clean(project_config.project, parts=None) self.assertThat(os.path.join("snap", ".snapcraft"), Not(DirExists()))
def test_dirty_stage_part_with_built_dependent_raises(self): self.make_snapcraft_yaml( textwrap.dedent("""\ parts: part1: plugin: nil part2: plugin: nil after: [part1] """)) # Stage dependency lifecycle.execute('stage', self.project_options, part_names=['part1']) # Build dependent lifecycle.execute('build', self.project_options, part_names=['part2']) # Reset logging since we only care about the following self.fake_logger = fixtures.FakeLogger(level=logging.INFO) self.useFixture(self.fake_logger) def _fake_dirty_report(self, step): if step == 'stage': return pluginhandler.DirtyReport({'foo'}, {'bar'}) return None # Should raise a RuntimeError about the fact that stage is dirty but # it has dependents that need it. with mock.patch.object(pluginhandler.PluginHandler, 'get_dirty_report', _fake_dirty_report): raised = self.assertRaises( errors.StepOutdatedError, lifecycle.execute, 'stage', self.project_options, part_names=['part1']) output = self.fake_logger.output.split('\n') part1_output = [line.strip() for line in output if 'part1' in line] self.assertThat( part1_output, Equals([ 'Skipping pull part1 (already ran)', 'Skipping build part1 (already ran)', ])) self.assertThat( str(raised), Equals( "The 'stage' step of 'part1' is out of date:\n" "The 'stage' step for 'part1' needs to be run again, but " "'part2' depends on it.\n" "In order to continue, please clean that part's 'stage' step " "by running:\nsnapcraft clean part2 -s stage\n"))
def test_prime_excludes_internal_snapcraft_dir(self): project_config = self.make_snapcraft_project( textwrap.dedent( """\ parts: test-part: plugin: nil """ ) ) lifecycle.execute(steps.PRIME, project_config) self.assertThat( os.path.join(steps.PRIME.name, "snap", ".snapcraft"), Not(DirExists()) )
def test_build_is_dirty_if_scriptlet_changes( self, mock_install_build_packages, mock_enable_cross_compilation ): mock_install_build_packages.return_value = [] project_config = self.make_snapcraft_project( textwrap.dedent( """\ parts: part1: plugin: nil {}: touch scriptlet """ ).format(self.scriptlet) ) # Build it lifecycle.execute(steps.BUILD, project_config) # Reset logging since we only care about the following self.fake_logger = fixtures.FakeLogger(level=logging.INFO) self.useFixture(self.fake_logger) # Change prepare scriptlet project_config = self.make_snapcraft_project( textwrap.dedent( """\ parts: part1: plugin: nil {}: touch changed """ ).format(self.scriptlet) ) # Build it again. Should catch that the scriptlet changed and it needs # to be rebuilt. raised = self.assertRaises( errors.StepOutdatedError, lifecycle.execute, steps.BUILD, project_config ) self.assertThat(raised.step, Equals(steps.BUILD)) self.assertThat(raised.part, Equals("part1")) self.assertThat( raised.report, Equals( "The {!r} part property appears to have changed.\n".format( self.scriptlet ) ), )
def test_has_step_run(self): # No steps should have run, yet main_part = self.project_config.parts.get_part("main") self.assertFalse(self.cache.has_step_run(main_part, steps.PULL)) # Now run the pull step lifecycle.execute(steps.PULL, self.project_config, part_names=["main"]) # Should still have cached that no steps have run self.assertFalse(self.cache.has_step_run(main_part, steps.PULL)) # Now clear that step from the cache, and it should be up-to-date self.cache.clear_step(main_part, steps.PULL) self.assertTrue(self.cache.has_step_run(main_part, steps.PULL))
def test_prime_with_stage_packages(self): self.useFixture(fixtures.EnvironmentVariable( 'SNAPCRAFT_BUILD_INFO', '1')) for name, version in [('test-package1', 'test-version1'), ('test-package2', 'test-version2')]: self.fake_apt_cache.add_package( fixture_setup.FakeAptCachePackage(name, version)) self.make_snapcraft_yaml( textwrap.dedent("""\ parts: test-part: plugin: nil stage-packages: [test-package1=test-version1, test-package2] """)) # NOQA lifecycle.execute('prime', self.project_options) expected = textwrap.dedent("""\ name: test version: 0 summary: test description: test confinement: strict grade: stable parts: test-part: build-packages: [] installed-packages: [] installed-snaps: [] plugin: nil prime: [] stage: [] stage-packages: [test-package1=test-version1, test-package2=test-version2] uname: Linux test uname 4.10 x86_64 architectures: [{}] build-packages: [] build-snaps: [] """.format(self.project_options.deb_arch)) # NOQA self.assertThat( os.path.join('prime', 'snap', 'manifest.yaml'), FileContains(expected))
def test_prime_with_virtual_build_package(self, _): self.useFixture(fixtures.EnvironmentVariable( 'SNAPCRAFT_BUILD_INFO', '1')) self.fake_apt_cache.add_package( fixture_setup.FakeAptCachePackage( 'test-provider-package', 'test-version', provides=['test-virtual-package'])) self.make_snapcraft_yaml( textwrap.dedent("""\ parts: test-part: plugin: nil build-packages: ['test-virtual-package'] """)) lifecycle.execute('prime', self.project_options) expected = textwrap.dedent("""\ name: test version: 0 summary: test description: test confinement: strict grade: stable parts: test-part: build-packages: [test-virtual-package] installed-packages: [] installed-snaps: [] plugin: nil prime: [] stage: [] stage-packages: [] uname: Linux test uname 4.10 x86_64 architectures: [{}] build-packages: [test-provider-package=test-version] build-snaps: [] """.format(self.project_options.deb_arch)) self.assertThat( os.path.join('prime', 'snap', 'manifest.yaml'), FileContains(expected))
def test_dirty_stage_part_with_unbuilt_dependent(self): self.make_snapcraft_yaml( textwrap.dedent("""\ parts: part1: plugin: nil part2: plugin: nil after: [part1] """)) # Stage dependency (dependent is unbuilt) lifecycle.execute('stage', self.project_options, part_names=['part1']) # Reset logging since we only care about the following self.fake_logger = fixtures.FakeLogger(level=logging.INFO) self.useFixture(self.fake_logger) def _fake_dirty_report(self, step): if step == 'stage': return pluginhandler.DirtyReport({'foo'}, {'bar'}) return None # Should automatically clean and re-stage if that step is dirty # for the part. with mock.patch.object(pluginhandler.PluginHandler, 'get_dirty_report', _fake_dirty_report): lifecycle.execute('stage', self.project_options, part_names=['part1']) self.assertThat( self.fake_logger.output, Equals( 'Skipping pull part1 (already ran)\n' 'Skipping build part1 (already ran)\n' 'Skipping cleaning priming area for part1 (out of date) ' '(already clean)\n' 'Cleaning staging area for part1 (out of date)\n' 'Skipping cleaning priming area for part2 (out of date) ' '(already clean)\n' 'Skipping cleaning staging area for part2 (out of date) ' '(already clean)\n' 'Staging part1 \n'))
def run(args, project_options): lifecycle_command = _get_lifecycle_command(args) argless_command = _get_command_from_arg(args) if lifecycle_command: lifecycle.execute(lifecycle_command, project_options, args['<part>']) elif argless_command: argless_command() elif args['clean']: lifecycle.clean(project_options, args['<part>'], args['--step']) elif args['upload']: snapcraft.upload(args['<snap-file>']) elif args['cleanbuild']: lifecycle.cleanbuild(project_options), elif args['help']: snapcraft.topic_help(args['<topic>'] or args['<plugin>'], args['--devel'], args['topics']) else: # snap by default: lifecycle.snap(project_options, args['<directory>'], args['--output']) return project_options
def test_part_with_build_snaps_on_docker(self, mock_install_build_snaps, mock_docker_instance): project = self.make_snapcraft_project( textwrap.dedent("""\ parts: part1: plugin: nil build-snaps: [snap1, snap2] """)) lifecycle.execute(steps.PULL, project) mock_install_build_snaps.assert_not_called() self.assertThat( self.fake_logger.output, Contains( "The following snaps are required but not installed as snapcraft " "is running inside docker: "), )
def test_get_dirty_report(self): # No dirty reports should be available, yet dependent_part = self.project_config.parts.get_part("dependent") self.assertFalse( self.cache.get_dirty_report(dependent_part, steps.PULL)) # Now run the pull step lifecycle.execute(steps.PULL, self.project_config) # Re-stage main, which will make dependent dirty lifecycle.execute(steps.PULL, self.project_config, part_names=["main"]) # Should still have cached that it's not dirty, though self.assertFalse( self.cache.get_dirty_report(dependent_part, steps.PULL)) # Now clear that step from the cache, and it should be up-to-date self.cache.clear_step(dependent_part, steps.PULL) self.assertTrue(self.cache.get_dirty_report(dependent_part, steps.PULL))
def test_no_exception_when_dependency_is_required_but_already_staged(self): project_config = self.make_snapcraft_project( textwrap.dedent("""\ parts: part1: plugin: nil part2: plugin: nil after: - part1 """)) def _fake_should_step_run(self, step, force=False): return self.name != "part1" with mock.patch.object(pluginhandler.PluginHandler, "should_step_run", _fake_should_step_run): lifecycle.execute(steps.PULL, project_config, part_names=["part2"]) self.assertThat(self.fake_logger.output, Equals("Pulling part2 \n"))
def test_prime_with_build_info_records_manifest(self): self.useFixture( fixtures.EnvironmentVariable("SNAPCRAFT_BUILD_INFO", "1")) project_config = self.make_snapcraft_project( textwrap.dedent("""\ parts: test-part: plugin: nil """)) lifecycle.execute(steps.PRIME, project_config) expected = textwrap.dedent("""\ snapcraft-version: '3.0' snapcraft-os-release-id: ubuntu snapcraft-os-release-version-id: '16.04' name: test version: 0 summary: test description: test confinement: strict grade: stable parts: test-part: build-packages: [] installed-packages: - patchelf=0.9 installed-snaps: [] plugin: nil prime: [] stage: [] stage-packages: [] uname: Linux test uname 4.10 x86_64 architectures: - {} build-packages: [] build-snaps: [] """.format(project_config.project.deb_arch)) self.assertThat( os.path.join(steps.PRIME.name, "snap", "manifest.yaml"), FileContains(expected), )
def test_no_exception_when_dependency_is_required_but_already_staged(self): self.make_snapcraft_yaml("""parts: part1: plugin: nil part2: plugin: nil after: - part1 """) def _fake_should_step_run(self, step, force=False): return self.name != 'part1' with mock.patch.object(pluginhandler.PluginHandler, 'should_step_run', _fake_should_step_run): lifecycle.execute('pull', self.project_options, part_names=['part2']) self.assertEqual('Preparing to pull part2 \n' 'Pulling part2 \n', self.fake_logger.output)
def test_dependency_is_staged_when_required(self, mock_install_build_snaps): project_config = self.make_snapcraft_project( textwrap.dedent( """\ parts: part1: plugin: nil part2: plugin: nil after: - part1 """ ) ) lifecycle.execute(steps.PULL, project_config, part_names=["part2"]) self.assertThat( self.fake_logger.output, Contains("'part2' has dependencies that need to be staged: part1"), )
def test_core_setup(self, download_mock): core_snap = self._create_core_snap() core_snap_hash = calculate_sha3_384(core_snap) download_mock.return_value = core_snap_hash self.tempdir_mock.side_effect = self._setup_tempdir_side_effect( core_snap) self._create_classic_confined_snapcraft_yaml() lifecycle.execute('pull', self.project_options) regex = ('mkdir -p {}\n' 'unsquashfs -d {} .*{}\n').format( os.path.dirname(self.core_path), self.core_path, core_snap_hash) self.assertThat( self.witness_path, FileContains(matcher=MatchesRegex(regex, flags=re.DOTALL))) download_mock.assert_called_once_with( 'core', 'stable', os.path.join(self.tempdir, 'core.snap'), self.project_options.deb_arch, '')
def test_build_is_dirty_if_scriptlet_changes( self, mock_install_build_packages, mock_enable_cross_compilation): mock_install_build_packages.return_value = [] self.make_snapcraft_yaml( textwrap.dedent("""\ parts: part1: plugin: nil {}: touch scriptlet """).format(self.scriptlet)) # Build it lifecycle.execute('build', snapcraft.ProjectOptions()) # Reset logging since we only care about the following self.fake_logger = fixtures.FakeLogger(level=logging.INFO) self.useFixture(self.fake_logger) # Change prepare scriptlet self.make_snapcraft_yaml( textwrap.dedent("""\ parts: part1: plugin: nil {}: touch changed """).format(self.scriptlet)) # Build it again. Should catch that the scriptlet changed and it needs # to be rebuilt. raised = self.assertRaises( errors.StepOutdatedError, lifecycle.execute, 'build', snapcraft.ProjectOptions()) self.assertThat( str(raised), Equals( "The 'build' step of 'part1' is out of date:\n" "The {!r} part property appears to have changed.\n" "In order to continue, please clean that part's 'build' step " "by running:\nsnapcraft clean part1 -s build\n".format( self.scriptlet)))
def test_dirty_build_raises(self): project_config = self.make_snapcraft_project( textwrap.dedent("""\ parts: part1: plugin: nil """)) # Build it. lifecycle.execute(steps.BUILD, project_config) # Reset logging since we only care about the following self.fake_logger = fixtures.FakeLogger(level=logging.INFO) self.useFixture(self.fake_logger) def _fake_dirty_report(self, step): if step == steps.BUILD: return pluginhandler.DirtyReport( dirty_properties={"foo", "bar"}) return None # Should catch that the part needs to be rebuilt and raise an error. with mock.patch.object(pluginhandler.PluginHandler, "get_dirty_report", _fake_dirty_report): raised = self.assertRaises(errors.StepOutdatedError, lifecycle.execute, steps.BUILD, project_config) self.assertThat(self.fake_logger.output, Equals("Skipping pull part1 (already ran)\n")) self.assertThat(raised.step, Equals(steps.BUILD)) self.assertThat(raised.part, Equals("part1")) self.assertThat( raised.report, Equals( "The 'bar' and 'foo' part properties appear to have changed.\n" ), ) self.assertThat(raised.parts_names, Equals("part1"))
def test_build_is_dirty_if_scriptlet_changes( self, mock_install_build_packages, mock_enable_cross_compilation): mock_install_build_packages.return_value = [] project_config = self.make_snapcraft_project( textwrap.dedent("""\ parts: part1: plugin: nil {}: touch scriptlet """).format(self.scriptlet)) # Build it lifecycle.execute(self.step, project_config) # Reset logging since we only care about the following self.fake_logger = fixtures.FakeLogger(level=logging.INFO) self.useFixture(self.fake_logger) # Change prepare scriptlet project_config = self.make_snapcraft_project( textwrap.dedent("""\ parts: part1: plugin: nil {}: touch changed """).format(self.scriptlet)) # Build it again. Should catch that the scriptlet changed and it needs # to be rebuilt. raised = self.assertRaises(errors.StepOutdatedError, lifecycle.execute, self.step, project_config) self.assertThat(raised.step, Equals(self.step)) self.assertThat(raised.part, Equals("part1")) self.assertThat( raised.report, Equals("The {!r} part property appears to have changed.\n".format( self.scriptlet)), )
def test_prime_with_source_details(self, _): self.useFixture(fixtures.EnvironmentVariable( 'SNAPCRAFT_BUILD_INFO', '1')) self.make_snapcraft_yaml("""parts: test-part: plugin: nil source: test-source source-type: git source-commit: test-commit """) lifecycle.execute('prime', self.project_options) expected = ("""name: test version: 0 summary: test description: test confinement: strict grade: stable parts: test-part: build-packages: [] plugin: nil prime: [] source: test-source source-branch: '' source-checksum: '' source-commit: test-commit source-tag: '' source-type: git stage: [] stage-packages: [] architectures: [{}] build-packages: [] """.format(self.project_options.deb_arch)) self.assertThat( os.path.join('prime', 'snap', 'snapcraft.yaml'), FileContains(expected))
def test_dependency_is_staged_when_required(self): project_config = self.make_snapcraft_project( textwrap.dedent("""\ parts: part1: plugin: nil part2: plugin: nil after: - part1 """)) lifecycle.execute(steps.PULL, project_config, part_names=["part2"]) self.assertThat( self.fake_logger.output, Equals("'part2' has dependencies that need to be staged: part1\n" "Pulling part1 \n" "Building part1 \n" "Staging part1 \n" "Pulling part2 \n"), )
def test_dirty_build_raises(self): self.make_snapcraft_yaml( textwrap.dedent("""\ parts: part1: plugin: nil """)) # Build it. lifecycle.execute('build', self.project_options) # Reset logging since we only care about the following self.fake_logger = fixtures.FakeLogger(level=logging.INFO) self.useFixture(self.fake_logger) def _fake_dirty_report(self, step): if step == 'build': return pluginhandler.DirtyReport({'foo', 'bar'}, set()) return None # Should catch that the part needs to be rebuilt and raise an error. with mock.patch.object(pluginhandler.PluginHandler, 'get_dirty_report', _fake_dirty_report): raised = self.assertRaises( errors.StepOutdatedError, lifecycle.execute, 'build', self.project_options) self.assertThat( self.fake_logger.output, Equals('Skipping pull part1 (already ran)\n')) self.assertThat( str(raised), Equals( "The 'build' step of 'part1' is out of date:\n" "The 'bar' and 'foo' part properties appear to have changed.\n" "In order to continue, please clean that part's 'build' step " "by running:\nsnapcraft clean part1 -s build\n"))
def test_prime_with_plugin_manifest(self, fake_plugin_manifest): fake_plugin_manifest.return_value = { 'test-plugin-manifest': 'test-value' } self.useFixture( fixtures.EnvironmentVariable('SNAPCRAFT_BUILD_INFO', '1')) self.make_snapcraft_yaml( textwrap.dedent("""\ parts: test-part: plugin: nil """)) lifecycle.execute('prime', self.project_options) expected = textwrap.dedent("""\ name: test version: 0 summary: test description: test confinement: strict grade: stable parts: test-part: build-packages: [] installed-packages: [] installed-snaps: [] plugin: nil prime: [] stage: [] stage-packages: [] test-plugin-manifest: test-value uname: Linux test uname 4.10 x86_64 architectures: [{}] build-packages: [] build-snaps: [] """.format(self.project_options.deb_arch)) self.assertThat(os.path.join('prime', 'snap', 'manifest.yaml'), FileContains(expected))
def test_prime_with_virtual_build_package(self, _): self.useFixture(fixtures.EnvironmentVariable( 'SNAPCRAFT_BUILD_INFO', '1')) fake_apt_cache = fixture_setup.FakeAptCache() self.useFixture(fake_apt_cache) fake_apt_cache.cache['test-provider-package'] = ( fixture_setup.FakeAptCachePackage( fake_apt_cache.path, 'test-provider-package', 'test-version', provides=['test-virtual-package'])) self.make_snapcraft_yaml("""parts: test-part: plugin: nil build-packages: ['test-virtual-package'] """) lifecycle.execute('prime', self.project_options) expected = ("""name: test version: 0 summary: test description: test confinement: strict grade: stable parts: test-part: build-packages: [test-provider-package=test-version] plugin: nil prime: [] stage: [] stage-packages: [] architectures: [{}] build-packages: [] """.format(self.project_options.deb_arch)) self.assertThat( os.path.join('prime', 'snap', 'snapcraft.yaml'), FileContains(expected))
def test_get_outdated_report(self): # No outdated reports should be available, yet main_part = self.project_config.parts.get_part("main") self.assertFalse(self.cache.get_outdated_report(main_part, steps.PULL)) # Now run the pull step for main lifecycle.execute(steps.PULL, self.project_config, part_names=["main"]) # Change the source on disk, which will make the pull step of main # outdated (to ensure this is the case, manually set the timestamp) open("new-file", "w").close() pull_state_file = states.get_step_state_file(main_part.plugin.statedir, steps.PULL) access_time = os.stat(pull_state_file).st_atime modified_time = os.stat(pull_state_file).st_atime os.utime("new-file", (access_time, modified_time + 1)) # Should still have cached that it's not outdated, though self.assertFalse(self.cache.get_outdated_report(main_part, steps.PULL)) # Now clear that step from the cache, and it should be up-to-date self.cache.clear_step(main_part, steps.PULL) self.assertTrue(self.cache.get_outdated_report(main_part, steps.PULL))
def test_pull_is_dirty_if_target_arch_changes( self, mock_install_build_packages, mock_enable_cross_compilation): mock_install_build_packages.return_value = [] self.make_snapcraft_yaml( textwrap.dedent("""\ parts: part1: plugin: nil """)) # Pull it with amd64 lifecycle.execute('pull', snapcraft.ProjectOptions( target_deb_arch='amd64')) # Reset logging since we only care about the following self.fake_logger = fixtures.FakeLogger(level=logging.INFO) self.useFixture(self.fake_logger) # Pull it again with armhf. Should catch that the part needs to be # re-pulled due to the change in target architecture and raise an # error. raised = self.assertRaises( errors.StepOutdatedError, lifecycle.execute, 'pull', snapcraft.ProjectOptions( target_deb_arch='armhf')) self.assertThat( self.fake_logger.output, Equals("Setting target machine to 'armhf'\n")) self.assertThat( str(raised), Equals( "The 'pull' step of 'part1' is out of date:\n" "The 'deb_arch' project option appears to have changed.\n" "In order to continue, please clean that part's 'pull' step " "by running:\nsnapcraft clean part1 -s pull\n"))
def test_prime_with_build_info_records_snapcraft_yaml(self): self.useFixture(fixtures.EnvironmentVariable("SNAPCRAFT_BUILD_INFO", "1")) project_config = self.make_snapcraft_project( textwrap.dedent( """\ parts: test-part: plugin: nil """ ), snap_type="type: app", ) lifecycle.execute(steps.PRIME, project_config) expected = textwrap.dedent( """\ name: test base: core18 version: "1.0" summary: test description: test confinement: strict grade: stable type: app parts: test-part: plugin: nil """ ) self.assertThat( os.path.join(steps.PRIME.name, "snap", "snapcraft.yaml"), FileContains(expected), )
def run(args, project_options): # noqa lifecycle_command = _get_lifecycle_command(args) argless_command = _get_command_from_arg(args) if lifecycle_command: lifecycle.execute(lifecycle_command, project_options, args['<part>']) elif argless_command: argless_command() elif args['clean']: _run_clean(args, project_options) elif args['cleanbuild']: lifecycle.cleanbuild(project_options, remote=args['--remote']), elif _is_store_command(args): _run_store_command(args) elif args['tour']: _scaffold_examples(args['<directory>'] or _SNAPCRAFT_TOUR_DIR) elif args['help']: snapcraft.topic_help(args['<topic>'] or args['<plugin>'], args['--devel'], args['topics']) elif args['enable-ci']: enable_ci(args['<ci-system>'], args['--refresh']) elif args['update']: parts.update() elif args['define']: parts.define(args['<part-name>']) elif args['search']: parts.search(' '.join(args['<query>'])) elif os.environ.get('SNAPCRAFT_CONTAINER_BUILDS'): lifecycle.containerbuild(project_options, args['--output'], args['--remote']) else: # snap by default: if args['--remote']: raise RuntimeError( '--remote can only be used with SNAPCRAFT_CONTAINER_BUILDS') lifecycle.snap(project_options, args['<directory>'], args['--output']) return project_options
def test_dirty_stage_part_with_built_dependent_raises(self): # Set the option to error on dirty/outdated steps with snapcraft.config.CLIConfig() as cli_config: cli_config.set_outdated_step_action( snapcraft.config.OutdatedStepAction.ERROR ) project_config = self.make_snapcraft_project( textwrap.dedent( """\ parts: part1: plugin: nil part2: plugin: nil after: [part1] """ ) ) # Stage dependency lifecycle.execute(steps.STAGE, project_config, part_names=["part1"]) # Build dependent lifecycle.execute(steps.BUILD, project_config, part_names=["part2"]) def _fake_dirty_report(self, step): if step == steps.STAGE: return pluginhandler.DirtyReport( dirty_properties={"foo"}, dirty_project_options={"bar"} ) return None # Should stage no problem with mock.patch.object( pluginhandler.PluginHandler, "get_dirty_report", _fake_dirty_report ): lifecycle.execute(steps.STAGE, project_config, part_names=["part1"]) # Reset logging since we only care about the following self.fake_logger = fixtures.FakeLogger(level=logging.INFO) self.useFixture(self.fake_logger) # Should raise an error since part2 is now dirty raised = self.assertRaises( errors.StepOutdatedError, lifecycle.execute, steps.BUILD, project_config ) output = self.fake_logger.output.split("\n") part1_output = [line.strip() for line in output if "part1" in line] self.assertThat(part1_output, Equals(["Skipping pull part1 (already ran)"])) self.assertThat(raised.step, Equals(steps.PULL)) self.assertThat(raised.part, Equals("part2")) self.assertThat(raised.report, Equals("A dependency has changed: 'part1'\n"))
def test_dependency_recursed_correctly(self): self.make_snapcraft_yaml( textwrap.dedent("""\ parts: part1: plugin: nil part2: plugin: nil after: - part1 part3: plugin: nil after: - part2 """)) snap_info = lifecycle.execute('pull', self.project_options) expected_snap_info = { 'name': 'test', 'version': 0, 'arch': [self.project_options.deb_arch], 'type': '' } self.assertThat(snap_info, Equals(expected_snap_info)) self.assertThat( self.fake_logger.output, Equals( 'Preparing to pull part1 \n' 'Pulling part1 \n' '\'part2\' has prerequisites that need to be staged: part1\n' 'Preparing to build part1 \n' 'Building part1 \n' 'Staging part1 \n' 'Preparing to pull part2 \n' 'Pulling part2 \n' '\'part3\' has prerequisites that need to be staged: part2\n' 'Preparing to build part2 \n' 'Building part2 \n' 'Staging part2 \n' 'Preparing to pull part3 \n' 'Pulling part3 \n', ))
def test_os_type_returned_by_lifecycle(self): self.make_snapcraft_yaml("""parts: part1: plugin: nil part2: plugin: nil after: - part1 """, 'type: os') snap_info = lifecycle.execute('pull', self.project_options) expected_snap_info = { 'name': 'test', 'version': 0, 'arch': [self.project_options.deb_arch], 'type': 'os' } self.assertEqual(snap_info, expected_snap_info)
def test_os_type_returned_by_lifecycle(self): project_config = self.make_snapcraft_project( textwrap.dedent("""\ parts: part1: plugin: nil part2: plugin: nil after: - part1 """), "type: os", ) snap_info = lifecycle.execute(steps.PULL, project_config) expected_snap_info = { "name": "test", "version": "1.0", "arch": [project_config.project.deb_arch], "type": "os", } self.assertThat(snap_info, Equals(expected_snap_info))
def test_dirty_stage_part_with_built_dependent_raises(self): self.make_snapcraft_yaml("""parts: part1: plugin: nil part2: plugin: nil after: [part1] """) # Stage dependency lifecycle.execute('stage', self.project_options, part_names=['part1']) # Build dependent lifecycle.execute('build', self.project_options, part_names=['part2']) # Reset logging since we only care about the following self.fake_logger = fixtures.FakeLogger(level=logging.INFO) self.useFixture(self.fake_logger) def _fake_is_dirty(self, step): return step == 'stage' # Should raise a RuntimeError about the fact that stage is dirty but # it has dependents that need it. with mock.patch.object(pluginhandler.PluginHandler, 'is_dirty', _fake_is_dirty): with self.assertRaises(RuntimeError) as raised: lifecycle.execute('stage', self.project_options, part_names=['part1']) output = self.fake_logger.output.split('\n') part1_output = [line.strip() for line in output if 'part1' in line] self.assertEqual( [ 'Skipping pull part1 (already ran)', 'Skipping build part1 (already ran)', ], part1_output) self.assertEqual( "The 'stage' step for 'part1' needs to be run again, but 'part2' " "depends upon it. Please clean the build step of 'part2' first.", str(raised.exception))
def test_core_setup_skipped_if_not_classic(self): lifecycle.init() lifecycle.execute('pull', self.project_options) self.assertThat(self.witness_path, Not(FileExists()))
def _execute( # noqa: C901 step: steps.Step, parts: str, pack_project: bool = False, output: str = None, shell: bool = False, shell_after: bool = False, destructive_mode: bool = False, **kwargs) -> "Project": _clean_provider_error() provider = "host" if destructive_mode else None build_environment = env.BuilderEnvironmentConfig(force_provider=provider) project = get_project(is_managed_host=build_environment.is_managed_host, **kwargs) echo.wrapped("Using {!r}: Project assets will be " "searched for from the {!r} directory.".format( project.info.snapcraft_yaml_file_path, os.path.relpath(project._get_snapcraft_assets_dir(), project._project_dir), )) conduct_project_sanity_check(project) if build_environment.is_managed_host or build_environment.is_host: project_config = project_loader.load_config(project) lifecycle.execute(step, project_config, parts) if pack_project: _pack(project.prime_dir, output=output) else: build_provider_class = build_providers.get_provider_for( build_environment.provider) try: build_provider_class.ensure_provider() except build_providers.errors.ProviderNotFound as provider_error: if provider_error.prompt_installable: click.echo(str(provider_error)) if click.confirm("Would you like to install it now?"): build_provider_class.setup_provider(echoer=echo) else: raise provider_error else: raise provider_error echo.info("Launching a VM.") with build_provider_class(project=project, echoer=echo) as instance: instance.mount_project() try: if shell: # shell means we want to do everything right up to the previous # step and then go into a shell instead of the requested step. # the "snap" target is a special snowflake that has not made its # way to be a proper step. previous_step = None if pack_project: previous_step = steps.PRIME elif step > steps.PULL: previous_step = step.previous_step() # steps.PULL is the first step, so we would directly shell into it. if previous_step: instance.execute_step(previous_step) elif pack_project: instance.pack_project(output=output) else: instance.execute_step(step) except Exception: _retrieve_provider_error(instance) if project.debug: instance.shell() else: echo.warning( "Run the same command again with --debug to shell into the environment " "if you wish to introspect this failure.") raise else: if shell or shell_after: instance.shell() return project