Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

set python to 3.7 in docker container and release action #2879

Merged
merged 11 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from 9 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
12 changes: 9 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,23 @@ jobs:
df -h

# This is required before checkout because the container does not
# have Git installed, so cannot run checkout action. The checkout
# action requires Git >=2.18, so use the Git maintainers' PPA.
# have Git installed, so cannot run checkout action. T
jaisnan marked this conversation as resolved.
Show resolved Hide resolved
# The checkout action requires Git >=2.18 and python 3.7, so use the Git maintainers' PPA.
# and the "deadsnakes" PPA, as the default version of python on ubuntu is 3.11.
jaisnan marked this conversation as resolved.
Show resolved Hide resolved
- name: Install system dependencies
run: |
apt-get update
apt-get install -y software-properties-common apt-utils
add-apt-repository ppa:git-core/ppa
add-apt-repository ppa:deadsnakes/ppa
jaisnan marked this conversation as resolved.
Show resolved Hide resolved
apt-get update
apt-get install -y \
build-essential bash-completion curl lsb-release sudo g++ gcc flex \
bison make patch git
bison make patch git python3.7 python3.7-dev python3.7-distutils
update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.7 1
curl -s https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python3 get-pip.py --force-reinstall
rm get-pip.py

- name: Checkout Kani
uses: actions/checkout@v3
Expand Down
2 changes: 1 addition & 1 deletion docs/src/install-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ GitHub CI workflows, see [GitHub CI Action](./install-github-ci.md).

The following must already be installed:

* **Python version 3.6 or newer** and the package installer `pip`.
* **Python version 3.7 or newer** and the package installer `pip`.
* Rust 1.58 or newer installed via `rustup`.
* `ctags` is required for Kani's `--visualize` option to work correctly. [Universal ctags](https://ctags.io/) is recommended.

Expand Down
14 changes: 12 additions & 2 deletions scripts/ci/Dockerfile.bundle-test-ubuntu-18-04
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,19 @@
FROM ubuntu:18.04
ENV DEBIAN_FRONTEND=noninteractive \
DEBCONF_NONINTERACTIVE_SEEN=true

RUN apt-get update && \
apt-get install -y python3 python3-pip curl ctags && \
curl -sSf https://sh.rustup.rs | sh -s -- -y
apt-get install --no-install-recommends -y build-essential software-properties-common && \
add-apt-repository -y ppa:deadsnakes/ppa && \
apt install --no-install-recommends -y python3.7 python3.7-dev python3.7-distutils curl ctags

RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.7 1

RUN curl -s https://bootstrap.pypa.io/get-pip.py -o get-pip.py && \
python3 get-pip.py --force-reinstall && \
rm get-pip.py

RUN curl -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"

WORKDIR /tmp/kani
Expand Down
123 changes: 49 additions & 74 deletions src/os_hacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
//! "flow" of code in setup.rs, this module contains all functions that implement os-specific
//! workarounds.

use std::ffi::OsString;
use std::path::Path;
use std::process::Command;

Expand All @@ -14,72 +13,45 @@ use os_info::Info;

use crate::cmd::AutoRun;

pub fn should_apply_ubuntu_18_04_python_hack(os: &os_info::Info) -> Result<bool> {
if os.os_type() != os_info::Type::Ubuntu {
return Ok(false);
pub fn check_minimum_python_version(output: &str) -> Result<bool> {
// Split the string by whitespace and get the second element
let version_number = output.split_whitespace().nth(1).unwrap_or("Version number not found");
let parts: Vec<&str> = version_number.split('.').take(2).collect();
let system_python_version = parts.join(".");

// The minimum version is set to be 3.7 for now
// TODO: Maybe read from some config file instead of a local variable?
let base_version = "3.7";

match compare_versions(&system_python_version, base_version) {
Ok(ordering) => match ordering {
std::cmp::Ordering::Less => Ok(false),
std::cmp::Ordering::Equal => Ok(true),
std::cmp::Ordering::Greater => Ok(true),
},
Err(_e) => Ok(false),
}
// Check both versions: https://github.com/stanislav-tkach/os_info/issues/318
if *os.version() != os_info::Version::Semantic(18, 4, 0)
&& *os.version() != os_info::Version::Custom("18.04".into())
{
return Ok(false);
}
// It's not enough to check that we're on Ubuntu 18.04 because the user may have
// manually updated to a newer version of Python instead of using what the OS ships.
// So check if it looks like the OS-shipped version as best we can.
let cmd = Command::new("python3").args(["-m", "pip", "--version"]).output()?;
let output = std::str::from_utf8(&cmd.stdout)?;
// The problem version looks like:
// 'pip 9.0.1 from /usr/lib/python3/dist-packages (python 3.6)'
// So we'll test for version 9.
Ok(pip_major_version(output)? == 9)
}

/// Unit testable parsing function for extracting pip version numbers, from strings that look like:
/// 'pip 9.0.1 from /usr/lib/python3/dist-packages (python 3.6)'
fn pip_major_version(output: &str) -> Result<u32> {
// We don't want dependencies so parse with stdlib string functions as best we can.
let mut words = output.split_whitespace();
let _pip = words.next().context("No pip output")?;
let version = words.next().context("No pip version")?;
// Given two semver strings, compare them and return an std::Ordering result
fn compare_versions(version1: &str, version2: &str) -> Result<std::cmp::Ordering, String> {
let v1_parts: Vec<i32> = version1.split('.').map(|s| s.parse::<i32>().unwrap()).collect();
let v2_parts: Vec<i32> = version2.split('.').map(|s| s.parse::<i32>().unwrap()).collect();

let mut versions = version.split('.');
let major = versions.next().context("No pip major version")?;
let max_len = std::cmp::max(v1_parts.len(), v2_parts.len());

Ok(major.parse()?)
}
// Compare semver strings by comparing each individual substring
// to corresponding counterpart. i.e major version vs major version and so on
for i in 0..max_len {
let part_v1 = *v1_parts.get(i).unwrap_or(&0);
let part_v2 = *v2_parts.get(i).unwrap_or(&0);

/// See [`crate::setup::setup_python_deps`]
pub fn setup_python_deps_on_ubuntu_18_04(pyroot: &Path, pkg_versions: &[&str]) -> Result<()> {
println!("Applying a workaround for 18.04...");
// https://github.com/pypa/pip/issues/3826
// Ubuntu 18.04 has a patched-to-be-broken version of pip that just straight-up makes `--target` not work.
// Worse still, there is no apparent way to replicate the correct behavior cleanly.

// This is a really awful hack to simulate getting the same result. I can find no other solution.
// Example failed approach: `--system --target pyroot` fails to create a `pyroot/bin` with binaries.

// Step 1: use `--system --prefix pyroot`. This disables the broken behavior, and creates `bin` but...
Command::new("python3")
.args(["-m", "pip", "install", "--system", "--prefix"])
.arg(pyroot)
.args(pkg_versions)
.run()?;

// Step 2: move `pyroot/lib/python3.6/site-packages/*` up to `pyroot`
// This seems to successfully replicate the behavior of `--target`
// "mv" is not idempotent however so we need to do "cp -r" then delete
let mut cp_cmd = OsString::new();
cp_cmd.push("cp -r ");
cp_cmd.push(pyroot.as_os_str());
cp_cmd.push("/lib/python*/site-packages/* ");
cp_cmd.push(pyroot.as_os_str());
Command::new("bash").arg("-c").arg(cp_cmd).run()?;

// `lib` is the directory `--prefix` creates that `--target` does not.
std::fs::remove_dir_all(pyroot.join("lib"))?;
if part_v1 != part_v2 {
return Ok(part_v1.cmp(&part_v2));
}
}

Ok(())
Ok(std::cmp::Ordering::Equal)
}

/// This is the final step of setup, where we look for OSes that require additional setup steps
Expand Down Expand Up @@ -165,19 +137,22 @@ mod tests {
use super::*;

#[test]
fn check_pip_major_version() -> Result<()> {
// These read a lot better formatted on one line, so shorten them:
use pip_major_version as p;
// 18.04 example: (with extra newline to test whitespace handling)
assert_eq!(p("pip 9.0.1 from /usr/lib/python3/dist-packages (python 3.6)\n")?, 9);
// a mac
assert_eq!(p("pip 21.1.1 from /usr/local/python3.9/site-packages/pip (python 3.9)")?, 21);
// 20.04
assert_eq!(p("pip 20.0.2 from /usr/lib/python3/dist-packages/pip (python 3.8)")?, 20);
// How mangled can we get and still "work"?
assert_eq!(p("pip 1")?, 1);
assert_eq!(p("p 1")?, 1);
assert_eq!(p("\n\n p 1 p")?, 1);
Ok(())
fn version_greater() {
assert_eq!(compare_versions("3.7.1", "3.6.3"), Ok(std::cmp::Ordering::Greater));
}

#[test]
fn version_less() {
assert_eq!(compare_versions("3.7.1", "3.7.3"), Ok(std::cmp::Ordering::Less));
}

#[test]
fn version_equal() {
assert_eq!(compare_versions("3.6.3", "3.6.3"), Ok(std::cmp::Ordering::Equal));
}

#[test]
fn version_different_len() {
assert_eq!(compare_versions("4.0", "4.0.0"), Ok(std::cmp::Ordering::Equal));
}
}
15 changes: 10 additions & 5 deletions src/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ pub fn setup(use_local_bundle: Option<OsString>) -> Result<()> {

setup_rust_toolchain(&kani_dir)?;

setup_python_deps(&kani_dir, &os)?;
setup_python_deps(&kani_dir)?;

os_hacks::setup_os_hacks(&kani_dir, &os)?;

Expand Down Expand Up @@ -152,16 +152,21 @@ fn setup_rust_toolchain(kani_dir: &Path) -> Result<String> {
}

/// Install into the pyroot the python dependencies we need
fn setup_python_deps(kani_dir: &Path, os: &os_info::Info) -> Result<()> {
fn setup_python_deps(kani_dir: &Path) -> Result<()> {
println!("[4/5] Installing Kani python dependencies...");
let pyroot = kani_dir.join("pyroot");

// TODO: this is a repetition of versions from kani/kani-dependencies
let pkg_versions = &["cbmc-viewer==3.8"];

if os_hacks::should_apply_ubuntu_18_04_python_hack(os)? {
os_hacks::setup_python_deps_on_ubuntu_18_04(&pyroot, pkg_versions)?;
return Ok(());
let cmd_python = Command::new("python3").args(["--version"]).output()?;
let output_python = std::str::from_utf8(&cmd_python.stdout)?;

// Check for minimum version of python=3.7 in the system and bail if not present
if !os_hacks::check_minimum_python_version(output_python)? {
bail!(
"Python version detected is 3.6 or lower. Please upgrade to Python 3.7 to setup Kani."
);
}

Command::new("python3")
Expand Down