Repository: canonical/charmcraft

Issue:
`charmcraft publish-lib`  exits with 0 even if publishing fails
### Bug Description

Currently `charmcraft publish-lib` correctly fails the library push if something's wrong with LIBPATCH (e.g., LIBPATCH is not bumped, going back, etc). However, the exit of the command is always `0`.

This is a problem especially in CI, where a "publish libraries" step will never fail (because of the 0 exit code), and thus is undetectable.

### To Reproduce

1. Take a charm library of any charm you own
2. Modify the `LIBPATCH` to `LIBPATCH-1`
3. Run `charmcraft publish-lib` on that library
4. Read the failure message and check the exit code (it will be 0)

### Environment

charmcraft 3.2.2.post72+g3a094837 (3.x/edge) (but you can reproduce this with any version)

### charmcraft.yaml

```yaml
-
```


### Relevant log output

```shell
-
```


Discussion:

Patch:
diff --git a/charmcraft/application/commands/store.py b/charmcraft/application/commands/store.py
index 8515c306..80c7c1d4 100644
--- a/charmcraft/application/commands/store.py
+++ b/charmcraft/application/commands/store.py
@@ -1539,6 +1539,7 @@ class PublishLibCommand(CharmcraftCommand):
             analysis.append((lib_data, error_message))
 
         # work on the analysis result, showing messages to the user if not programmatic output
+        return_code = 0
         for lib_data, error_message in analysis:
             if error_message is None:
                 store.create_library_revision(
@@ -1555,6 +1556,7 @@ class PublishLibCommand(CharmcraftCommand):
                 )
             else:
                 message = error_message
+                return_code = 1
             if not parsed_args.format:
                 emit.message(message)
 
@@ -1577,6 +1579,8 @@ class PublishLibCommand(CharmcraftCommand):
                 output_data.append(datum)
             emit.message(cli.format_content(output_data, parsed_args.format))
 
+        return return_code
+
 
 class FetchLibCommand(CharmcraftCommand):
     """Fetch one or more charm libraries."""


Test patch:
diff --git a/tests/unit/commands/test_store.py b/tests/unit/commands/test_store.py
index 9d68d4bf..512f5cbf 100644
--- a/tests/unit/commands/test_store.py
+++ b/tests/unit/commands/test_store.py
@@ -30,9 +30,16 @@ from craft_store import models
 from charmcraft import errors, store
 from charmcraft.application import commands
 from charmcraft.application.commands import SetResourceArchitecturesCommand
-from charmcraft.application.commands.store import FetchLibs, LoginCommand
+from charmcraft.application.commands import store as store_commands
+from charmcraft.application.commands.store import (
+    FetchLibs,
+    LoginCommand,
+    PublishLibCommand,
+)
 from charmcraft.application.main import APP_METADATA
 from charmcraft.models.project import CharmLib
+from charmcraft.services import CharmcraftServiceFactory
+from charmcraft.store.models import Library
 from charmcraft.utils import cli
 from tests import get_fake_revision
 
@@ -119,6 +126,40 @@ def test_set_resource_architectures_output_json(emitter, updates, expected):
     emitter.assert_json_output(expected)
 
 
+def test_publish_lib_error(monkeypatch, new_path: pathlib.Path) -> None:
+    mock_service_factory = mock.Mock(spec=CharmcraftServiceFactory)
+    mock_service_factory.project.name = "test-project"
+    lib_path = new_path / "lib/charms/test_project/v0/my_lib.py"
+    lib_path.parent.mkdir(parents=True)
+    lib_path.write_text("LIBAPI=0\nLIBID='blah'\nLIBPATCH=1")
+
+    mock_store = mock.Mock()
+    mock_store.return_value.get_libraries_tips.return_value = {
+        ("blah", 0): Library(
+            charm_name="test-project",
+            lib_id="blah",
+            lib_name="my_lib",
+            api=0,
+            patch=2,
+            content=None,
+            content_hash="",
+        ),
+    }
+    monkeypatch.setattr(store_commands, "Store", mock_store)
+
+    cmd = PublishLibCommand({"app": APP_METADATA, "services": mock_service_factory})
+
+    assert (
+        cmd.run(
+            argparse.Namespace(
+                library="charms.test-project.v0.my_lib",
+                format=False,
+            )
+        )
+        == 1
+    )
+
+
 @pytest.mark.parametrize(
     ("updates", "expected"),
     [