Repository: canonical/charmcraft

Issue:
New dependency resolution fails with empty lines in requirements file
### Bug Description

https://github.com/canonical/charmcraft/actions/runs/6210409286/job/16858681237

Empty requirements lines cause an IndexError

### To Reproduce

run the `tests/spread/charms/k8s-operator:redis` spread test

### Environment

spread test

### charmcraft.yaml

```shell
n/a
```


### Relevant log output

```shell
+ charmcraft pack --verbose
Starting charmcraft version 2.4.1.post38+gitfb2307b
Logging execution to '/root/.local/state/charmcraft/log/charmcraft-20230917-004723.537977.log'
Packing the charm.
Launching environment to pack for base name='ubuntu' channel='22.04' architectures=['amd64'] (may take a while the first time but it's reusable)
Creating instance from base instance
Starting instance
Packing the charm
Starting charmcraft version 2.4.1.post38+gitfb2307b
Logging execution to '/tmp/charmcraft.log'
Packing the charm.
Building charm in '/root'
Installing build-packages
Running step PULL for part 'charm'
Execute action
Running step BUILD for part 'charm'
Execute action
:: + env -i LANG=C.UTF-8 LC_ALL=C.UTF-8 PATH=/snap/charmcraft/x1/libexec/charmcraft:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin SNAP=/snap/charmcraft/x1 SNAP_ARCH=amd64 SNAP_NAME=charmcraft SNAP_VERSION=2.4.1.post38+gitfb2307b /snap/charmcraft/x1/bin/python3 -u -I /snap/charmcraft/x1/lib/charmcraft/charm_builder.py --builddir /root/parts/charm/build --installdir /root/parts/charm/install --entrypoint /root/parts/charm/build/src/charm.py -p pip -p wheel -p setuptools -r requirements.txt
:: Starting charm builder
:: Translated .jujuignore 2 '.git' => '.*/\\.git\\Z'
:: Translated .jujuignore 3 '.svn' => '.*/\\.svn\\Z'
:: Translated .jujuignore 4 '.hg' => '.*/\\.hg\\Z'
:: Translated .jujuignore 5 '.bzr' => '.*/\\.bzr\\Z'
:: Translated .jujuignore 6 '.tox' => '.*/\\.tox\\Z'
:: Translated .jujuignore 8 '/build/' => '/build\\Z'
:: Translated .jujuignore 9 '/revision' => '/revision\\Z'
:: Translated .jujuignore 10 '/venv' => '/venv\\Z'
:: Translated .jujuignore 12 '.jujuignore' => '.*/\\.jujuignore\\Z'
:: Translated .jujuignore 1 '/venv\n' => '/venv\\Z'
:: Translated .jujuignore 2 '*.py[cod]\n' => '.*/[^/]*\\.py[cod]\\Z'
:: Translated .jujuignore 3 '*.charm\n' => '.*/[^/]*\\.charm\\Z'
:: Translated .jujuignore 4 'build/\n' => '.*/build\\Z'
:: Translated .jujuignore 5 '__pycache__\n' => '.*/__pycache__\\Z'
:: Translated .jujuignore 6 '.coverage\n' => '.*/\\.coverage\\Z'
:: Translated .jujuignore 7 '.idea\n' => '.*/\\.idea\\Z'
:: Translated .jujuignore 1 '/staging-venv' => '/staging\\-venv\\Z'
:: Collected charmlib dependencies: set()
:: Building charm in '/root/parts/charm/install'
:: Linking in generic paths
:: Ignoring directory because of rules: '.git'
:: Ignoring file because of rules: '.jujuignore'
:: Creating the dispatch mechanism
:: Creating the 'upgrade-charm' hook script pointing to dispatch
:: Creating the 'install' hook script pointing to dispatch
:: Creating the 'start' hook script pointing to dispatch
:: Handling dependencies
:: Current dependencies hash: '45a082d9cf2a50e992392bdc400cde64490fed0d'
:: Dependencies directory not found
:: Installing dependencies
:: Running external command ['python3', '-m', 'venv', '/root/parts/charm/build/staging-venv']
:: Traceback (most recent call last):
::   File "/snap/charmcraft/x1/lib/charmcraft/charm_builder.py", line 442, in <module>
::     main()
::   File "/snap/charmcraft/x1/lib/charmcraft/charm_builder.py", line 437, in main
::     builder.build_charm()
::   File "/snap/charmcraft/x1/lib/charmcraft/charm_builder.py", line 95, in build_charm
::     self.handle_dependencies()
::   File "/snap/charmcraft/x1/lib/charmcraft/charm_builder.py", line 324, in handle_dependencies
::     self._install_dependencies(staging_venv_dir)
::   File "/snap/charmcraft/x1/lib/charmcraft/instrum.py", line 152, in _f
::     return func(*args, **kwargs)
::   File "/snap/charmcraft/x1/lib/charmcraft/charm_builder.py", line 249, in _install_dependencies
::     get_pip_command(
::   File "/snap/charmcraft/x1/lib/charmcraft/utils.py", line 491, in get_pip_command
::     requirements_packages = get_pypi_packages(
::   File "/snap/charmcraft/x1/lib/charmcraft/utils.py", line 436, in get_pypi_packages
::     if line[0] not in valid_package_start_chars:
:: IndexError: string index out of range
Parts processing error: Failed to run the build script for part 'charm'.
Failed to build charm for bases index '0'.
Full execution log: '/root/.local/state/charmcraft/log/charmcraft-20230917-004723.537977.log'
-----
.
2023-09-17 00:48:48 Restoring google:ubuntu-22.04-64:tests/spread/charms/k8s-operator:redis (sep170027-557828)...
```


Discussion:
syu-w: You may also add some empty space `    ` in that

Patch:
diff --git a/charmcraft/utils/package.py b/charmcraft/utils/package.py
index d25b8a21..325518f4 100644
--- a/charmcraft/utils/package.py
+++ b/charmcraft/utils/package.py
@@ -36,7 +36,7 @@ def get_pypi_packages(*requirements: Iterable[str]) -> set[str]:
     for req in requirements:
         for line in req:
             line = line.strip()
-            if line[0] not in valid_package_start_chars:
+            if not line or line[0] not in valid_package_start_chars:
                 continue
             if PACKAGE_LINE_REGEX.match(line):
                 packages.add(line)


Test patch:
diff --git a/tests/unit/utils/test_package.py b/tests/unit/utils/test_package.py
index dac487c1..dfcad687 100644
--- a/tests/unit/utils/test_package.py
+++ b/tests/unit/utils/test_package.py
@@ -36,6 +36,7 @@ from charmcraft.utils.package import (
         pytest.param([], set(), id="empty"),
         pytest.param(["abc==1.0.0"], {"abc==1.0.0"}, id="simple"),
         pytest.param(["-e ."], set(), id="editable-ignored"),
+        pytest.param([""], set(), id="empty-line"),
     ],
 )
 def test_get_pypi_packages(requirements, expected):