def test_stable_grade_for_stable_base(self):
        lifecycle.execute(steps.PULL, self.project_config)

        global_state = states.GlobalState.load(
            filepath=self.global_state_filepath)
        self.assertThat(global_state.get_required_grade(), Equals("stable"))
        self.fake_storeapi_get_info.mock.assert_called_once_with("core18")
Exemple #2
0
    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 test_grade_not_queried_for_if_already_set(self):
        # Set the grade
        global_state = states.GlobalState()
        global_state.set_required_grade("devel")
        global_state.save(filepath=self.global_state_filepath)

        lifecycle.execute(steps.PULL, self.project_config)

        self.fake_storeapi_get_info.mock.assert_not_called()
    def test_stable_grade_for_non_stable_base(self):
        self.fake_storeapi_get_info.mock.side_effect = SnapNotFoundError(
            snap_name="core18")
        lifecycle.execute(steps.PULL, self.project_config)

        global_state = states.GlobalState.load(
            filepath=self.global_state_filepath)
        self.assertThat(global_state.get_required_grade(), Equals("devel"))
        self.fake_storeapi_get_info.mock.assert_called_once_with("core18")
Exemple #5
0
 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()))
Exemple #6
0
 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()))
Exemple #7
0
 def test_prime_without_build_info_does_not_record(self):
     self.useFixture(
         fixtures.EnvironmentVariable("SNAPCRAFT_BUILD_INFO", None))
     project_config = self.make_snapcraft_project(
         textwrap.dedent("""\
             parts:
               test-part:
                 plugin: nil
             """))
     lifecycle.execute(steps.PRIME, project_config)
     for file_name in ("snapcraft.yaml", "manifest.yaml"):
         self.assertThat(os.path.join(steps.PRIME.name, "snap", file_name),
                         Not(FileExists()))
Exemple #8
0
    def test_pull_is_dirty_if_target_arch_changes(
        self,
        mock_install_build_snaps,
        mock_install_build_packages,
        mock_enable_cross_compilation,
    ):
        # Set the option to error on dirty/outdated steps
        with snapcraft_legacy.config.CLIConfig() as cli_config:
            cli_config.set_outdated_step_action(
                snapcraft_legacy.config.OutdatedStepAction.ERROR)

        mock_install_build_packages.return_value = []
        project_config = self.make_snapcraft_project(
            textwrap.dedent("""\
                parts:
                  part1:
                    plugin: nil
                """))

        project = Project(
            snapcraft_yaml_file_path=self.snapcraft_yaml_file_path,
            target_deb_arch="amd64",
        )
        project_config = project_loader.load_config(project)
        # Pull it with amd64
        lifecycle.execute(steps.PULL, project_config)

        # Reset logging since we only care about the following
        self.fake_logger = fixtures.FakeLogger(level=logging.INFO)
        self.useFixture(self.fake_logger)

        project = Project(
            snapcraft_yaml_file_path=self.snapcraft_yaml_file_path,
            target_deb_arch="armhf",
        )
        project_config = project_loader.load_config(project)
        # 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,
                                   steps.PULL, project_config)

        self.assertThat(self.fake_logger.output,
                        Contains("Setting target machine to 'armhf'"))

        self.assertThat(raised.step, Equals(steps.PULL))
        self.assertThat(raised.part, Equals("part1"))
        self.assertThat(
            raised.report,
            Equals("The 'deb_arch' project option appears to have changed.\n"),
        )
    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))
Exemple #10
0
 def test_clean_leaves_prime_alone_for_tried(self, mock_for_root):
     project_config = self.make_snapcraft_project(
         textwrap.dedent("""\
             parts:
               test-part:
                 plugin: nil
             """))
     lifecycle.execute(steps.PRIME, project_config)
     lifecycle.clean(project_config.project, parts=None)
     self.assertThat(
         steps.PRIME.name,
         DirExists(),
         "Expected prime directory to remain after cleaning for tried snap",
     )
Exemple #11
0
    def test_dirty_build_raises(self, mock_install_build_snaps):
        # Set the option to error on dirty/outdated steps
        with snapcraft_legacy.config.CLIConfig() as cli_config:
            cli_config.set_outdated_step_action(
                snapcraft_legacy.config.OutdatedStepAction.ERROR)

        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"))
Exemple #12
0
    def test_non_prime_and_no_version(self):
        snapcraft_yaml = fixture_setup.SnapcraftYaml(self.path, version=None)
        snapcraft_yaml.data["adopt-info"] = "test-part"
        snapcraft_yaml.update_part(
            "test-part",
            {
                "plugin": "nil",
                "override-build": "snapcraftctl set-version 1.0"
            },
        )
        self.useFixture(snapcraft_yaml)

        project = Project(
            snapcraft_yaml_file_path=snapcraft_yaml.snapcraft_yaml_file_path)
        project_config = project_loader.load_config(project)

        # This should not fail
        lifecycle.execute(steps.PULL, project_config)
Exemple #13
0
    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_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))
Exemple #15
0
    def test_no_exception_when_dependency_is_required_but_already_staged(
            self, mock_install_build_snaps):
        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, Contains("Pulling part2"))
        self.assertThat(self.fake_logger.output,
                        Not(Contains("Pulling part1")))
    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))
Exemple #17
0
    def test_dirty_stage_part_with_built_dependent_raises(self):
        # Set the option to error on dirty/outdated steps
        with snapcraft_legacy.config.CLIConfig() as cli_config:
            cli_config.set_outdated_step_action(
                snapcraft_legacy.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"))
Exemple #18
0
def _execute(  # noqa: C901
    step: steps.Step,
    parts: Sequence[str],
    pack_project: bool = False,
    output: Optional[str] = None,
    shell: bool = False,
    shell_after: bool = False,
    setup_prime_try: bool = False,
    ua_token: Optional[str] = None,
    **kwargs,
) -> "Project":
    # Cleanup any previous errors.
    _clean_provider_error()

    build_provider = get_build_provider(**kwargs)
    build_provider_flags = get_build_provider_flags(
        build_provider, ua_token=ua_token, **kwargs
    )
    apply_host_provider_flags(build_provider_flags)

    is_managed_host = build_provider == "managed-host"

    # Temporary fix to ignore target_arch.
    if kwargs.get("target_arch") is not None and build_provider in ["multipass", "lxd"]:
        echo.warning(
            "Ignoring '--target-arch' flag.  This flag requires --destructive-mode and is unsupported with Multipass and LXD build providers."
        )
        kwargs.pop("target_arch")

    project = get_project(is_managed_host=is_managed_host, **kwargs)
    conduct_project_sanity_check(project, **kwargs)

    project_path = pathlib.Path(project._project_dir)
    if project_path.name in ["build-aux", "snap"]:
        echo.warning(
            f"Snapcraft is running in directory {project_path.name!r}.  If this is the snap assets directory, please run snapcraft from {project_path.parent}."
        )

    if build_provider in ["host", "managed-host"]:
        with ua_manager.ua_manager(ua_token):
            project_config = project_loader.load_config(project)
            lifecycle.execute(step, project_config, parts)
            if pack_project:
                _pack(
                    project.prime_dir,
                    compression=project._snap_meta.compression,
                    output=output,
                )
    else:
        build_provider_class = build_providers.get_provider_for(build_provider)
        try:
            build_provider_class.ensure_provider()
        except build_providers.errors.ProviderNotFound as provider_error:
            if provider_error.prompt_installable:
                if echo.is_tty_connected() and echo.confirm(
                    "Support for {!r} needs to be set up. "
                    "Would you like to do it now?".format(provider_error.provider)
                ):
                    build_provider_class.setup_provider(echoer=echo)
                else:
                    raise provider_error
            else:
                raise provider_error

        with build_provider_class(
            project=project, echoer=echo, build_provider_flags=build_provider_flags
        ) as instance:
            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, part_names=parts)
                elif pack_project:
                    instance.pack_project(output=output)
                elif setup_prime_try:
                    instance.expose_prime()
                    instance.execute_step(step, part_names=parts)
                else:
                    instance.execute_step(step, part_names=parts)
            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