Repository: canonical/charmcraft

Issue:
flask-framework charm builds failing due to dependency
### Bug Description

Building a flask-framework charm is now failing due to indirect rust-based dependency issues requiring a newer version of rust.

### To Reproduce

1. `charmcraft init --profile=flask-framework`
2. `charmcraft pack`

### Environment

Ubuntu 24.04 with charmcraft edge

### charmcraft.yaml

```shell
# This file configures Charmcraft.
# See https://juju.is/docs/sdk/charmcraft-config for guidance.

name: ff-charm

type: charm

bases:
  - build-on:
    - name: ubuntu
      channel: "22.04"
    run-on:
    - name: ubuntu
      channel: "22.04"

# (Required)
summary: A very short one-line summary of the Flask application.

# (Required)
description: |
  A comprehensive overview of your Flask application.

extensions:
  - flask-framework
```


### Relevant log output

```shell
::    ::   Created wheel for attrs: filename=attrs-23.2.0-py3-none-any.whl size=60851 sha256=2ae03c2409587c42894bd2e9189ad977b1af7cb0cc6341914ffe30e1f73b84a5                                              
::    ::   Stored in directory: /root/.cache/pip/wheels/61/05/28/80f4ae2dc3db1710750af2cbc3f4efb6c0435097d9ca802dd1                                                                                        
::    ::   Building wheel for jsonschema-specifications (pyproject.toml): started                                                                                                                          
::    ::   Building wheel for jsonschema-specifications (pyproject.toml): finished with status 'done'                                                                                                      
::    ::   Created wheel for jsonschema-specifications: filename=jsonschema_specifications-2023.12.1-py3-none-any.whl size=18485 sha256=ee14a8da9c86f6dae36385e01caae0fb68b9f801a057f8d0689c796055c00239   
::    ::   Stored in directory: /root/.cache/pip/wheels/d2/89/51/54adbb04eea5d40f71a30acc182abb3e5e5d0e3d131a28d0e5                                                                                        
::    ::   Building wheel for referencing (pyproject.toml): started                                                                                                                                        
::    ::   Building wheel for referencing (pyproject.toml): finished with status 'done'                                                                                                                    
::    ::   Created wheel for referencing: filename=referencing-0.35.1-py3-none-any.whl size=26684 sha256=87509bc1eb8b7fa5747a2f7ccfc3248f77e56e820a158c6bbf4554e6a0779f13                                  
::    ::   Stored in directory: /root/.cache/pip/wheels/9c/cd/9d/c3a4add17384843de796a6704a2f798deed0391574d822558d                                                                                        
::    ::   Building wheel for rpds-py (pyproject.toml): started                                                                                                                                            
::    ::   Building wheel for rpds-py (pyproject.toml): finished with status 'error'                                                                                                                       
::    ::   error: subprocess-exited-with-error                                                                                                                                                             
::    ::                                                                                                                                                                                                   
::    ::   × Building wheel for rpds-py (pyproject.toml) did not run successfully.                                                                                                                         
::    ::   │ exit code: 1                                                                                                                                                                                  
::    ::   ╰─> [13 lines of output]                                                                                                                                                                        
::    ::       Running `maturin pep517 build-wheel -i /root/parts/charm/build/staging-venv/bin/python3 --compatibility off`                                                                                
::    ::       📦 Including license file "/tmp/pip-install-obsmhqgl/rpds-py_02cef2b8c3c64826ab00342aaea7fe0c/LICENSE"                                                                                       
::    ::       🔗 Found pyo3 bindings                                                                                                                                                                       
::    ::       🐍 Found CPython 3.10 at /root/parts/charm/build/staging-venv/bin/python3                                                                                                                    
::    ::       📡 Using build options features from pyproject.toml                                                                                                                                          
::    ::       error: package `triomphe v0.1.13` cannot be built because it requires rustc 1.76 or newer, while the currently active rustc version is 1.75.0                                               
::    ::       Either upgrade to rustc 1.76 or newer, or use                                                                                                                                               
::    ::       cargo update triomphe@0.1.13 --precise ver                                                                                                                                                  
::    ::       where `ver` is the latest version of `triomphe` supporting rustc 1.75.0                                                                                                                     
::    ::       💥 maturin failed                                                                                                                                                                            
::    ::         Caused by: Failed to build a native library through cargo                                                                                                                                 
::    ::         Caused by: Cargo build finished with "exit status: 101": `env -u CARGO PYO3_ENVIRONMENT_SIGNATURE="cpython-3.10-64bit" PYO3_PYTHON="/root/parts/charm/build/staging-venv/bin/python3" PYTHON_SYS_EXECUTABLE="/root/parts/charm/build/staging-venv/bin/python3" "cargo" "rustc" "--features" "pyo3/extension-module" "--message-format" "json-render-diagnostics" "--manifest-path" "/tmp/pip-install-obsmhqgl/rpds-py_02cef2b8c3c64826ab00342aaea7fe0c/Cargo.toml" "--release" "--lib"`                                                                                                                           
::    ::       Error: command ['maturin', 'pep517', 'build-wheel', '-i', '/root/parts/charm/build/staging-venv/bin/python3', '--compatibility', 'off'] returned non-zero exit status 1                     
::    ::       [end of output]                                                                                                                                                                             
::    ::                                                                                                                                                                                                   
::    ::   note: This error originates from a subprocess, and is likely not a problem with pip.                                                                                                            
::    ::   ERROR: Failed building wheel for rpds-py                                                                                                                                                        
::    :: Successfully built paas-app-charmer pydantic pydantic-core jsonschema ops websocket-client cosl annotated-types attrs jsonschema-specifications referencing                                       
::    :: Failed to build rpds-py                                                                                                                                                                           
::    :: ERROR: ERROR: Failed to build installable wheels for some pyproject.toml based projects (rpds-py)
```


Discussion:

Patch:
diff --git a/charmcraft/extensions/gunicorn.py b/charmcraft/extensions/gunicorn.py
index 23f78a9e..56ae3ce7 100644
--- a/charmcraft/extensions/gunicorn.py
+++ b/charmcraft/extensions/gunicorn.py
@@ -158,7 +158,13 @@ class _GunicornBase(Extension):
                 "grafana-dashboard": {"interface": "grafana_dashboard"},
             },
             "config": {"options": {**self._WEBSERVER_OPTIONS, **self.options}},
-            "parts": {"charm": {"plugin": "charm", "source": "."}},
+            "parts": {
+                "charm": {
+                    "plugin": "charm",
+                    "source": ".",
+                    "build-snaps": ["rustup"],  # Needed to build pydantic.
+                }
+            },
         }
 
     @override
@@ -225,12 +231,6 @@ class FlaskFramework(_GunicornBase):
         """Check if the extension is in an experimental state."""
         return False
 
-    @override
-    def get_parts_snippet(self) -> dict[str, Any]:
-        """Return the parts to add to parts."""
-        # rust is needed to build pydantic-core, a dependency of flask.
-        return {"flask-framework/rust-deps": {"plugin": "nil", "build-packages": ["cargo"]}}
-
 
 class DjangoFramework(_GunicornBase):
     """Extension for 12-factor Django applications."""


Test patch:
diff --git a/tests/extensions/test_gunicorn.py b/tests/extensions/test_gunicorn.py
index e2f8d55c..1151fde3 100644
--- a/tests/extensions/test_gunicorn.py
+++ b/tests/extensions/test_gunicorn.py
@@ -66,8 +66,7 @@ def flask_input_yaml_fixture():
                     "options": {**FlaskFramework.options, **FlaskFramework._WEBSERVER_OPTIONS}
                 },
                 "parts": {
-                    "charm": {"plugin": "charm", "source": "."},
-                    "flask-framework/rust-deps": {"plugin": "nil", "build-packages": ["cargo"]},
+                    "charm": {"plugin": "charm", "source": ".", "build-snaps": ["rustup"]},
                 },
                 "peers": {"secret-storage": {"interface": "secret-storage"}},
                 "provides": {
@@ -121,7 +120,7 @@ def flask_input_yaml_fixture():
                 "config": {
                     "options": {**DjangoFramework.options, **DjangoFramework._WEBSERVER_OPTIONS}
                 },
-                "parts": {"charm": {"plugin": "charm", "source": "."}},
+                "parts": {"charm": {"plugin": "charm", "source": ".", "build-snaps": ["rustup"]}},
                 "peers": {"secret-storage": {"interface": "secret-storage"}},
                 "provides": {
                     "metrics-endpoint": {"interface": "prometheus_scrape"},
@@ -255,4 +254,8 @@ def test_handle_charm_part(flask_input_yaml, tmp_path):
         apply_extensions(tmp_path, flask_input_yaml)
     del flask_input_yaml["parts"]
     applied = apply_extensions(tmp_path, flask_input_yaml)
-    assert applied["parts"]["charm"] == {"plugin": "charm", "source": "."}
+    assert applied["parts"]["charm"] == {
+        "plugin": "charm",
+        "source": ".",
+        "build-snaps": ["rustup"],
+    }
diff --git a/tests/spread/commands/init-flask-framework/task.yaml b/tests/spread/commands/init-flask-framework/task.yaml
index 65ad3973..fd58ecbf 100644
--- a/tests/spread/commands/init-flask-framework/task.yaml
+++ b/tests/spread/commands/init-flask-framework/task.yaml
@@ -1,9 +1,11 @@
 summary: test charmcraft init with flask-framework profile
+priority: 500  # This builds pydantic, so do it early
+kill-timeout: 75m  # Because it builds pydantic, it takes a long time.
+systems:
+  # We only need to run this test once, and it takes a long time.
+  - ubuntu-22.04-64
 
 execute: |
-  unset CHARMCRAFT_STORE_API_URL
-  unset CHARMCRAFT_UPLOAD_URL
-  unset CHARMCRAFT_REGISTRY_URL
   mkdir -p test-init
   cd test-init
   charmcraft init --profile flask-framework
diff --git a/tests/spread/smoketests/basic/charmcraft-23.10.yaml b/tests/spread/smoketests/basic/charmcraft-23.10.yaml
deleted file mode 100644
index 984eb861..00000000
--- a/tests/spread/smoketests/basic/charmcraft-23.10.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-name: mantic
-type: charm
-title: A charm built and running on mantic.
-summary: A charm built and running on mantic.
-description: A charm built and running on mantic.
-bases:
-  - build-on:
-    - name: ubuntu
-      channel: "23.10"
-    run-on:
-    - name: ubuntu
-      channel: "23.10"
diff --git a/tests/spread/smoketests/basic/charmcraft-bases-23.10.yaml b/tests/spread/smoketests/basic/charmcraft-bases-23.10.yaml
deleted file mode 100644
index 984eb861..00000000
--- a/tests/spread/smoketests/basic/charmcraft-bases-23.10.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-name: mantic
-type: charm
-title: A charm built and running on mantic.
-summary: A charm built and running on mantic.
-description: A charm built and running on mantic.
-bases:
-  - build-on:
-    - name: ubuntu
-      channel: "23.10"
-    run-on:
-    - name: ubuntu
-      channel: "23.10"
diff --git a/tests/spread/smoketests/basic/task.yaml b/tests/spread/smoketests/basic/task.yaml
index 79ba8ac8..8bf7a3b5 100644
--- a/tests/spread/smoketests/basic/task.yaml
+++ b/tests/spread/smoketests/basic/task.yaml
@@ -5,9 +5,8 @@ priority: 50  # Because these can take a while, run them early.
 environment:
   BASE_CHANNEL/focal_bases: 20.04
   BASE_CHANNEL/jammy_bases,jammy_platforms: 22.04
-  BASE_CHANNEL/mantic_bases: 23.10  # Non-LTS
   BASE_CHANNEL/noble_platforms: 24.04
-  CHARM_TYPE/focal_bases,jammy_bases,mantic_bases: bases
+  CHARM_TYPE/focal_bases,jammy_bases: bases
   CHARM_TYPE/jammy_platforms,noble_platforms: platforms
   # Alma Linux is disabled temporarily: https://github.com/canonical/charmcraft/issues/1496
   # BASE_CHANNEL/alma: alma9
diff --git a/tests/spread/smoketests/remote-build/task.yaml b/tests/spread/smoketests/remote-build/task.yaml
index 9bcf821c..4cb44247 100644
--- a/tests/spread/smoketests/remote-build/task.yaml
+++ b/tests/spread/smoketests/remote-build/task.yaml
@@ -9,7 +9,6 @@ environment:
   LAUNCHPAD_TOKEN: "$(HOST: echo ${LAUNCHPAD_TOKEN})"
   BASE_NAME: ubuntu
   BASE_CHANNEL/jammy: 22.04
-  BASE_CHANNEL/mantic: 23.10  # Non-LTS
 
 include:
   - tests/