Skip to content

Commit c2da765

Browse files
authored
Add support for Ubuntu 24.04 and ARM64 (#202)
Adds support for: - Ubuntu 24.04 (and thus Heroku-24 / `heroku/builder:24`) - The ARM64 CPU architecture (for Ubuntu 24.04 only) The ARM64 support is tested in CI via GitHub's ARM runners beta. GUS-W-14667596. GUS-W-15158299.
1 parent 21d8671 commit c2da765

File tree

11 files changed

+113
-57
lines changed

11 files changed

+113
-57
lines changed

.github/workflows/ci.yml

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,34 +41,50 @@ jobs:
4141
run: cargo test --locked
4242

4343
integration-test:
44-
runs-on: ubuntu-latest
4544
strategy:
4645
fail-fast: false
4746
matrix:
48-
builder: ["builder:22", "builder:20"]
47+
builder: ["builder:24", "builder:22", "builder:20"]
48+
arch: ["amd64", "arm64"]
49+
exclude:
50+
- builder: "builder:22"
51+
arch: "arm64"
52+
- builder: "builder:20"
53+
arch: "arm64"
54+
runs-on: ${{ matrix.arch == 'arm64' && 'pub-hk-ubuntu-22.04-arm-large' || 'ubuntu-latest' }}
4955
env:
50-
INTEGRATION_TEST_CNB_BUILDER: heroku/${{ matrix.builder }}
56+
INTEGRATION_TEST_BUILDER: heroku/${{ matrix.builder }}
5157
steps:
5258
- name: Checkout
5359
uses: actions/checkout@v4
60+
# The beta ARM64 runners don't yet ship with the normal installed tools.
61+
- name: Install Docker, Rust and missing development libs (ARM64 only)
62+
if: matrix.arch == 'arm64'
63+
run: |
64+
sudo apt-get update --error-on=any
65+
sudo apt-get install -y --no-install-recommends acl docker.io docker-buildx libc6-dev
66+
sudo usermod -aG docker $USER
67+
sudo setfacl --modify user:$USER:rw /var/run/docker.sock
68+
curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal
69+
echo "${HOME}/.cargo/bin" >> "${GITHUB_PATH}"
5470
- name: Install musl-tools
55-
run: sudo apt-get install musl-tools --no-install-recommends
71+
run: sudo apt-get install -y --no-install-recommends musl-tools
5672
- name: Update Rust toolchain
5773
run: rustup update
5874
- name: Install Rust linux-musl target
59-
run: rustup target add x86_64-unknown-linux-musl
75+
run: rustup target add ${{ matrix.arch == 'arm64' && 'aarch64-unknown-linux-musl' || 'x86_64-unknown-linux-musl' }}
6076
- name: Rust Cache
6177
uses: Swatinem/rust-cache@v2.7.3
6278
- name: Install Pack CLI
6379
uses: buildpacks/github-actions/setup-pack@v5.6.0
6480
- name: Pull builder image
65-
run: docker pull ${{ env.INTEGRATION_TEST_CNB_BUILDER }}
81+
run: docker pull ${{ env.INTEGRATION_TEST_BUILDER }}
6682
# The integration tests are annotated with the `ignore` attribute, allowing us to run
6783
# only those and not the unit tests, via the `--ignored` option. On the latest stack
6884
# we run all integration tests, but on older stacks we only run stack-specific tests.
6985
- name: Run integration tests (all tests)
70-
if: matrix.builder == 'builder:22'
86+
if: matrix.builder == 'builder:24'
7187
run: cargo test --locked -- --ignored --test-threads 5
7288
- name: Run integration tests (stack-specific tests only)
73-
if: matrix.builder != 'builder:22'
89+
if: matrix.builder != 'builder:24'
7490
run: cargo test --locked -- --ignored --test-threads 5 'python_version_test::'

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Added support for Ubuntu 24.04 (and thus Heroku-24 / `heroku/builder:24`). ([#202](https://github.com/heroku/buildpacks-python/pull/202))
13+
- Added support for the ARM64 CPU architecture (Ubuntu 24.04 only). ([#202](https://github.com/heroku/buildpacks-python/pull/202))
14+
1015
## [0.9.0] - 2024-05-03
1116

1217
### Changed

buildpack.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,17 @@ version = "20.04"
2828
name = "ubuntu"
2929
version = "22.04"
3030

31+
[[targets.distros]]
32+
name = "ubuntu"
33+
version = "24.04"
34+
35+
[[targets]]
36+
os = "linux"
37+
arch = "arm64"
38+
39+
[[targets.distros]]
40+
name = "ubuntu"
41+
version = "24.04"
42+
3143
[metadata.release]
3244
image = { repository = "docker.io/heroku/buildpack-python" }

tests/detect_test.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
use crate::tests::builder;
1+
use crate::tests::default_build_config;
22
use indoc::indoc;
3-
use libcnb_test::{assert_contains, BuildConfig, PackResult, TestRunner};
3+
use libcnb_test::{assert_contains, PackResult, TestRunner};
44

55
#[test]
66
#[ignore = "integration test"]
77
fn detect_rejects_non_python_projects() {
88
TestRunner::default().build(
9-
BuildConfig::new(builder(), "tests/fixtures/empty")
10-
.expected_pack_result(PackResult::Failure),
9+
default_build_config("tests/fixtures/empty").expected_pack_result(PackResult::Failure),
1110
|context| {
1211
assert_contains!(
1312
context.pack_stdout,

tests/django_test.rs

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
use crate::tests::builder;
1+
use crate::tests::default_build_config;
22
use indoc::indoc;
3-
use libcnb_test::{assert_contains, assert_empty, BuildConfig, PackResult, TestRunner};
3+
use libcnb_test::{assert_contains, assert_empty, PackResult, TestRunner};
44

55
// This test uses symlinks for requirements.txt and manage.py to confirm that it's possible to use
66
// them when the Django app is nested inside a subdirectory (such as in backend+frontend monorepos).
77
#[test]
88
#[ignore = "integration test"]
99
fn django_staticfiles_latest_django() {
1010
TestRunner::default().build(
11-
BuildConfig::new(builder(), "tests/fixtures/django_staticfiles_latest_django")
11+
default_build_config("tests/fixtures/django_staticfiles_latest_django")
1212
// Tests that env vars are passed to the 'manage.py' script invocations.
1313
.env("EXPECTED_ENV_VAR", "1"),
1414
|context| {
@@ -26,23 +26,20 @@ fn django_staticfiles_latest_django() {
2626
);
2727
}
2828

29-
// This tests the oldest Django version that works on Python 3.9 (which is the
29+
// This tests the oldest Django version that works on Python 3.10 (which is the
3030
// oldest Python that is available on all of our supported builders).
3131
#[test]
3232
#[ignore = "integration test"]
3333
fn django_staticfiles_legacy_django() {
3434
TestRunner::default().build(
35-
BuildConfig::new(builder(), "tests/fixtures/django_staticfiles_legacy_django"),
35+
default_build_config("tests/fixtures/django_staticfiles_legacy_django"),
3636
|context| {
3737
assert_empty!(context.pack_stderr);
3838
assert_contains!(
3939
context.pack_stdout,
4040
indoc! {"
41-
Successfully installed Django-1.8.19
42-
4341
[Generating Django static files]
4442
Running 'manage.py collectstatic'
45-
Linking '/workspace/testapp/static/robots.txt'
4643
4744
1 static file symlinked to '/workspace/staticfiles'.
4845
"}
@@ -55,7 +52,7 @@ fn django_staticfiles_legacy_django() {
5552
#[ignore = "integration test"]
5653
fn django_no_manage_py() {
5754
TestRunner::default().build(
58-
BuildConfig::new(builder(), "tests/fixtures/django_no_manage_py"),
55+
default_build_config("tests/fixtures/django_no_manage_py"),
5956
|context| {
6057
assert_empty!(context.pack_stderr);
6158
assert_contains!(
@@ -75,10 +72,7 @@ fn django_no_manage_py() {
7572
#[ignore = "integration test"]
7673
fn django_staticfiles_app_not_enabled() {
7774
TestRunner::default().build(
78-
BuildConfig::new(
79-
builder(),
80-
"tests/fixtures/django_staticfiles_app_not_enabled",
81-
),
75+
default_build_config("tests/fixtures/django_staticfiles_app_not_enabled"),
8276
|context| {
8377
assert_empty!(context.pack_stderr);
8478
assert_contains!(
@@ -97,7 +91,7 @@ fn django_staticfiles_app_not_enabled() {
9791
#[ignore = "integration test"]
9892
fn django_invalid_settings_module() {
9993
TestRunner::default().build(
100-
BuildConfig::new(builder(), "tests/fixtures/django_invalid_settings_module")
94+
default_build_config("tests/fixtures/django_invalid_settings_module")
10195
.expected_pack_result(PackResult::Failure),
10296
|context| {
10397
assert_contains!(
@@ -139,7 +133,7 @@ fn django_invalid_settings_module() {
139133
#[ignore = "integration test"]
140134
fn django_staticfiles_misconfigured() {
141135
TestRunner::default().build(
142-
BuildConfig::new(builder(), "tests/fixtures/django_staticfiles_misconfigured")
136+
default_build_config("tests/fixtures/django_staticfiles_misconfigured")
143137
.expected_pack_result(PackResult::Failure),
144138
|context| {
145139
assert_contains!(
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
# This is the oldest Django version that works on Python 3.9 (which is the
1+
# This is the oldest Django version that works on Python 3.10 (which is the
22
# oldest Python that is available on all of our supported builders).
3-
Django==1.8.19
3+
Django==2.1.15
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
python-3.9.19
1+
python-3.10.14

tests/mod.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
//! These tests are not run via automatic integration test discovery, but instead are
44
//! imported in main.rs so that they have access to private APIs (see comment in main.rs).
55
6+
use libcnb_test::BuildConfig;
67
use std::env;
8+
use std::path::Path;
79

810
mod detect_test;
911
mod django_test;
@@ -19,8 +21,24 @@ const LATEST_PYTHON_3_11: &str = "3.11.9";
1921
const LATEST_PYTHON_3_12: &str = "3.12.3";
2022
const DEFAULT_PYTHON_VERSION: &str = LATEST_PYTHON_3_12;
2123

22-
const DEFAULT_BUILDER: &str = "heroku/builder:22";
24+
const DEFAULT_BUILDER: &str = "heroku/builder:24";
25+
26+
fn default_build_config(fixture_path: impl AsRef<Path>) -> BuildConfig {
27+
let builder = builder();
28+
29+
// TODO: Once Pack build supports `--platform` and libcnb-test adjusted accordingly, change this
30+
// to allow configuring the target arch independently of the builder name (eg via env var).
31+
let target_triple = match builder.as_str() {
32+
// Compile the buildpack for ARM64 iff the builder supports multi-arch and the host is ARM64.
33+
"heroku/builder:24" if cfg!(target_arch = "aarch64") => "aarch64-unknown-linux-musl",
34+
_ => "x86_64-unknown-linux-musl",
35+
};
36+
37+
let mut config = BuildConfig::new(&builder, fixture_path);
38+
config.target_triple(target_triple);
39+
config
40+
}
2341

2442
fn builder() -> String {
25-
env::var("INTEGRATION_TEST_CNB_BUILDER").unwrap_or(DEFAULT_BUILDER.to_string())
43+
env::var("INTEGRATION_TEST_BUILDER").unwrap_or(DEFAULT_BUILDER.to_string())
2644
}

tests/package_manager_test.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
use crate::tests::builder;
1+
use crate::tests::default_build_config;
22
use indoc::indoc;
3-
use libcnb_test::{assert_contains, BuildConfig, PackResult, TestRunner};
3+
use libcnb_test::{assert_contains, PackResult, TestRunner};
44

55
#[test]
66
#[ignore = "integration test"]
77
fn no_package_manager_detected() {
88
TestRunner::default().build(
9-
BuildConfig::new(builder(), "tests/fixtures/pyproject_toml_only")
9+
default_build_config("tests/fixtures/pyproject_toml_only")
1010
.expected_pack_result(PackResult::Failure),
1111
|context| {
1212
assert_contains!(

tests/pip_test.rs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
use crate::packaging_tool_versions::PackagingToolVersions;
2-
use crate::tests::{builder, DEFAULT_PYTHON_VERSION};
2+
use crate::tests::{builder, default_build_config, DEFAULT_PYTHON_VERSION};
33
use indoc::{formatdoc, indoc};
4-
use libcnb_test::{
5-
assert_contains, assert_empty, BuildConfig, BuildpackReference, PackResult, TestRunner,
6-
};
4+
use libcnb_test::{assert_contains, assert_empty, BuildpackReference, PackResult, TestRunner};
75

86
#[test]
97
#[ignore = "integration test"]
@@ -14,7 +12,7 @@ fn pip_basic_install_and_cache_reuse() {
1412
wheel_version,
1513
} = PackagingToolVersions::default();
1614

17-
let config = BuildConfig::new(builder(), "tests/fixtures/pip_basic");
15+
let config = default_build_config("tests/fixtures/pip_basic");
1816

1917
TestRunner::default().build(&config, |context| {
2018
assert_empty!(context.pack_stderr);
@@ -97,8 +95,9 @@ fn pip_basic_install_and_cache_reuse() {
9795
#[test]
9896
#[ignore = "integration test"]
9997
fn pip_cache_invalidation_with_compatible_metadata() {
100-
// TODO: Re-enable this test after the next buildpack release, at which point there will
101-
// be a historic buildpack version with compatible cache metadata that we can use.
98+
// TODO: Re-enable this test the next time the default-Python/pip/setuptools/wheel versions
99+
// change, at which point there will be a historic buildpack version that has both compatible
100+
// metadata, and that is also compatible with Ubuntu 24.04.
102101
#![allow(unreachable_code)]
103102
return;
104103

@@ -108,7 +107,7 @@ fn pip_cache_invalidation_with_compatible_metadata() {
108107
wheel_version,
109108
} = PackagingToolVersions::default();
110109

111-
let config = BuildConfig::new(builder(), "tests/fixtures/pip_basic");
110+
let config = default_build_config("tests/fixtures/pip_basic");
112111

113112
TestRunner::default().build(
114113
config.clone().buildpacks([BuildpackReference::Other(
@@ -154,13 +153,19 @@ fn pip_cache_invalidation_with_compatible_metadata() {
154153
#[test]
155154
#[ignore = "integration test"]
156155
fn pip_cache_invalidation_with_incompatible_metadata() {
156+
// TODO: Enable this test on Heroku-24 the next time there is an incompatible metadata change,
157+
// meaning we can bump the historic buildpack version to one that also supports Ubuntu 24.04.
158+
if builder() == "heroku/builder:24" {
159+
return;
160+
}
161+
157162
let PackagingToolVersions {
158163
pip_version,
159164
setuptools_version,
160165
wheel_version,
161166
} = PackagingToolVersions::default();
162167

163-
let config = BuildConfig::new(builder(), "tests/fixtures/pip_basic");
168+
let config = default_build_config("tests/fixtures/pip_basic");
164169

165170
TestRunner::default().build(
166171
config.clone().buildpacks([BuildpackReference::Other(
@@ -206,7 +211,7 @@ fn pip_cache_invalidation_with_incompatible_metadata() {
206211
#[ignore = "integration test"]
207212
fn pip_editable_git_compiled() {
208213
TestRunner::default().build(
209-
BuildConfig::new(builder(), "tests/fixtures/pip_editable_git_compiled")
214+
default_build_config( "tests/fixtures/pip_editable_git_compiled")
210215
.env("WHEEL_PACKAGE_URL", "https://github.com/pypa/wheel"),
211216
|context| {
212217
assert_contains!(
@@ -221,7 +226,7 @@ fn pip_editable_git_compiled() {
221226
#[ignore = "integration test"]
222227
fn pip_install_error() {
223228
TestRunner::default().build(
224-
BuildConfig::new(builder(), "tests/fixtures/pip_invalid_requirement")
229+
default_build_config( "tests/fixtures/pip_invalid_requirement")
225230
.expected_pack_result(PackResult::Failure),
226231
|context| {
227232
// Ideally we could test a combined stdout/stderr, however libcnb-test doesn't support this:

0 commit comments

Comments
 (0)