コード例 #1
0
    def test_missing_keys(self):
        snap_dict = OrderedDict({"name": "snap-test", "grade": "stable"})

        snap = Snap.from_dict(snap_dict=snap_dict)

        self.assertEqual(snap_dict, snap.to_dict())
        self.assertRaises(errors.MissingSnapcraftYamlKeysError, snap.validate)
コード例 #2
0
    def test_snap_yaml_passthrough(self):
        snap_dict = OrderedDict({
            "name": "snap-test",
            "version": "snap-version",
            "summary": "snap-summary",
            "description": "snap-description",
            "passthrough": {
                "otherkey": "othervalue"
            },
            "grade": "stable",
        })

        snap = Snap.from_dict(snap_dict=snap_dict)
        snap.validate()

        transformed_dict = snap_dict.copy()
        passthrough = transformed_dict.pop("passthrough")
        transformed_dict.update(passthrough)

        self.assertEqual(transformed_dict, snap.to_snap_yaml_dict())
        self.assertEqual(True, snap.is_passthrough_enabled)
        self.assertEqual(passthrough, snap.passthrough)
        self.assertEqual(snap_dict["name"], snap.name)
        self.assertEqual(snap_dict["version"], snap.version)
        self.assertEqual(snap_dict["summary"], snap.summary)
        self.assertEqual(snap_dict["description"], snap.description)
コード例 #3
0
def test_setup_environment_content_x86(tmp_work_path, monkeypatch,
                                       machine_platform, distro):
    snapcraft_project = Project()
    snapcraft_project._snap_meta = Snap(name="test-snap", base=distro[0])

    monkeypatch.setattr(platform, "machine", lambda: machine_platform[0])

    recorded_files = dict()

    @contextlib.contextmanager
    def fake_namedtempfile(*, suffix: str, **kwargs):
        # Usage hides the file basename in the suffix.
        tmp_path = os.path.join("tmpfile")
        with open(tmp_path, "wb") as f_write:
            yield f_write
        with open(tmp_path, "r") as f_read:
            recorded_files[suffix] = f_read.read()

    monkeypatch.setattr(tempfile, "NamedTemporaryFile", fake_namedtempfile)

    provider = ProviderImpl(project=snapcraft_project, echoer=Mock())
    provider._setup_environment()

    assert recorded_files == {
        ".bashrc":
        '#!/bin/bash\nexport PS1="\\h \\$(/bin/_snapcraft_prompt)# "\n',
        "00-snapcraft":
        'Apt::Install-Recommends "false";\n',
        "_snapcraft_prompt":
        dedent("""\
            #!/bin/bash
            if [[ "$PWD" =~ ^$HOME.* ]]; then
                path="${PWD/#$HOME/\\ ..}"
                if [[ "$path" == " .." ]]; then
                    ps1=""
                else
                    ps1="$path"
                fi
            else
                ps1="$PWD"
            fi
            echo -n $ps1
            """),
        "default.sources":
        dedent(f"""\
                Types: deb
                URIs: {machine_platform[1]["main"]}
                Suites: {distro[1]} {distro[1]}-updates
                Components: main multiverse restricted universe
            """),
        "default-security.sources":
        dedent(f"""\
                Types: deb
                URIs: {machine_platform[1]["security"]}
                Suites: {distro[1]}-security
                Components: main multiverse restricted universe
            """),
        "sources.list":
        "",
    }
コード例 #4
0
ファイル: _config.py プロジェクト: snapcore/snapcraft
    def __init__(self, project: project.Project) -> None:
        self.build_snaps: Set[str] = set()
        self.project = project

        # raw_snapcraft_yaml is read only, create a new copy
        snapcraft_yaml = apply_extensions(project.info.get_raw_snapcraft())

        self.validator = Validator(snapcraft_yaml)
        self.validator.validate()

        snapcraft_yaml = self._expand_filesets(snapcraft_yaml)

        self.data = self._expand_env(snapcraft_yaml)

        self.data["architectures"] = _process_architectures(
            self.data.get("architectures"), project.deb_arch)

        self._ensure_no_duplicate_app_aliases()

        self._global_grammar_processor = grammar_processing.GlobalGrammarProcessor(
            properties=self.data,
            arch=project.host_deb_arch,
            target_arch=project.target_arch,
        )

        # XXX: Resetting snap_meta due to above mangling of data.
        # Convergence to operating on snap_meta will remove this requirement...
        project._snap_meta = Snap.from_dict(self.data)

        self.parts = PartsConfig(parts=self.data,
                                 project=project,
                                 validator=self.validator)
コード例 #5
0
    def test_system_usernames_longform_scope(self):
        snap_dict = OrderedDict({
            "name": "snap-test",
            "version": "test-version",
            "summary": "test-summary",
            "description": "test-description",
            "system-usernames": {
                "snap_daemon": {
                    "scope": "shared"
                },
                "lxd": {
                    "scope": "shared"
                },
            },
        })

        snap = Snap.from_dict(snap_dict=snap_dict)
        snap.validate()

        self.assertThat(snap.system_usernames["snap_daemon"].name,
                        Equals("snap_daemon"))
        self.assertThat(snap.system_usernames["snap_daemon"].scope,
                        Equals(SystemUserScope.SHARED))
        self.assertThat(snap.system_usernames["lxd"].name, Equals("lxd"))
        self.assertThat(snap.system_usernames["lxd"].scope,
                        Equals(SystemUserScope.SHARED))
コード例 #6
0
    def setUp(self):
        super().setUp()

        self.project = Project()
        self.project._snap_meta = Snap(name="test-snap",
                                       base="core18",
                                       confinement="strict")
コード例 #7
0
ファイル: __init__.py プロジェクト: snapcore/snapcraft
def get_project(base: str = "core20") -> Project:
    project = Project()
    project._snap_meta = Snap(name="project-name",
                              base=base,
                              version="1.0",
                              confinement="strict")
    return project
コード例 #8
0
    def __init__(
        self,
        project_config: _config.Config,
        extracted_metadata: Optional[_metadata.ExtractedMetadata],
    ) -> None:
        self._project_config = project_config
        self._extracted_metadata = extracted_metadata
        self._snapcraft_yaml_path = project_config.project.info.snapcraft_yaml_file_path
        self._prime_dir = project_config.project.prime_dir
        self._parts_dir = project_config.project.parts_dir

        self._arch_triplet = project_config.project.arch_triplet
        self.meta_dir = os.path.join(self._prime_dir, "meta")
        self.meta_gui_dir = os.path.join(self.meta_dir, "gui")
        self._config_data = project_config.data.copy()
        self._original_snapcraft_yaml = project_config.project.info.get_raw_snapcraft(
        )

        self._install_path_pattern = re.compile(
            r"{}/[a-z0-9][a-z0-9+-]*/install".format(re.escape(
                self._parts_dir)))

        os.makedirs(self.meta_dir, exist_ok=True)

        # TODO: create_snap_packaging managles config data, so we create
        # a new private instance of snap_meta.  Longer term, this needs
        # to converge with project's snap_meta.
        self._snap_meta = Snap.from_dict(project_config.data)
コード例 #9
0
def get_project(*, is_managed_host: bool = False, **kwargs):
    # We need to do this here until we can get_snapcraft_yaml as part of Project.
    if is_managed_host:
        try:
            os.chdir(os.path.expanduser(os.path.join("~", "project")))
        except FileNotFoundError:
            # No project found (fresh environment).
            raise errors.ProjectNotFoundError()

    snapcraft_yaml_file_path = get_snapcraft_yaml()

    # This method may be called from a click.Command with no parent.
    ctx = click.get_current_context()
    if ctx.parent is not None:
        for key, value in ctx.parent.params.items():
            if not kwargs.get(key):
                kwargs[key] = value

    project = Project(
        debug=kwargs.pop("debug", False),
        target_deb_arch=kwargs.pop("target_arch", None),
        snapcraft_yaml_file_path=snapcraft_yaml_file_path,
        is_managed_host=is_managed_host,
    )

    # Validate yaml info from schema prior to consumption.
    if project.info is not None:
        project.info.validate_raw_snapcraft()

        # TODO: this should be automatic on get_project().
        # This is not the complete meta parsed by the project loader.
        project._snap_meta = Snap.from_dict(project.info.get_raw_snapcraft())

    return project
コード例 #10
0
def project(monkeypatch, tmp_work_path, request):
    """Return project variants for core and core18"""
    monkeypatch.setattr(Project, "parallel_build_count", 2)

    snapcraft_project = Project()
    snapcraft_project._snap_meta = Snap(name="test-snap",
                                        base="core18",
                                        confinement="strict")
    return snapcraft_project
コード例 #11
0
    def test_get_provider_content_directories_with_content_plugs(self):
        snap_dict = OrderedDict({
            "name": "snap-test",
            "version": "test-version",
            "summary": "test-summary",
            "description": "test-description",
            "plugs": {
                "test-plug": {
                    "interface": "content",
                    "content": "content",
                    "target": "target",
                    "default-provider": "gtk-common-themes:gtk-3-themes",
                }
            },
        })

        meta_snap_yaml = dedent("""
            name: test-content-snap-meta-snap-yaml
            version: "1.0"
            summary: test-summary
            description: test-description
            base: core18
            architectures:
            - all
            confinement: strict
            grade: stable
            slots:
              test-slot-name:
                interface: content
                source:
                  read:
                  - $SNAP/dir1
                  - $SNAP/dir2
        """)

        snap = Snap.from_dict(snap_dict=snap_dict)
        snap.validate()

        patcher = mock.patch(
            "snapcraft_legacy.internal.common.get_installed_snap_path")
        mock_core_path = patcher.start()
        mock_core_path.return_value = self.path
        self.addCleanup(patcher.stop)

        meta_path = os.path.join(self.path, "meta")
        os.makedirs(meta_path)

        snap_yaml_path = os.path.join(meta_path, "snap.yaml")
        open(snap_yaml_path, "w").write(meta_snap_yaml)

        expected_content_dirs = set(
            [os.path.join(self.path, "dir1"),
             os.path.join(self.path, "dir2")])

        self.assertEqual(expected_content_dirs,
                         snap.get_provider_content_directories())
コード例 #12
0
ファイル: test_maven.py プロジェクト: snapcore/snapcraft
    def test_use_invalid_openjdk_version_fails(self, base, version, expected_message):
        class Options:
            maven_options = []
            maven_targets = [""]
            maven_version = maven._DEFAULT_MAVEN_VERSION
            maven_version_checksum = maven._DEFAULT_MAVEN_CHECKSUM
            maven_openjdk_version = version

        project = Project()
        project._snap_meta = Snap(name="test-snap", base=base, confinement="strict")

        with pytest.raises(maven.UnsupportedJDKVersionError) as error:
            maven.MavenPlugin("test-part", Options(), project)
            assert str(error) == expected_message
コード例 #13
0
ファイル: _project.py プロジェクト: snapcore/snapcraft
    def __init__(
        self,
        *,
        target_deb_arch=None,
        debug=False,
        snapcraft_yaml_file_path=None,
        work_dir: str = None,
        is_managed_host: bool = False
    ) -> None:

        project_dir = os.getcwd()
        if is_managed_host:
            work_dir = os.path.expanduser("~")
        else:
            work_dir = project_dir

        super().__init__(target_deb_arch, debug, work_dir=work_dir)

        # This here check is mostly for backwards compatibility with the
        # rest of the code base.
        if snapcraft_yaml_file_path is None:
            self.info: ProjectInfo = None  # type: ignore

        else:
            self.info = ProjectInfo(snapcraft_yaml_file_path=snapcraft_yaml_file_path)

        self._is_managed_host = is_managed_host
        self._project_dir = project_dir
        self._work_dir = work_dir

        self.local_plugins_dir = self._get_local_plugins_dir()
        self._start_time = datetime.utcnow()

        # XXX: (Re)set by Config because it mangles source data.
        # Ideally everywhere wold converge to operating on snap_meta, and ww
        # would only need to initialize it once (properly).
        self._snap_meta = Snap()
コード例 #14
0
ファイル: test_ant.py プロジェクト: snapcore/snapcraft
    def test_use_invalid_openjdk_version_fails(self, base, version, expected_message):
        class Options:
            ant_properties = {}
            ant_build_targets = None
            ant_channel = None
            ant_version = "1.10.5"
            ant_version_checksum = "sha512/a7f1e0cec9d5ed1b3ab6cddbb9364f127305a997bbc88ecd734f9ef142ec0332375e01ace3592759bb5c3307cd9c1ac0a78a30053f304c7030ea459498e4ce4e"
            ant_openjdk_version = version

        project = Project()
        project._snap_meta = Snap(name="test-snap", base=base, confinement="strict")

        with pytest.raises(ant.UnsupportedJDKVersionError) as error:
            ant.AntPlugin("test-part", Options(), project)
            assert str(error) == expected_message
コード例 #15
0
ファイル: test_maven.py プロジェクト: snapcore/snapcraft
def maven_plugin(tmp_work_path, request):
    """Return an instance of MavenPlugin setup with different bases and java versions."""
    java_version, base = request.param

    class Options:
        maven_options = []
        maven_targets = [""]
        maven_version = maven._DEFAULT_MAVEN_VERSION
        maven_version_checksum = maven._DEFAULT_MAVEN_CHECKSUM
        maven_openjdk_version = java_version

    project = Project()
    project._snap_meta = Snap(name="test-snap", base=base, confinement="strict")

    return maven.MavenPlugin("test-part", Options(), project)
コード例 #16
0
ファイル: test_ant.py プロジェクト: snapcore/snapcraft
def ant_plugin(tmp_work_path, request):
    """Return an instance of AntPlugin setup with different bases and java versions."""
    java_version, base = request.param

    class Options:
        ant_properties = {}
        ant_build_targets = None
        ant_channel = None
        ant_version = "1.10.5"
        ant_version_checksum = "sha512/a7f1e0cec9d5ed1b3ab6cddbb9364f127305a997bbc88ecd734f9ef142ec0332375e01ace3592759bb5c3307cd9c1ac0a78a30053f304c7030ea459498e4ce4e"
        ant_openjdk_version = java_version
        ant_buildfile = None

    project = Project()
    project._snap_meta = Snap(name="test-snap", base=base, confinement="strict")

    return ant.AntPlugin("test-part", Options(), project)
コード例 #17
0
    def test_use_invalid_openjdk_version_fails(self, base, version,
                                               expected_message):
        class Options:
            gradle_options = []
            gradle_output_dir = "build/libs"
            gradle_version = gradle._DEFAULT_GRADLE_VERSION
            gradle_version_checksum = gradle._DEFAULT_GRADLE_CHECKSUM
            gradle_openjdk_version = version

        project = Project()
        project._snap_meta = Snap(name="test-snap",
                                  base=base,
                                  confinement="strict")

        with pytest.raises(gradle.UnsupportedJDKVersionError) as error:
            gradle.GradlePlugin("test-part", Options(), project)
            assert str(error) == expected_message
コード例 #18
0
def gradle_plugin(tmp_path, request):
    """Return an instance of GradlePlugin setup with different bases and java versions."""
    java_version, base = request.param

    class Options:
        gradle_options = []
        gradle_output_dir = "build/libs"
        gradle_version = gradle._DEFAULT_GRADLE_VERSION
        gradle_version_checksum = gradle._DEFAULT_GRADLE_CHECKSUM
        gradle_openjdk_version = java_version

    os.chdir(tmp_path)
    project = Project()
    project._snap_meta = Snap(name="test-snap",
                              base=base,
                              confinement="strict")

    return gradle.GradlePlugin("test-part", Options(), project)
コード例 #19
0
    def test_simple(self):
        snap_dict = OrderedDict({
            "name": "snap-test",
            "version": "snap-version",
            "summary": "snap-summary",
            "description": "snap-description",
            "grade": "stable",
        })

        snap = Snap.from_dict(snap_dict=snap_dict)
        snap.validate()

        self.assertEqual(snap_dict, snap.to_dict())
        self.assertEqual(False, snap.is_passthrough_enabled)
        self.assertEqual(snap_dict["name"], snap.name)
        self.assertEqual(snap_dict["version"], snap.version)
        self.assertEqual(snap_dict["summary"], snap.summary)
        self.assertEqual(snap_dict["description"], snap.description)
コード例 #20
0
    def test_from_file(self):
        snap_yaml = dedent("""
            name: test-name
            version: "1.0"
            summary: test-summary
            description: test-description
            base: core18
            architectures:
            - amd64
            assumes:
            - snapd2.39
            confinement: classic
            grade: devel
            apps:
              test-app:
                command: test-command
                completer: test-completer
        """)

        meta_path = os.path.join(self.path, "meta")
        os.makedirs(meta_path)

        snap_yaml_path = os.path.join(self.path, "meta", "snap.yaml")
        open(snap_yaml_path, "w").write(snap_yaml)

        snap = Snap.from_file(snap_yaml_path)
        snap.validate()

        self.assertEqual("test-name", snap.name)
        self.assertEqual("1.0", snap.version)
        self.assertEqual("test-summary", snap.summary)
        self.assertEqual("test-description", snap.description)
        self.assertEqual(
            OrderedDict({
                "command": "test-command",
                "completer": "test-completer"
            }),
            snap.apps["test-app"].to_dict(),
        )
        self.assertEqual(["amd64"], snap.architectures)
        self.assertEqual({"snapd2.39"}, snap.assumes)
        self.assertEqual("core18", snap.base)
        self.assertEqual("classic", snap.confinement)
        self.assertEqual("devel", snap.grade)
コード例 #21
0
    def test_is_passthrough_enabled_hook(self):
        snap_dict = OrderedDict({
            "name": "snap-test",
            "version": "test-version",
            "summary": "test-summary",
            "description": "test-description",
            "hooks": {
                "test-hook": {
                    "passthrough": {
                        "some-key": "some-value"
                    }
                }
            },
        })

        snap = Snap.from_dict(snap_dict=snap_dict)
        snap.validate()

        self.assertEqual(True, snap.is_passthrough_enabled)
コード例 #22
0
    def test_grade_devel_statisfies_required_grade(self):
        self.fake_snapd.snaps_result = [{
            "name": "fake-base",
            "channel": "edge",
            "revision": "fake-revision"
        }]

        snap_dict = OrderedDict({
            "name": "snap-test",
            "base": "fake-base",
            "version": "snap-version",
            "summary": "snap-summary",
            "description": "snap-description",
            "grade": "devel",
        })

        snap = Snap.from_dict(snap_dict=snap_dict)

        snap.validate()
コード例 #23
0
    def test_conversions(self, tmp_work_path, snapcraft_yaml, snap_yaml):
        # Ordering matters for verifying the YAML.
        snapcraft_yaml_path = tmp_work_path / "snapcraft.yaml"
        with snapcraft_yaml_path.open("w") as snapcraft_file:
            print(snapcraft_yaml, file=snapcraft_file)

        snap = Snap.from_file(snapcraft_yaml_path.as_posix())
        snap.validate()

        # Write snap yaml.
        snap_yaml_path = tmp_work_path / "snap.yaml"
        snap.write_snap_yaml(path=snap_yaml_path.as_posix())

        # Read snap yaml.
        with snap_yaml_path.open() as snap_file:
            written_snap_yaml = snap_file.read()

        # Compare stripped versions (to remove leading/trailing newlines).
        assert snap_yaml.strip() == written_snap_yaml.strip()
コード例 #24
0
    def test_ensure_command_chain_assumption(self):
        snap_dict = OrderedDict({
            "name": "snap-test",
            "version": "snap-version",
            "summary": "snap-summary",
            "description": "snap-description",
            "apps": {
                "test-app": {
                    "command": "test-app",
                    "command-chain": ["test-command-chain"],
                }
            },
        })

        snap = Snap.from_dict(snap_dict=snap_dict)
        snap._ensure_command_chain_assumption()
        snap.validate()

        self.assertEqual({"command-chain"}, snap.assumes)
コード例 #25
0
    def test_snapcraft_yaml_links(self):
        snap_dict = {
            "name":
            "snap-test",
            "version":
            "test-version",
            "summary":
            "test-summary",
            "description":
            "test-description",
            "issues":
            "https://bug_url.org",
            "donation": [
                "https://paypal.com",
                "https://cafecito.app/",
                "https://ko-fi.com/",
            ],
            "contact": ["mailto:[email protected]", "*****@*****.**"],
            "source-code":
            "https://github.com/org/source",
            "website":
            "https://webfront.org",
        }

        snap = Snap.from_dict(snap_dict=snap_dict)
        snap.validate()

        self.assertEqual(
            {
                "issues": ["https://bug_url.org"],
                "donation": [
                    "https://paypal.com",
                    "https://cafecito.app/",
                    "https://ko-fi.com/",
                ],
                "contact":
                ["mailto:[email protected]", "*****@*****.**"],
                "source-code": ["https://github.com/org/source"],
                "website": ["https://webfront.org"],
            },
            snap.to_snap_yaml_dict()["links"],
        )
コード例 #26
0
    def test_build_base_and_write_snap_yaml_type_base(self):
        snap_dict = OrderedDict({
            "name": "core18",
            "version": "snap-version",
            "summary": "snap-summary",
            "description": "snap-description",
            "grade": "devel",
            "type": "base",
        })

        snap = Snap.from_dict(snap_dict=snap_dict)
        snap.validate()

        # Write snap yaml.
        snap_yaml_path = os.path.join(self.path, "snap.yaml")
        snap.write_snap_yaml(path=snap_yaml_path)

        # Read snap yaml.
        written_snap_yaml = open(snap_yaml_path, "r").read()

        self.assertEqual(snap_dict, snap.to_dict())
        self.assertTrue("base" in written_snap_yaml)
        self.assertEqual(snap.get_build_base(), "core18")
コード例 #27
0
    def test_to_file(self):
        # Ordering matters for verifying the YAML.
        snap_yaml = dedent("""
            name: test-name
            version: '1.0'
            summary: test-summary
            description: test-description
            apps:
              test-app:
                command: test-command
                completer: test-completer
            architectures:
            - amd64
            assumes:
            - snapd2.39
            base: core18
            confinement: classic
            grade: devel
        """)

        meta_path = os.path.join(self.path, "meta")
        os.makedirs(meta_path)

        snap_yaml_path = os.path.join(self.path, "meta", "snap.yaml")
        open(snap_yaml_path, "w").write(snap_yaml)

        snap = Snap.from_file(snap_yaml_path)
        snap.validate()

        # Write snap yaml.
        snap.write_snap_yaml(path=snap_yaml_path)

        # Read snap yaml.
        written_snap_yaml = open(snap_yaml_path, "r").read()

        # Compare stripped versions (to remove leading/trailing newlines).
        self.assertEqual(snap_yaml.strip(), written_snap_yaml.strip())
コード例 #28
0
 def test_get_provider_content_directories_no_plugs(self):
     snap = Snap()
     self.assertEqual(set([]), snap.get_provider_content_directories())
コード例 #29
0
ファイル: test_flutter.py プロジェクト: snapcore/snapcraft
def test_unsupported_base_raises(flutter_options):
    project = Project()
    project._snap_meta = Snap(name="test-snap", base="bad-base", confinement="strict")

    with pytest.raises(errors.PluginBaseError):
        flutter.FlutterPlugin("test-part", flutter_options, project)
コード例 #30
0
ファイル: _project.py プロジェクト: snapcore/snapcraft
class Project(ProjectOptions):
    """All details around building a project concerning the build environment
    and the snap being built."""

    def __init__(
        self,
        *,
        target_deb_arch=None,
        debug=False,
        snapcraft_yaml_file_path=None,
        work_dir: str = None,
        is_managed_host: bool = False
    ) -> None:

        project_dir = os.getcwd()
        if is_managed_host:
            work_dir = os.path.expanduser("~")
        else:
            work_dir = project_dir

        super().__init__(target_deb_arch, debug, work_dir=work_dir)

        # This here check is mostly for backwards compatibility with the
        # rest of the code base.
        if snapcraft_yaml_file_path is None:
            self.info: ProjectInfo = None  # type: ignore

        else:
            self.info = ProjectInfo(snapcraft_yaml_file_path=snapcraft_yaml_file_path)

        self._is_managed_host = is_managed_host
        self._project_dir = project_dir
        self._work_dir = work_dir

        self.local_plugins_dir = self._get_local_plugins_dir()
        self._start_time = datetime.utcnow()

        # XXX: (Re)set by Config because it mangles source data.
        # Ideally everywhere wold converge to operating on snap_meta, and ww
        # would only need to initialize it once (properly).
        self._snap_meta = Snap()

    def _get_build_base(self) -> str:
        """
        Return name for type base or the base otherwise build-base is set
        """

        # In case snap_meta is not yet populated, lookup base in info.
        if self.info:
            if self.info.build_base:
                return self.info.build_base
            if self.info.base:
                return self.info.base

        return self._snap_meta.get_build_base()

    def _get_project_directory_hash(self) -> str:
        # This function uses md5 hashes because they are fast, and because
        # the hashes only need to be unique per project, so clashes are
        # tremendously unlikely and not a big deal even if they happen.
        hashes: List[str] = list()
        for root, dirs, files in os.walk(self._project_dir):
            # Sort contents, we need this to be stable so it's reproducible
            dirs.sort()
            files.sort()

            for filename in files:
                md5_hash = hashlib.md5()
                # Read files in chunks in case they are big
                with open(os.path.join(root, filename), "rb") as f:
                    for block in iter(lambda: f.read(4096), b""):
                        md5_hash.update(block)
                hashes.append(md5_hash.hexdigest())

        # Return final hash of hashes for the directory
        return hashlib.md5("".join(hashes).encode()).hexdigest()

    def _get_content_snaps(self) -> Set[str]:
        """Return the set of content snaps from snap_meta."""
        return set(
            [
                x.provider
                for x in self._snap_meta.get_content_plugs()
                if x.provider is not None
            ]
        )

    def _get_provider_content_dirs(self) -> Set[str]:
        """Return the set installed provider content directories."""
        return self._snap_meta.get_provider_content_directories()

    def _get_snapcraft_assets_dir(self) -> str:
        # Many test cases don't set the yaml file path and assume the default dir
        if not self.info:
            return os.path.join(self._project_dir, "snap")

        if self.info.snapcraft_yaml_file_path.endswith(
            os.path.join("build-aux", "snap", "snapcraft.yaml")
        ):
            return os.path.join(self._project_dir, "build-aux", "snap")
        else:
            return os.path.join(self._project_dir, "snap")

    def _get_keys_path(self) -> Path:
        # Directory containing <KEY_ID>.asc keys for use with
        # package-repositories, relative to 'snap' assets.
        return Path(self._get_snapcraft_assets_dir(), "keys")

    def _get_local_plugins_dir(self) -> str:
        deprecated_plugins_dir = os.path.join(self._parts_dir, "plugins")
        if os.path.exists(deprecated_plugins_dir):
            handle_deprecation_notice("dn2")
            return deprecated_plugins_dir
        else:
            assets_dir = self._get_snapcraft_assets_dir()
            return os.path.join(assets_dir, "plugins")

    def _get_global_state_file_path(self) -> str:
        if self._is_managed_host:
            state_file_path = os.path.join(self._work_dir, "state")
        else:
            state_file_path = os.path.join(self._parts_dir, ".snapcraft_global_state")

        return state_file_path

    def _get_stage_packages_target_arch(self) -> str:
        """Get architecture for staging packages.

        Prior to core20, staging packages has broken behavior in that it will
        stage native architecture packages by default.

        :return: The appropriate default architecture to stage.
        """
        if self._get_build_base() == "core18":
            return self.deb_arch
        else:
            return self.target_arch

    def _get_start_time(self) -> datetime:
        """Returns the timestamp for when a snapcraft project was loaded."""
        return self._start_time