Skip to content

Commit e0592a6

Browse files
authored
Merge pull request juju#672 from freyes/py312-support
Python 3.12 support
2 parents 52eb3a6 + 4988527 commit e0592a6

File tree

7 files changed

+101
-23
lines changed

7 files changed

+101
-23
lines changed

.github/workflows/tests.yml

+50-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ jobs:
1010
runs-on: ubuntu-latest
1111
strategy:
1212
matrix:
13-
python-version: ['3.8', '3.9', '3.10']
13+
python-version:
14+
- '3.8' # focal
15+
- '3.10' # jammy
16+
- '3.12' # noble
1417
steps:
1518
- name: Check out code
1619
uses: actions/checkout@v2
@@ -39,6 +42,11 @@ jobs:
3942
path: ${{ steps.snap-build.outputs.snap }}
4043

4144
integration:
45+
strategy:
46+
matrix:
47+
charmcraft_channel:
48+
- "2.x/stable"
49+
- "3.x/beta"
4250
name: Integration test
4351
needs: build
4452
runs-on: ubuntu-latest
@@ -64,17 +72,19 @@ jobs:
6472
name: charm-snap
6573
path: tests/charm-minimal/charm-snap
6674

67-
- name: Build reactive charm with charmcraft
75+
- name: Build reactive charm with charmcraft-2.x
76+
if: ${{ matrix.charmcraft_channel == '2.x/stable' }}
6877
run: |
6978
set -euxo pipefail
70-
sudo snap install --classic --channel latest/edge charmcraft
79+
sudo snap install --classic --channel ${{ matrix.charmcraft_channel }} charmcraft
7180
cat << EOF | tee tests/charm-minimal/charmcraft.yaml
7281
type: charm
7382
parts:
7483
charm-tools:
7584
plugin: nil
7685
override-build: |
77-
snap install --dangerous --classic \$CRAFT_PROJECT_DIR/parts/charm/src/charm-snap/*.snap
86+
ls -lR \$CRAFT_PROJECT_DIR/
87+
snap install --dangerous --classic /root/project/charm-snap/charm_0.0.0_amd64.snap
7888
rm -rf \$CRAFT_PROJECT_DIR/parts/charm/src/charm-snap
7989
charm:
8090
after: [charm-tools]
@@ -99,6 +109,41 @@ jobs:
99109
architectures: [amd64]
100110
EOF
101111
charmcraft pack -p tests/charm-minimal -v
112+
- name: Build reactive charm with charmcraft-3.x
113+
if: ${{ matrix.charmcraft_channel == '3.x/beta' }}
114+
run: |
115+
set -euxo pipefail
116+
sudo snap install --classic --channel ${{ matrix.charmcraft_channel }} charmcraft
117+
cat << EOF | tee tests/charm-minimal/charmcraft.yaml
118+
type: charm
119+
parts:
120+
charm-tools:
121+
plugin: nil
122+
override-build: |
123+
ls -lR \$CRAFT_PROJECT_DIR/
124+
snap install --dangerous --classic /root/project/charm-snap/charm_0.0.0_amd64.snap
125+
rm -rf \$CRAFT_PROJECT_DIR/parts/charm/src/charm-snap
126+
charm:
127+
after: [charm-tools]
128+
source: .
129+
plugin: reactive
130+
reactive-charm-build-arguments:
131+
- -v
132+
- --binary-wheels-from-source
133+
- --upgrade-buildvenv-core-deps
134+
build-packages:
135+
- python3-dev
136+
- libpq-dev
137+
base: ubuntu@24.04
138+
platforms:
139+
amd64:
140+
EOF
141+
charmcraft pack -p tests/charm-minimal -v
142+
mv minimal_amd64.charm minimal_ubuntu-24.04-amd64.charm
143+
## action to interactively debug CI failures.
144+
# - name: Setup upterm session
145+
# if: failure()
146+
# uses: lhotari/action-upterm@v1
102147
- name: Upload charmcraft execution logs
103148
if: always()
104149
uses: actions/upload-artifact@v3
@@ -113,3 +158,4 @@ jobs:
113158
minimal_ubuntu-18.04-amd64.charm
114159
minimal_ubuntu-20.04-amd64.charm
115160
minimal_ubuntu-22.04-amd64.charm
161+
minimal_ubuntu-24.04-amd64.charm

charmtools/build/tactics.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from inspect import getargspec
1+
from inspect import getfullargspec
22
import errno
33
import json
44
import logging
@@ -43,7 +43,7 @@ def get(cls, entity, target, layer, next_config, current_config,
4343
given entity.
4444
"""
4545
for candidate in current_config.tactics + DEFAULT_TACTICS:
46-
argspec = getargspec(candidate.trigger)
46+
argspec = getfullargspec(candidate.trigger)
4747
if len(argspec.args) == 2:
4848
# old calling convention
4949
name = candidate.__name__
@@ -1253,6 +1253,9 @@ def __call__(self):
12531253
).exit_on_error()()
12541254
if self.upgrade_deps:
12551255
utils.upgrade_venv_core_packages(self._venv, env=self._get_env())
1256+
elif utils.get_python_version(self._venv,
1257+
env=self._get_env()) >= utils.PY312:
1258+
log.debug('Skip pinning of setuptools, because Python>=3.12')
12561259
else:
12571260
utils.pin_setuptools_for_pep440(self._venv, env=self._get_env())
12581261
log.debug(

charmtools/utils.py

+24-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from path import Path as path
2121

2222
log = logging.getLogger('utils')
23+
PY312 = (3, 12, 0)
2324

2425

2526
@contextmanager
@@ -681,8 +682,8 @@ def pin_setuptools_for_pep440(venv_dir, env=None):
681682
:type env: Optional[Dict[str,str]]
682683
:returns: This function is called for its side effect
683684
"""
684-
log.debug('Pinning setuptools < 67 for pep440 non compliant packages "{}"'
685-
.format(venv_dir))
685+
log.info('Pinning setuptools < 67 for pep440 non compliant packages "{}"'
686+
.format(venv_dir))
686687
Process((os.path.join(venv_dir, 'bin/pip'),
687688
'install', '-U', 'pip<23.1', 'setuptools<67'),
688689
env=env).exit_on_error()()
@@ -702,3 +703,24 @@ def get_venv_package_list(venv_dir, env=None):
702703
if result:
703704
return result.output
704705
result.exit_on_error()
706+
707+
708+
def get_python_version(venv_dir, env=None):
709+
"""Get the Python interpreter version in the virtualenv.
710+
711+
:param venv_dir: Full path to virtualenv in which packages will be listed
712+
:type venv_dir: str
713+
:param env: Environment to use when executing command
714+
:type env: Optional[Dict[str,str]]
715+
:returns: Tuple with major, minor and microversion
716+
:rtype: Tuple[str]
717+
"""
718+
result = Process((os.path.join(venv_dir, 'bin/python3'), '--version'),
719+
env=env)()
720+
m = re.match(r'^Python[ ]+(\d+)\.(\d+)\.(\d+).*', result.output)
721+
if not m:
722+
raise ValueError('Cannot identify the python version: %s' % result)
723+
724+
return (int(m.group(1)),
725+
int(m.group(2)),
726+
int(m.group(3)))

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
'pathspec<0.11;python_version >= "3.7"',
5353
'otherstuf<=1.1.0',
5454
'path.py>=10.5,<13',
55-
'pip>=1.5.4,<23',
55+
'pip>=1.5.4',
5656
'jujubundlelib<0.6',
5757
'virtualenv>=1.11.4,<21',
5858
'colander<1.9',

tests/test_build.py

+11-11
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,10 @@ def test_tester_layer(self, pv):
179179
cyaml = base / "layer.yaml"
180180
self.assertTrue(cyaml.exists())
181181
cyaml_data = yaml.safe_load(cyaml.open())
182-
self.assertEquals(cyaml_data['includes'], ['layers/test-base',
182+
self.assertEqual(cyaml_data['includes'], ['layers/test-base',
183183
'layers/mysql'])
184-
self.assertEquals(cyaml_data['is'], 'foo')
185-
self.assertEquals(cyaml_data['options']['mysql']['qux'], 'one')
184+
self.assertEqual(cyaml_data['is'], 'foo')
185+
self.assertEqual(cyaml_data['options']['mysql']['qux'], 'one')
186186

187187
self.assertTrue((base / "hooks/config-changed").exists())
188188

@@ -205,13 +205,13 @@ def test_tester_layer(self, pv):
205205
sigs = base / ".build.manifest"
206206
self.assertTrue(sigs.exists())
207207
data = json.load(sigs.open())
208-
self.assertEquals(data['signatures']["README.md"], [
208+
self.assertEqual(data['signatures']["README.md"], [
209209
u'foo',
210210
"static",
211211
u'cfac20374288c097975e9f25a0d7c81783acdbc81'
212212
'24302ff4a731a4aea10de99'])
213213

214-
self.assertEquals(data["signatures"]['metadata.yaml'], [
214+
self.assertEqual(data["signatures"]['metadata.yaml'], [
215215
u'foo',
216216
"dynamic",
217217
u'12c1f6fc865da0660f6dc044cca03b0244e883d9a99fdbdfab6ef6fc2fed63b7'
@@ -588,9 +588,9 @@ def test_layer_options(self, log):
588588
},
589589
})
590590

591-
@mock.patch('charmtools.build.tactics.getargspec')
591+
@mock.patch('charmtools.build.tactics.getfullargspec')
592592
@mock.patch('charmtools.utils.walk')
593-
def test_custom_tactics(self, mwalk, mgetargspec):
593+
def test_custom_tactics(self, mwalk, mgetfullargspec):
594594
def _layer(tactics):
595595
return mock.Mock(config=build.builder.BuildConfig({'tactics':
596596
tactics}),
@@ -611,13 +611,13 @@ def _layer(tactics):
611611
builder.plan_layers(layers, {})
612612
calls = [call[1]['current_config'].tactics
613613
for call in mwalk.call_args_list]
614-
self.assertEquals(calls, [
614+
self.assertEqual(calls, [
615615
['first'],
616616
['second', 'first'],
617617
['third', 'second', 'first'],
618618
])
619619

620-
mgetargspec.return_value = mock.Mock(args=[1, 2, 3, 4])
620+
mgetfullargspec.return_value = mock.Mock(args=[1, 2, 3, 4])
621621
current_config = mock.Mock(tactics=[
622622
mock.Mock(name='1', **{'trigger.return_value': False}),
623623
mock.Mock(name='2', **{'trigger.return_value': False}),
@@ -629,9 +629,9 @@ def _layer(tactics):
629629
mock.Mock(),
630630
current_config,
631631
mock.Mock())
632-
self.assertEquals([t.trigger.called for t in current_config.tactics],
632+
self.assertEqual([t.trigger.called for t in current_config.tactics],
633633
[True, True, True])
634-
self.assertEquals([t.called for t in current_config.tactics],
634+
self.assertEqual([t.called for t in current_config.tactics],
635635
[False, False, True])
636636

637637

tests/test_utils.py

+9
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,12 @@ def test_get_venv_package_list(self, mock_Process, mock_sys_exit):
8888
mock_Process().return_value = utils.ProcessResult('fakecmd', 1, '', '')
8989
utils.get_venv_package_list('/some/dir', env={'some': 'envvar'})
9090
mock_sys_exit.assert_called_once_with(1)
91+
92+
@unittest.mock.patch.object(utils, "Process")
93+
def test_get_oython_version(self, process_klass):
94+
process_klass().return_value = utils.ProcessResult(
95+
['python3', '--version'], 0, b'Python 3.12.4', b'')
96+
self.assertEqual(
97+
utils.get_python_version('/some/dir', env={'some': 'envvar'}),
98+
(3, 12, 4)
99+
)

tox.ini

+1-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
[tox]
77
envlist = py3
8-
skipsdist = true
8+
package = editable
99

1010
[testenv]
1111
install_command = pip install {opts} {packages}
@@ -18,8 +18,6 @@ deps =
1818
coverage
1919
mock
2020
responses
21-
-e {toxinidir}
22-
https://github.com/openstack-charmers/charm-templates-openstack/archive/master.zip#egg=charm_templates_openstack
2321
ipdb
2422

2523
[testenv:lint]

0 commit comments

Comments
 (0)