Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Removed

- Stopped explicitly installing setuptools and wheel. They will be automatically installed by pip into an isolated build environment if they are required for building a package. ([#243](https://github.com/heroku/buildpacks-python/pull/243))

## [0.13.0] - 2024-08-01

### Changed
Expand Down
1 change: 0 additions & 1 deletion requirements/setuptools.txt

This file was deleted.

1 change: 0 additions & 1 deletion requirements/wheel.txt

This file was deleted.

4 changes: 2 additions & 2 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,13 @@ fn on_python_layer_error(error: PythonLayerError) {
PythonLayerError::BootstrapPipCommand(error) => match error {
StreamedCommandError::Io(io_error) => log_io_error(
"Unable to bootstrap pip",
"running the command to install pip, setuptools and wheel",
"running the command to install pip",
&io_error,
),
StreamedCommandError::NonZeroExitStatus(exit_status) => log_error(
"Unable to bootstrap pip",
formatdoc! {"
The command to install pip, setuptools and wheel did not exit successfully ({exit_status}).
The command to install pip did not exit successfully ({exit_status}).

See the log output above for more information.

Expand Down
13 changes: 6 additions & 7 deletions src/layers/pip_cache.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::packaging_tool_versions::PackagingToolVersions;
use crate::packaging_tool_versions::PIP_VERSION;
use crate::python_version::PythonVersion;
use crate::{BuildpackError, PythonBuildpack};
use libcnb::build::BuildContext;
Expand All @@ -17,14 +17,13 @@ pub(crate) fn prepare_pip_cache(
context: &BuildContext<PythonBuildpack>,
env: &mut Env,
python_version: &PythonVersion,
packaging_tool_versions: &PackagingToolVersions,
) -> Result<(), libcnb::Error<BuildpackError>> {
let new_metadata = PipCacheLayerMetadata {
arch: context.target.arch.clone(),
distro_name: context.target.distro_name.clone(),
distro_version: context.target.distro_version.clone(),
python_version: python_version.to_string(),
packaging_tool_versions: packaging_tool_versions.clone(),
pip_version: PIP_VERSION.to_string(),
};

let layer = context.cached_layer(
Expand Down Expand Up @@ -74,15 +73,15 @@ pub(crate) fn prepare_pip_cache(
Ok(())
}

// Timestamp based cache invalidation isn't used here since the Python/pip/setuptools/wheel
// versions will change often enough that it isn't worth the added complexity. Ideally pip
// would support cleaning up its own cache: https://github.com/pypa/pip/issues/6956
// Timestamp based cache invalidation isn't used here since the Python and pip versions will
// change often enough that it isn't worth the added complexity. Ideally pip would support
// cleaning up its own cache: https://github.com/pypa/pip/issues/6956
#[derive(Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
struct PipCacheLayerMetadata {
arch: String,
distro_name: String,
distro_version: String,
python_version: String,
packaging_tool_versions: PackagingToolVersions,
pip_version: String,
}
66 changes: 11 additions & 55 deletions src/layers/python.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::packaging_tool_versions::PackagingToolVersions;
use crate::packaging_tool_versions::PIP_VERSION;
use crate::python_version::PythonVersion;
use crate::utils::{self, DownloadUnpackArchiveError, StreamedCommandError};
use crate::{BuildpackError, PythonBuildpack};
Expand All @@ -17,7 +17,7 @@ use std::path::{Path, PathBuf};
use std::process::Command;
use std::{fs, io};

/// Creates a layer containing the Python runtime and the packages `pip`, `setuptools` and `wheel`.
/// Creates a layer containing the Python runtime and pip.
//
// We install both Python and the packaging tools into the same layer, since:
// - We don't want to mix buildpack/packaging dependencies with the app's own dependencies
Expand All @@ -31,25 +31,18 @@ use std::{fs, io};
// - This leaves just the system site-packages directory, which exists within the Python
// installation directory and Python does not support moving it elsewhere.
// - It matches what both local and official Docker image environments do.
#[allow(clippy::too_many_lines)]
pub(crate) fn install_python_and_packaging_tools(
context: &BuildContext<PythonBuildpack>,
env: &mut Env,
python_version: &PythonVersion,
packaging_tool_versions: &PackagingToolVersions,
) -> Result<(), libcnb::Error<BuildpackError>> {
let new_metadata = PythonLayerMetadata {
arch: context.target.arch.clone(),
distro_name: context.target.distro_name.clone(),
distro_version: context.target.distro_version.clone(),
python_version: python_version.to_string(),
packaging_tool_versions: packaging_tool_versions.clone(),
pip_version: PIP_VERSION.to_string(),
};
let PackagingToolVersions {
pip_version,
setuptools_version,
wheel_version,
} = packaging_tool_versions;

let layer = context.cached_layer(
layer_name!("python"),
Expand All @@ -71,9 +64,8 @@ pub(crate) fn install_python_and_packaging_tools(

match layer.state {
LayerState::Restored { .. } => {
log_info(format!("Using cached Python {python_version}"));
log_info(format!(
"Using cached pip {pip_version}, setuptools {setuptools_version} and wheel {wheel_version}"
"Using cached Python {python_version} and pip {PIP_VERSION}"
));
}
LayerState::Empty { ref cause } => {
Expand Down Expand Up @@ -117,9 +109,7 @@ pub(crate) fn install_python_and_packaging_tools(
return Ok(());
}

log_info(format!(
"Installing pip {pip_version}, setuptools {setuptools_version} and wheel {wheel_version}"
));
log_info(format!("Installing pip {PIP_VERSION}"));

let python_stdlib_dir = layer_path.join(format!(
"lib/python{}.{}",
Expand All @@ -140,9 +130,7 @@ pub(crate) fn install_python_and_packaging_tools(
"--no-cache-dir",
"--no-input",
"--quiet",
format!("pip=={pip_version}").as_str(),
format!("setuptools=={setuptools_version}").as_str(),
format!("wheel=={wheel_version}").as_str(),
format!("pip=={PIP_VERSION}").as_str(),
])
.current_dir(&context.app_dir)
.env_clear()
Expand Down Expand Up @@ -170,7 +158,7 @@ struct PythonLayerMetadata {
distro_name: String,
distro_version: String,
python_version: String,
packaging_tool_versions: PackagingToolVersions,
pip_version: String,
}

/// Compare cached layer metadata to the new layer metadata to determine if the cache should be
Expand All @@ -189,25 +177,15 @@ fn cache_invalidation_reasons(
distro_name: cached_distro_name,
distro_version: cached_distro_version,
python_version: cached_python_version,
packaging_tool_versions:
PackagingToolVersions {
pip_version: cached_pip_version,
setuptools_version: cached_setuptools_version,
wheel_version: cached_wheel_version,
},
pip_version: cached_pip_version,
} = cached_metadata;

let PythonLayerMetadata {
arch,
distro_name,
distro_version,
python_version,
packaging_tool_versions:
PackagingToolVersions {
pip_version,
setuptools_version,
wheel_version,
},
pip_version,
} = new_metadata;

let mut reasons = Vec::new();
Expand Down Expand Up @@ -236,18 +214,6 @@ fn cache_invalidation_reasons(
));
}

if cached_setuptools_version != setuptools_version {
reasons.push(format!(
"The setuptools version has changed from {cached_setuptools_version} to {setuptools_version}"
));
}

if cached_wheel_version != wheel_version {
reasons.push(format!(
"The wheel version has changed from {cached_wheel_version} to {wheel_version}"
));
}

reasons
}

Expand Down Expand Up @@ -423,11 +389,7 @@ mod tests {
distro_name: "ubuntu".to_string(),
distro_version: "22.04".to_string(),
python_version: "3.11.0".to_string(),
packaging_tool_versions: PackagingToolVersions {
pip_version: "A.B.C".to_string(),
setuptools_version: "D.E.F".to_string(),
wheel_version: "G.H.I".to_string(),
},
pip_version: "A.B.C".to_string(),
}
}

Expand Down Expand Up @@ -462,11 +424,7 @@ mod tests {
distro_name: "debian".to_string(),
distro_version: "12".to_string(),
python_version: "3.11.1".to_string(),
packaging_tool_versions: PackagingToolVersions {
pip_version: "A.B.C-new".to_string(),
setuptools_version: "D.E.F-new".to_string(),
wheel_version: "G.H.I-new".to_string(),
},
pip_version: "A.B.C-new".to_string(),
};
assert_eq!(
cache_invalidation_reasons(&cached_metadata, &new_metadata),
Expand All @@ -475,8 +433,6 @@ mod tests {
"The OS has changed from ubuntu-22.04 to debian-12",
"The Python version has changed from 3.11.0 to 3.11.1",
"The pip version has changed from A.B.C to A.B.C-new",
"The setuptools version has changed from D.E.F to D.E.F-new",
"The wheel version has changed from G.H.I to G.H.I-new"
]
);
}
Expand Down
20 changes: 4 additions & 16 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use crate::layers::pip_dependencies::PipDependenciesLayerError;
use crate::layers::python::{self, PythonLayerError};
use crate::layers::{pip_cache, pip_dependencies};
use crate::package_manager::{DeterminePackageManagerError, PackageManager};
use crate::packaging_tool_versions::PackagingToolVersions;
use crate::python_version::PythonVersionError;
use libcnb::build::{BuildContext, BuildResult, BuildResultBuilder};
use libcnb::detect::{DetectContext, DetectResult, DetectResultBuilder};
Expand Down Expand Up @@ -53,7 +52,6 @@ impl Buildpack for PythonBuildpack {
log_header("Determining Python version");
let python_version = python_version::determine_python_version(&context.app_dir)
.map_err(BuildpackError::PythonVersion)?;
let packaging_tool_versions = PackagingToolVersions::default();

// We inherit the current process's env vars, since we want `PATH` and `HOME` from the OS
// to be set (so that later commands can find tools like Git in the base image), along
Expand All @@ -62,26 +60,16 @@ impl Buildpack for PythonBuildpack {
// making sure that buildpack env vars take precedence in layers envs and command usage.
let mut env = Env::from_current();

// Create the layer containing the Python runtime, and the packages `pip`, `setuptools` and `wheel`.
log_header("Installing Python and packaging tools");
python::install_python_and_packaging_tools(
&context,
&mut env,
&python_version,
&packaging_tool_versions,
)?;
// Create the layer containing the Python runtime and pip.
log_header("Installing Python and pip");
python::install_python_and_packaging_tools(&context, &mut env, &python_version)?;

// Create the layers for the application dependencies and package manager cache.
// In the future support will be added for package managers other than pip.
let dependencies_layer_dir = match package_manager {
PackageManager::Pip => {
log_header("Installing dependencies using pip");
pip_cache::prepare_pip_cache(
&context,
&mut env,
&python_version,
&packaging_tool_versions,
)?;
pip_cache::prepare_pip_cache(&context, &mut env, &python_version)?;
pip_dependencies::install_dependencies(&context, &mut env)?
}
};
Expand Down
30 changes: 2 additions & 28 deletions src/packaging_tool_versions.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,10 @@
use serde::{Deserialize, Serialize};
use std::str;

// We store these versions in requirements files so that Dependabot can update them.
// Each file must contain a single package specifier in the format `package==1.2.3`,
// from which we extract/validate the version substring at compile time.
const PIP_VERSION: &str = extract_requirement_version(include_str!("../requirements/pip.txt"));
const SETUPTOOLS_VERSION: &str =
extract_requirement_version(include_str!("../requirements/setuptools.txt"));
const WHEEL_VERSION: &str = extract_requirement_version(include_str!("../requirements/wheel.txt"));

/// The versions of various packaging tools used during the build.
/// These are always installed, and are independent of the chosen package manager.
/// Strings are used instead of a semver version, since these packages don't use
/// semver, and we never introspect the version parts anyway.
#[allow(clippy::struct_field_names)]
#[derive(Clone, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct PackagingToolVersions {
pub(crate) pip_version: String,
pub(crate) setuptools_version: String,
pub(crate) wheel_version: String,
}

impl Default for PackagingToolVersions {
fn default() -> Self {
Self {
pip_version: PIP_VERSION.to_string(),
setuptools_version: SETUPTOOLS_VERSION.to_string(),
wheel_version: WHEEL_VERSION.to_string(),
}
}
}
pub(crate) const PIP_VERSION: &str =
extract_requirement_version(include_str!("../requirements/pip.txt"));

// Extract the version substring from an exact-version package specifier (such as `foo==1.2.3`).
// This function should only be used to extract the version constants from the buildpack's own
Expand Down
Loading