craft-application 2.2.0__tar.gz → 2.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. {craft-application-2.2.0 → craft-application-2.3.0}/PKG-INFO +4 -4
  2. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/_version.py +2 -2
  3. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/application.py +32 -8
  4. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/services/lifecycle.py +3 -0
  5. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/services/provider.py +1 -2
  6. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application.egg-info/PKG-INFO +4 -4
  7. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application.egg-info/requires.txt +3 -3
  8. {craft-application-2.2.0 → craft-application-2.3.0}/pyproject.toml +3 -3
  9. {craft-application-2.2.0 → craft-application-2.3.0}/tests/conftest.py +13 -1
  10. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/services/test_lifecycle.py +20 -0
  11. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/services/test_provider.py +0 -4
  12. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/test_application.py +64 -1
  13. {craft-application-2.2.0 → craft-application-2.3.0}/.editorconfig +0 -0
  14. {craft-application-2.2.0 → craft-application-2.3.0}/.github/.jira_sync_config.yaml +0 -0
  15. {craft-application-2.2.0 → craft-application-2.3.0}/.github/ISSUE_TEMPLATE/bug.yaml +0 -0
  16. {craft-application-2.2.0 → craft-application-2.3.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  17. {craft-application-2.2.0 → craft-application-2.3.0}/.github/ISSUE_TEMPLATE/task.yaml +0 -0
  18. {craft-application-2.2.0 → craft-application-2.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  19. {craft-application-2.2.0 → craft-application-2.3.0}/.github/release-drafter.yaml +0 -0
  20. {craft-application-2.2.0 → craft-application-2.3.0}/.github/release-drafter.yml +0 -0
  21. {craft-application-2.2.0 → craft-application-2.3.0}/.github/renovate.json5 +0 -0
  22. {craft-application-2.2.0 → craft-application-2.3.0}/.github/workflows/cla-check.yaml +0 -0
  23. {craft-application-2.2.0 → craft-application-2.3.0}/.github/workflows/docs.yaml +0 -0
  24. {craft-application-2.2.0 → craft-application-2.3.0}/.github/workflows/release-drafter.yaml +0 -0
  25. {craft-application-2.2.0 → craft-application-2.3.0}/.github/workflows/tests.yaml +0 -0
  26. {craft-application-2.2.0 → craft-application-2.3.0}/.gitignore +0 -0
  27. {craft-application-2.2.0 → craft-application-2.3.0}/.pre-commit-config.yaml +0 -0
  28. {craft-application-2.2.0 → craft-application-2.3.0}/.readthedocs.yaml +0 -0
  29. {craft-application-2.2.0 → craft-application-2.3.0}/.yamllint.yaml +0 -0
  30. {craft-application-2.2.0 → craft-application-2.3.0}/HACKING.rst +0 -0
  31. {craft-application-2.2.0 → craft-application-2.3.0}/LICENSE +0 -0
  32. {craft-application-2.2.0 → craft-application-2.3.0}/README.rst +0 -0
  33. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/__init__.py +0 -0
  34. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/commands/__init__.py +0 -0
  35. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/commands/base.py +0 -0
  36. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/commands/lifecycle.py +0 -0
  37. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/commands/other.py +0 -0
  38. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/errors.py +0 -0
  39. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/grammar.py +0 -0
  40. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/launchpad/__init__.py +0 -0
  41. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/launchpad/errors.py +0 -0
  42. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/launchpad/launchpad.py +0 -0
  43. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/launchpad/models/__init__.py +0 -0
  44. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/launchpad/models/base.py +0 -0
  45. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/launchpad/models/build.py +0 -0
  46. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/launchpad/models/code.py +0 -0
  47. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/launchpad/models/distro.py +0 -0
  48. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/launchpad/models/project.py +0 -0
  49. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/launchpad/models/recipe.py +0 -0
  50. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/launchpad/util.py +0 -0
  51. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/misc/instance_bashrc +0 -0
  52. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/models/__init__.py +0 -0
  53. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/models/base.py +0 -0
  54. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/models/constraints.py +0 -0
  55. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/models/grammar.py +0 -0
  56. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/models/metadata.py +0 -0
  57. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/models/project.py +0 -0
  58. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/py.typed +0 -0
  59. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/remote/__init__.py +0 -0
  60. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/remote/errors.py +0 -0
  61. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/remote/git.py +0 -0
  62. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/remote/utils.py +0 -0
  63. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/remote/worktree.py +0 -0
  64. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/secrets.py +0 -0
  65. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/services/__init__.py +0 -0
  66. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/services/base.py +0 -0
  67. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/services/package.py +0 -0
  68. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/services/remotebuild.py +0 -0
  69. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/services/request.py +0 -0
  70. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/services/service_factory.py +0 -0
  71. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/util/__init__.py +0 -0
  72. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/util/callbacks.py +0 -0
  73. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/util/error_formatting.py +0 -0
  74. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/util/logging.py +0 -0
  75. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/util/paths.py +0 -0
  76. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/util/platforms.py +0 -0
  77. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/util/repositories.py +0 -0
  78. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/util/snap_config.py +0 -0
  79. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/util/string.py +0 -0
  80. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application/util/yaml.py +0 -0
  81. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application.egg-info/SOURCES.txt +0 -0
  82. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application.egg-info/dependency_links.txt +0 -0
  83. {craft-application-2.2.0 → craft-application-2.3.0}/craft_application.egg-info/top_level.txt +0 -0
  84. {craft-application-2.2.0 → craft-application-2.3.0}/docs/_static/css/custom.css +0 -0
  85. {craft-application-2.2.0 → craft-application-2.3.0}/docs/conf.py +0 -0
  86. {craft-application-2.2.0 → craft-application-2.3.0}/docs/explanation/index.rst +0 -0
  87. {craft-application-2.2.0 → craft-application-2.3.0}/docs/howto/index.rst +0 -0
  88. {craft-application-2.2.0 → craft-application-2.3.0}/docs/index.rst +0 -0
  89. {craft-application-2.2.0 → craft-application-2.3.0}/docs/reference/index.rst +0 -0
  90. {craft-application-2.2.0 → craft-application-2.3.0}/docs/tutorials/index.rst +0 -0
  91. {craft-application-2.2.0 → craft-application-2.3.0}/setup.cfg +0 -0
  92. {craft-application-2.2.0 → craft-application-2.3.0}/tests/__init__.py +0 -0
  93. {craft-application-2.2.0 → craft-application-2.3.0}/tests/integration/__init__.py +0 -0
  94. {craft-application-2.2.0 → craft-application-2.3.0}/tests/integration/conftest.py +0 -0
  95. {craft-application-2.2.0 → craft-application-2.3.0}/tests/integration/data/build-secrets/secret-source-folder/source-file.txt +0 -0
  96. {craft-application-2.2.0 → craft-application-2.3.0}/tests/integration/data/build-secrets/testcraft.yaml +0 -0
  97. {craft-application-2.2.0 → craft-application-2.3.0}/tests/integration/data/invalid_projects/build-error/testcraft.yaml +0 -0
  98. {craft-application-2.2.0 → craft-application-2.3.0}/tests/integration/data/valid_projects/adoption/stderr +0 -0
  99. {craft-application-2.2.0 → craft-application-2.3.0}/tests/integration/data/valid_projects/adoption/testcraft.yaml +0 -0
  100. {craft-application-2.2.0 → craft-application-2.3.0}/tests/integration/data/valid_projects/basic/stderr +0 -0
  101. {craft-application-2.2.0 → craft-application-2.3.0}/tests/integration/data/valid_projects/basic/testcraft.yaml +0 -0
  102. {craft-application-2.2.0 → craft-application-2.3.0}/tests/integration/data/valid_projects/environment/stderr +0 -0
  103. {craft-application-2.2.0 → craft-application-2.3.0}/tests/integration/data/valid_projects/environment/testcraft.yaml +0 -0
  104. {craft-application-2.2.0 → craft-application-2.3.0}/tests/integration/data/valid_projects/grammar/src/on-amd64-to-amd64/hello.txt +0 -0
  105. {craft-application-2.2.0 → craft-application-2.3.0}/tests/integration/data/valid_projects/grammar/src/on-amd64-to-arm64/hello.txt +0 -0
  106. {craft-application-2.2.0 → craft-application-2.3.0}/tests/integration/data/valid_projects/grammar/stderr +0 -0
  107. {craft-application-2.2.0 → craft-application-2.3.0}/tests/integration/data/valid_projects/grammar/testcraft.yaml +0 -0
  108. {craft-application-2.2.0 → craft-application-2.3.0}/tests/integration/launchpad/__init__.py +0 -0
  109. {craft-application-2.2.0 → craft-application-2.3.0}/tests/integration/launchpad/conftest.py +0 -0
  110. {craft-application-2.2.0 → craft-application-2.3.0}/tests/integration/launchpad/test_anonymous_access.py +0 -0
  111. {craft-application-2.2.0 → craft-application-2.3.0}/tests/integration/services/__init__.py +0 -0
  112. {craft-application-2.2.0 → craft-application-2.3.0}/tests/integration/services/test_lifecycle.py +0 -0
  113. {craft-application-2.2.0 → craft-application-2.3.0}/tests/integration/services/test_provider.py +0 -0
  114. {craft-application-2.2.0 → craft-application-2.3.0}/tests/integration/services/test_request.py +0 -0
  115. {craft-application-2.2.0 → craft-application-2.3.0}/tests/integration/services/test_service_factory.py +0 -0
  116. {craft-application-2.2.0 → craft-application-2.3.0}/tests/integration/test_application.py +0 -0
  117. {craft-application-2.2.0 → craft-application-2.3.0}/tests/integration/test_version.py +0 -0
  118. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/__init__.py +0 -0
  119. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/commands/__init__.py +0 -0
  120. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/commands/test_base.py +0 -0
  121. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/commands/test_lifecycle.py +0 -0
  122. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/commands/test_other.py +0 -0
  123. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/conftest.py +0 -0
  124. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/launchpad/__init__.py +0 -0
  125. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/launchpad/conftest.py +0 -0
  126. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/launchpad/models/__init__.py +0 -0
  127. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/launchpad/models/test_base.py +0 -0
  128. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/launchpad/models/test_code.py +0 -0
  129. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/launchpad/test_launchpad.py +0 -0
  130. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/launchpad/test_util.py +0 -0
  131. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/models/__init__.py +0 -0
  132. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/models/project_models/basic_project.yaml +0 -0
  133. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/models/project_models/full_project.yaml +0 -0
  134. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/models/project_models/invalid_project.yaml +0 -0
  135. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/models/test_constraints.py +0 -0
  136. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/models/test_project.py +0 -0
  137. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/remote/__init__.py +0 -0
  138. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/remote/conftest.py +0 -0
  139. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/remote/test_errors.py +0 -0
  140. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/remote/test_git.py +0 -0
  141. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/remote/test_utils.py +0 -0
  142. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/remote/test_worktree.py +0 -0
  143. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/services/__init__.py +0 -0
  144. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/services/conftest.py +0 -0
  145. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/services/test_package.py +0 -0
  146. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/services/test_remotebuild.py +0 -0
  147. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/services/test_repositories.py +0 -0
  148. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/services/test_request.py +0 -0
  149. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/services/test_service_factory.py +0 -0
  150. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/test_errors.py +0 -0
  151. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/test_grammar.py +0 -0
  152. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/test_nothing.py +0 -0
  153. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/test_secrets.py +0 -0
  154. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/util/__init__.py +0 -0
  155. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/util/invalid_yaml/_README +0 -0
  156. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/util/invalid_yaml/duplicate_second_level.yaml-invalid +0 -0
  157. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/util/invalid_yaml/duplicate_top_level.yaml-invalid +0 -0
  158. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/util/invalid_yaml/unhashable.yaml-invalid +0 -0
  159. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/util/test_error_formatting.py +0 -0
  160. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/util/test_logging.py +0 -0
  161. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/util/test_paths.py +0 -0
  162. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/util/test_snap_config.py +0 -0
  163. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/util/test_string.py +0 -0
  164. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/util/test_yaml.py +0 -0
  165. {craft-application-2.2.0 → craft-application-2.3.0}/tests/unit/util/valid_yaml/empty.yaml +0 -0
  166. {craft-application-2.2.0 → craft-application-2.3.0}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: craft-application
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: A framework for *craft applications.
5
5
  Classifier: Development Status :: 5 - Production/Stable
6
6
  Classifier: Environment :: Console
@@ -30,13 +30,13 @@ Requires-Dist: typing_extensions>=4.4.0
30
30
  Provides-Extra: remote
31
31
  Requires-Dist: launchpadlib>=1.10.16; extra == "remote"
32
32
  Provides-Extra: dev
33
- Requires-Dist: coverage[toml]==7.4.3; extra == "dev"
33
+ Requires-Dist: coverage[toml]==7.4.4; extra == "dev"
34
34
  Requires-Dist: hypothesis>=6.0; extra == "dev"
35
35
  Requires-Dist: pyfakefs~=5.3; extra == "dev"
36
36
  Requires-Dist: pytest==8.1.1; extra == "dev"
37
37
  Requires-Dist: pytest-check==2.3.1; extra == "dev"
38
38
  Requires-Dist: pytest-cov==5.0.0; extra == "dev"
39
- Requires-Dist: pytest-mock==3.12.0; extra == "dev"
39
+ Requires-Dist: pytest-mock==3.14.0; extra == "dev"
40
40
  Requires-Dist: pytest-rerunfailures==13.0; extra == "dev"
41
41
  Requires-Dist: pytest-time>=0.3.1; extra == "dev"
42
42
  Requires-Dist: responses~=0.25.0; extra == "dev"
@@ -47,7 +47,7 @@ Requires-Dist: codespell[toml]==2.2.6; extra == "lint"
47
47
  Requires-Dist: yamllint==1.35.1; extra == "lint"
48
48
  Provides-Extra: types
49
49
  Requires-Dist: mypy[reports]==1.9.0; extra == "types"
50
- Requires-Dist: pyright==1.1.353; extra == "types"
50
+ Requires-Dist: pyright==1.1.356; extra == "types"
51
51
  Requires-Dist: types-requests; extra == "types"
52
52
  Requires-Dist: types-urllib3; extra == "types"
53
53
  Provides-Extra: docs
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '2.2.0'
16
- __version_tuple__ = version_tuple = (2, 2, 0)
15
+ __version__ = version = '2.3.0'
16
+ __version_tuple__ = version_tuple = (2, 3, 0)
@@ -130,6 +130,7 @@ class Application:
130
130
  # When build_secrets are enabled, this contains the secret info to pass to
131
131
  # managed instances.
132
132
  self._secrets: secrets.BuildSecrets | None = None
133
+ self._partitions: list[str] | None = None
133
134
  # Cached project object, allows only the first time we load the project
134
135
  # to specify things like the project directory.
135
136
  # This is set as a private attribute in order to discourage real application
@@ -219,6 +220,7 @@ class Application:
219
220
  cache_dir=self.cache_dir,
220
221
  work_dir=self._work_dir,
221
222
  build_plan=self._build_plan,
223
+ partitions=self._partitions,
222
224
  )
223
225
  self.services.set_kwargs(
224
226
  "provider",
@@ -288,6 +290,9 @@ class Application:
288
290
  GrammarAwareProject.validate_grammar(yaml_data)
289
291
 
290
292
  build_on = host_arch
293
+
294
+ # Setup partitions, some projects require the yaml data, most will not
295
+ self._partitions = self._setup_partitions(yaml_data)
291
296
  yaml_data = self._transform_project_yaml(yaml_data, build_on, build_for)
292
297
  self.__project = self.app.ProjectClass.from_yaml_data(yaml_data, project_path)
293
298
 
@@ -344,14 +349,19 @@ class Application:
344
349
  with self.services.provider.instance(
345
350
  build_info, work_dir=self._work_dir
346
351
  ) as instance:
352
+ cmd = [self.app.name, *sys.argv[1:]]
353
+ craft_cli.emit.debug(
354
+ f"Executing {cmd} in instance location {instance_path} with {extra_args}."
355
+ )
347
356
  try:
348
- # Pyright doesn't fully understand craft_providers's CompletedProcess.
349
- instance.execute_run( # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
350
- [self.app.name, *sys.argv[1:]],
351
- cwd=instance_path,
352
- check=True,
353
- **extra_args,
354
- )
357
+ with craft_cli.emit.pause():
358
+ # Pyright doesn't fully understand craft_providers's CompletedProcess.
359
+ instance.execute_run( # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
360
+ cmd,
361
+ cwd=instance_path,
362
+ check=True,
363
+ **extra_args,
364
+ )
355
365
  except subprocess.CalledProcessError as exc:
356
366
  raise craft_providers.ProviderError(
357
367
  f"Failed to execute {self.app.name} in instance."
@@ -603,18 +613,32 @@ class Application:
603
613
  def _expand_environment(self, yaml_data: dict[str, Any]) -> None:
604
614
  """Perform expansion of project environment variables."""
605
615
  environment_vars = self._get_project_vars(yaml_data)
616
+ project_dirs = craft_parts.ProjectDirs(
617
+ work_dir=self._work_dir, partitions=self._partitions
618
+ )
619
+
606
620
  info = craft_parts.ProjectInfo(
607
621
  application_name=self.app.name, # not used in environment expansion
608
622
  cache_dir=pathlib.Path(), # not used in environment expansion
609
623
  project_name=yaml_data.get("name", ""),
610
- project_dirs=craft_parts.ProjectDirs(work_dir=self._work_dir),
624
+ project_dirs=project_dirs,
611
625
  project_vars=environment_vars,
626
+ partitions=self._partitions,
612
627
  )
613
628
 
614
629
  self._set_global_environment(info)
615
630
 
616
631
  craft_parts.expand_environment(yaml_data, info=info)
617
632
 
633
+ def _setup_partitions(self, yaml_data: dict[str, Any]) -> list[str] | None:
634
+ """Return partitions to be used.
635
+
636
+ When returning you will also need to ensure that the feature is enabled
637
+ on Application instantiation craft_parts.Features(partitions_enabled=True)
638
+ """
639
+ _ = yaml_data
640
+ return None
641
+
618
642
  def _get_project_vars(self, yaml_data: dict[str, Any]) -> dict[str, str]:
619
643
  """Return a dict with project variables to be expanded."""
620
644
  pvars: dict[str, str] = {}
@@ -134,12 +134,14 @@ class LifecycleService(base.ProjectService):
134
134
  work_dir: Path | str,
135
135
  cache_dir: Path | str,
136
136
  build_plan: list[models.BuildInfo],
137
+ partitions: list[str] | None = None,
137
138
  **lifecycle_kwargs: Any, # noqa: ANN401 - eventually used in an Any
138
139
  ) -> None:
139
140
  super().__init__(app, services, project=project)
140
141
  self._work_dir = work_dir
141
142
  self._cache_dir = cache_dir
142
143
  self._build_plan = build_plan
144
+ self._partitions = partitions
143
145
  self._manager_kwargs = lifecycle_kwargs
144
146
  self._lcm: LifecycleManager = None # type: ignore[assignment]
145
147
 
@@ -190,6 +192,7 @@ class LifecycleService(base.ProjectService):
190
192
  project_vars_part_name=self._project.adopt_info,
191
193
  project_vars=self._project_vars,
192
194
  track_stage_packages=True,
195
+ partitions=self._partitions,
193
196
  **self._manager_kwargs,
194
197
  )
195
198
  except PartsError as err:
@@ -133,8 +133,7 @@ class ProviderService(base.ProjectService):
133
133
  emit.debug("Instance launched and working directory mounted")
134
134
  self._setup_instance_bashrc(instance)
135
135
  try:
136
- with emit.pause():
137
- yield instance
136
+ yield instance
138
137
  finally:
139
138
  self._capture_logs_from_instance(instance)
140
139
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: craft-application
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: A framework for *craft applications.
5
5
  Classifier: Development Status :: 5 - Production/Stable
6
6
  Classifier: Environment :: Console
@@ -30,13 +30,13 @@ Requires-Dist: typing_extensions>=4.4.0
30
30
  Provides-Extra: remote
31
31
  Requires-Dist: launchpadlib>=1.10.16; extra == "remote"
32
32
  Provides-Extra: dev
33
- Requires-Dist: coverage[toml]==7.4.3; extra == "dev"
33
+ Requires-Dist: coverage[toml]==7.4.4; extra == "dev"
34
34
  Requires-Dist: hypothesis>=6.0; extra == "dev"
35
35
  Requires-Dist: pyfakefs~=5.3; extra == "dev"
36
36
  Requires-Dist: pytest==8.1.1; extra == "dev"
37
37
  Requires-Dist: pytest-check==2.3.1; extra == "dev"
38
38
  Requires-Dist: pytest-cov==5.0.0; extra == "dev"
39
- Requires-Dist: pytest-mock==3.12.0; extra == "dev"
39
+ Requires-Dist: pytest-mock==3.14.0; extra == "dev"
40
40
  Requires-Dist: pytest-rerunfailures==13.0; extra == "dev"
41
41
  Requires-Dist: pytest-time>=0.3.1; extra == "dev"
42
42
  Requires-Dist: responses~=0.25.0; extra == "dev"
@@ -47,7 +47,7 @@ Requires-Dist: codespell[toml]==2.2.6; extra == "lint"
47
47
  Requires-Dist: yamllint==1.35.1; extra == "lint"
48
48
  Provides-Extra: types
49
49
  Requires-Dist: mypy[reports]==1.9.0; extra == "types"
50
- Requires-Dist: pyright==1.1.353; extra == "types"
50
+ Requires-Dist: pyright==1.1.356; extra == "types"
51
51
  Requires-Dist: types-requests; extra == "types"
52
52
  Requires-Dist: types-urllib3; extra == "types"
53
53
  Provides-Extra: docs
@@ -17,13 +17,13 @@ typing_extensions>=4.4.0
17
17
  python-apt>=2.4.0
18
18
 
19
19
  [dev]
20
- coverage[toml]==7.4.3
20
+ coverage[toml]==7.4.4
21
21
  hypothesis>=6.0
22
22
  pyfakefs~=5.3
23
23
  pytest==8.1.1
24
24
  pytest-check==2.3.1
25
25
  pytest-cov==5.0.0
26
- pytest-mock==3.12.0
26
+ pytest-mock==3.14.0
27
27
  pytest-rerunfailures==13.0
28
28
  pytest-time>=0.3.1
29
29
  responses~=0.25.0
@@ -49,6 +49,6 @@ launchpadlib>=1.10.16
49
49
 
50
50
  [types]
51
51
  mypy[reports]==1.9.0
52
- pyright==1.1.353
52
+ pyright==1.1.356
53
53
  types-requests
54
54
  types-urllib3
@@ -45,13 +45,13 @@ remote = [
45
45
  "launchpadlib>=1.10.16",
46
46
  ]
47
47
  dev = [
48
- "coverage[toml]==7.4.3",
48
+ "coverage[toml]==7.4.4",
49
49
  "hypothesis>=6.0",
50
50
  "pyfakefs~=5.3",
51
51
  "pytest==8.1.1",
52
52
  "pytest-check==2.3.1",
53
53
  "pytest-cov==5.0.0",
54
- "pytest-mock==3.12.0",
54
+ "pytest-mock==3.14.0",
55
55
  "pytest-rerunfailures==13.0",
56
56
  "pytest-time>=0.3.1",
57
57
  "responses~=0.25.0",
@@ -64,7 +64,7 @@ lint = [
64
64
  ]
65
65
  types = [
66
66
  "mypy[reports]==1.9.0",
67
- "pyright==1.1.353",
67
+ "pyright==1.1.356",
68
68
  "types-requests",
69
69
  "types-urllib3",
70
70
  ]
@@ -132,13 +132,25 @@ def full_build_plan(mocker) -> list[models.BuildInfo]:
132
132
  return build_plan
133
133
 
134
134
 
135
+ @pytest.fixture()
136
+ def enable_partitions() -> Iterator[craft_parts.Features]:
137
+ """Enable the partitions feature in craft_parts for the relevant test."""
138
+ enable_overlay = craft_parts.Features().enable_overlay
139
+
140
+ craft_parts.Features.reset()
141
+ yield craft_parts.Features(enable_overlay=enable_overlay, enable_partitions=True)
142
+ craft_parts.Features.reset()
143
+
144
+
135
145
  @pytest.fixture()
136
146
  def enable_overlay() -> Iterator[craft_parts.Features]:
137
147
  """Enable the overlay feature in craft_parts for the relevant test."""
138
148
  if not os.getenv("CI") and not shutil.which("fuse-overlayfs"):
139
149
  pytest.skip("fuse-overlayfs not installed, skipping overlay tests.")
150
+
151
+ enable_partitions = craft_parts.Features().enable_partitions
140
152
  craft_parts.Features.reset()
141
- yield craft_parts.Features(enable_overlay=True)
153
+ yield craft_parts.Features(enable_overlay=True, enable_partitions=enable_partitions)
142
154
  craft_parts.Features.reset()
143
155
 
144
156
 
@@ -306,6 +306,26 @@ def test_init_with_feature_package_repositories(
306
306
  assert service._lcm._project_info.package_repositories == package_repositories
307
307
 
308
308
 
309
+ @pytest.mark.usefixtures("enable_partitions")
310
+ def test_init_with_partitions(
311
+ app_metadata, fake_project, fake_services, tmp_path, fake_build_plan
312
+ ):
313
+ service = lifecycle.LifecycleService(
314
+ app_metadata,
315
+ fake_services,
316
+ project=fake_project,
317
+ work_dir=tmp_path,
318
+ cache_dir=tmp_path,
319
+ platform=None,
320
+ build_plan=fake_build_plan,
321
+ partitions=["default", "mypartition"],
322
+ )
323
+ assert service._lcm is None
324
+ service.setup()
325
+ assert service._lcm is not None
326
+ assert service._lcm._project_info.partitions == ["default", "mypartition"]
327
+
328
+
309
329
  def test_prime_dir(lifecycle_service, tmp_path):
310
330
  prime_dir = lifecycle_service.prime_dir
311
331
 
@@ -290,7 +290,6 @@ def test_instance(
290
290
  provider_service,
291
291
  base_name,
292
292
  allow_unstable,
293
- mocker,
294
293
  ):
295
294
  mock_provider = mock.MagicMock(spec=craft_providers.Provider)
296
295
  monkeypatch.setattr(
@@ -298,7 +297,6 @@ def test_instance(
298
297
  "get_provider",
299
298
  lambda name: mock_provider, # noqa: ARG005 (unused argument)
300
299
  )
301
- spy_pause = mocker.spy(provider.emit, "pause")
302
300
  arch = util.get_host_architecture()
303
301
  build_info = models.BuildInfo("foo", arch, arch, base_name)
304
302
 
@@ -326,8 +324,6 @@ def test_instance(
326
324
  )
327
325
  with check:
328
326
  emitter.assert_progress("Launching managed .+ instance...", regex=True)
329
- with check:
330
- assert spy_pause.call_count == 1
331
327
 
332
328
 
333
329
  def test_load_bashrc(emitter):
@@ -237,11 +237,12 @@ def test_log_path(monkeypatch, app, provider_managed, expected):
237
237
  assert actual == expected
238
238
 
239
239
 
240
- def test_run_managed_success(app, fake_project, fake_build_plan):
240
+ def test_run_managed_success(mocker, app, fake_project, fake_build_plan):
241
241
  mock_provider = mock.MagicMock(spec_set=services.ProviderService)
242
242
  app.services.provider = mock_provider
243
243
  app.project = fake_project
244
244
  app._build_plan = fake_build_plan
245
+ mock_pause = mocker.spy(craft_cli.emit, "pause")
245
246
  arch = get_host_architecture()
246
247
 
247
248
  app.run_managed(None, arch)
@@ -253,6 +254,7 @@ def test_run_managed_success(app, fake_project, fake_build_plan):
253
254
  )
254
255
  in mock_provider.instance.mock_calls
255
256
  )
257
+ mock_pause.assert_called_once()
256
258
 
257
259
 
258
260
  def test_run_managed_failure(app, fake_project, fake_build_plan):
@@ -1176,3 +1178,64 @@ def test_process_grammar_no_match(grammar_app, mocker):
1176
1178
  project = grammar_app.get_project()
1177
1179
  # "source" is empty because "i386" doesn't match any of the grammar statements.
1178
1180
  assert project.parts["mypart"]["source"] is None
1181
+
1182
+
1183
+ class FakePartitionsApplication(FakeApplication):
1184
+ """A partition using FakeApplication."""
1185
+
1186
+ @override
1187
+ def _setup_partitions(self, yaml_data) -> list[str]:
1188
+ _ = yaml_data
1189
+ return ["default", "mypartition"]
1190
+
1191
+
1192
+ @pytest.fixture()
1193
+ def environment_partitions_project(monkeypatch, tmp_path):
1194
+ project_dir = tmp_path / "project"
1195
+ project_dir.mkdir()
1196
+ project_path = project_dir / "testcraft.yaml"
1197
+ project_path.write_text(
1198
+ dedent(
1199
+ """
1200
+ name: myproject
1201
+ version: 1.2.3
1202
+ parts:
1203
+ mypart:
1204
+ plugin: nil
1205
+ source-tag: v$CRAFT_PROJECT_VERSION
1206
+ override-stage: |
1207
+ touch $CRAFT_STAGE/default
1208
+ touch $CRAFT_MYPARTITION_STAGE/partition
1209
+ override-prime: |
1210
+ touch $CRAFT_PRIME/default
1211
+ touch $CRAFT_MYPARTITION_PRIME/partition
1212
+ """
1213
+ )
1214
+ )
1215
+ monkeypatch.chdir(project_dir)
1216
+
1217
+ return project_path
1218
+
1219
+
1220
+ @pytest.mark.usefixtures("enable_partitions")
1221
+ @pytest.mark.usefixtures("environment_partitions_project")
1222
+ def test_partition_application_expand_environment(app_metadata, fake_services):
1223
+ app = FakePartitionsApplication(app_metadata, fake_services)
1224
+ project = app.get_project(build_for=get_host_architecture())
1225
+
1226
+ assert craft_parts.Features().enable_partitions is True
1227
+ # Make sure the project is loaded correctly (from the cwd)
1228
+ assert project is not None
1229
+ assert project.parts["mypart"]["source-tag"] == "v1.2.3"
1230
+ assert project.parts["mypart"]["override-stage"] == dedent(
1231
+ f"""\
1232
+ touch {app.project_dir}/stage/default
1233
+ touch {app.project_dir}/partitions/mypartition/stage/partition
1234
+ """
1235
+ )
1236
+ assert project.parts["mypart"]["override-prime"] == dedent(
1237
+ f"""\
1238
+ touch {app.project_dir}/prime/default
1239
+ touch {app.project_dir}/partitions/mypartition/prime/partition
1240
+ """
1241
+ )