diff options
-rw-r--r-- | .gitignore | 6 | ||||
-rw-r--r-- | README.md | 21 | ||||
-rw-r--r-- | myapp/BUILD | 12 | ||||
-rw-r--r-- | myapp/myapp.py | 8 | ||||
-rw-r--r-- | myapp/requirements.txt | 1 | ||||
-rw-r--r-- | mylib/BUILD | 1 | ||||
-rw-r--r-- | mylib/__init__.py | 3 | ||||
-rw-r--r-- | mylib/main.py | 0 | ||||
-rwxr-xr-x | pants | 372 | ||||
-rw-r--r-- | pants.toml | 9 |
10 files changed, 433 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d2553fc --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# Pants workspace files +/.pants.d/ +/dist/ +/.pids +/.pants.workdir.file_lock* + diff --git a/README.md b/README.md new file mode 100644 index 0000000..340104c --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# Sample Pants Project + +This is just an example of how to use Pants to manage a monorepo. To run from +scratch you will need to activate a python interpreter `3.6.15` and the +following command. + +``` +./pants run myapp:pex +``` + +or to build a pex + +``` +./pants package myapp:pex +./dist/myapp/pex.pex +``` + +Things Tested: + - How to include a requirements + - How to pin a interpreter version (myapp) + - How to include an additional local dependancy (mylib) diff --git a/myapp/BUILD b/myapp/BUILD new file mode 100644 index 0000000..4061830 --- /dev/null +++ b/myapp/BUILD @@ -0,0 +1,12 @@ +python_library() + +# You don't need to pass in 'requirements_relpath' if the file +# is called 'requirements.txt' but I added this just for explicitness +python_requirements(requirements_relpath="requirements.txt") + +pex_binary( + name="pex", + dependencies=["mylib"], + entry_point="myapp.py", + interpreter_constraints=["CPython==3.6.15"], +) diff --git a/myapp/myapp.py b/myapp/myapp.py new file mode 100644 index 0000000..7f5c16b --- /dev/null +++ b/myapp/myapp.py @@ -0,0 +1,8 @@ +import requests + +import mylib + + +print(requests.get("https://www.google.com")) +print(requests.__version__) +mylib.hello_world() diff --git a/myapp/requirements.txt b/myapp/requirements.txt new file mode 100644 index 0000000..7d34355 --- /dev/null +++ b/myapp/requirements.txt @@ -0,0 +1 @@ +requests==2.25.0 diff --git a/mylib/BUILD b/mylib/BUILD new file mode 100644 index 0000000..d80ecb5 --- /dev/null +++ b/mylib/BUILD @@ -0,0 +1 @@ +python_library() diff --git a/mylib/__init__.py b/mylib/__init__.py new file mode 100644 index 0000000..3f1e31f --- /dev/null +++ b/mylib/__init__.py @@ -0,0 +1,3 @@ +"""My super library.""" +def hello_world(): + print("Hello World!") diff --git a/mylib/main.py b/mylib/main.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/mylib/main.py @@ -0,0 +1,372 @@ +#!/usr/bin/env bash +# Copyright 2020 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +# =============================== NOTE =============================== +# This ./pants bootstrap script comes from the pantsbuild/setup +# project. It is intended to be checked into your code repository so +# that other developers have the same setup. +# +# Learn more here: https://www.pantsbuild.org/docs/installation +# ==================================================================== + +set -eou pipefail + +# NOTE: To use an unreleased version of Pants from the pantsbuild/pants main branch, +# locate the main branch SHA, set PANTS_SHA=<SHA> in the environment, and run this script as usual. +# +# E.g., PANTS_SHA=725fdaf504237190f6787dda3d72c39010a4c574 ./pants --version + +PYTHON_BIN_NAME="${PYTHON:-unspecified}" + +# Set this to specify a non-standard location for this script to read the Pants version from. +# NB: This will *not* cause Pants itself to use this location as a config file. +# You can use PANTS_CONFIG_FILES or --pants-config-files to do so. +PANTS_TOML=${PANTS_TOML:-pants.toml} + +PANTS_BIN_NAME="${PANTS_BIN_NAME:-$0}" + +PANTS_SETUP_CACHE="${PANTS_SETUP_CACHE:-${XDG_CACHE_HOME:-$HOME/.cache}/pants/setup}" +# If given a relative path, we fix it to be absolute. +if [[ "$PANTS_SETUP_CACHE" != /* ]]; then + PANTS_SETUP_CACHE="${PWD}/${PANTS_SETUP_CACHE}" +fi + +PANTS_BOOTSTRAP="${PANTS_SETUP_CACHE}/bootstrap-$(uname -s)-$(uname -m)" + +_PEX_VERSION=2.1.42 +_PEX_URL="https://github.com/pantsbuild/pex/releases/download/v${_PEX_VERSION}/pex" +_PEX_EXPECTED_SHA256="69d6b1b1009b00dd14a3a9f19b72cff818a713ca44b3186c9b12074b2a31e51f" + +VIRTUALENV_VERSION=20.4.7 +VIRTUALENV_REQUIREMENTS=$( +cat << EOF +virtualenv==${VIRTUALENV_VERSION} --hash sha256:2b0126166ea7c9c3661f5b8e06773d28f83322de7a3ff7d06f0aed18c9de6a76 +filelock==3.0.12 --hash sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836 +six==1.16.0 --hash sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 +distlib==0.3.2 --hash sha256:23e223426b28491b1ced97dc3bbe183027419dfc7982b4fa2f05d5f3ff10711c +appdirs==1.4.4 --hash sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128 +importlib-resources==5.1.4; python_version < "3.7" --hash sha256:e962bff7440364183203d179d7ae9ad90cb1f2b74dcb84300e88ecc42dca3351 +importlib-metadata==4.5.0; python_version < "3.8" --hash sha256:833b26fb89d5de469b24a390e9df088d4e52e4ba33b01dc5e0e4f41b81a16c00 +zipp==3.4.1; python_version < "3.10" --hash sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098 +typing-extensions==3.10.0.0; python_version < "3.8" --hash sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84 +EOF +) + +COLOR_RED="\x1b[31m" +COLOR_GREEN="\x1b[32m" +COLOR_YELLOW="\x1b[33m" +COLOR_RESET="\x1b[0m" + +function log() { + echo -e "$@" 1>&2 +} + +function die() { + (($# > 0)) && log "${COLOR_RED}$*${COLOR_RESET}" + exit 1 +} + +function green() { + (($# > 0)) && log "${COLOR_GREEN}$*${COLOR_RESET}" +} + +function warn() { + (($# > 0)) && log "${COLOR_YELLOW}$*${COLOR_RESET}" +} + +function tempdir { + mkdir -p "$1" + mktemp -d "$1"/pants.XXXXXX +} + +function get_exe_path_or_die { + local exe="$1" + if ! command -v "${exe}"; then + die "Could not find ${exe}. Please ensure ${exe} is on your PATH." + fi +} + +function get_pants_config_value { + local config_key="$1" + local optional_space="[[:space:]]*" + local prefix="^${config_key}${optional_space}=${optional_space}" + local raw_value + raw_value="$(sed -ne "/${prefix}/ s#${prefix}##p" "${PANTS_TOML}")" + echo "${raw_value}" | tr -d \"\' && return 0 + return 0 +} + +function get_python_major_minor_version { + local python_exe="$1" + "$python_exe" <<EOF +import sys +major_minor_version = ''.join(str(version_num) for version_num in sys.version_info[0:2]) +print(major_minor_version) +EOF +} + +# The high-level flow: +# +# 1.) Resolve the Pants version from config so that we know what interpreters we can use, what to name the venv, +# and what to install via pip. +# 2.) Resolve the Python interpreter, first reading from the env var $PYTHON, then using a default based on the Pants +# version. +# 3.) Check if the venv already exists via a naming convention, and create the venv if not found. +# 4.) Execute Pants with the resolved Python interpreter and venv. +# +# After that, Pants itself will handle making sure any requested plugins +# are installed and up to date. + +function determine_pants_version { + if [ -n "${PANTS_SHA:-}" ]; then + # get_version_for_sha will echo the version, thus "returning" it from this function. + get_version_for_sha "$PANTS_SHA" + return + fi + + pants_version="$(get_pants_config_value 'pants_version')" + if [[ -z "${pants_version}" ]]; then + die "Please explicitly specify the \`pants_version\` in your \`pants.toml\` under the \`[GLOBAL]\` scope. +See https://pypi.org/project/pantsbuild.pants/#history for all released versions +and https://www.pantsbuild.org/docs/installation for more instructions." + fi + pants_major_version="$(echo "${pants_version}" | cut -d '.' -f1)" + pants_minor_version="$(echo "${pants_version}" | cut -d '.' -f2)" + # 1.26 is the first version to support `pants.toml`, so we fail eagerly if using an outdated version. + if [[ "${pants_major_version}" -eq 1 && "${pants_minor_version}" -le 25 ]]; then + die "This version of the \`./pants\` script does not work with Pants <= 1.25.0 (and it also requires using \`pants.toml\`, +rather than \`pants.ini\`). Instead, either upgrade your \`pants_version\` or use the version of the \`./pants\` script +at https://raw.githubusercontent.com/Eric-Arellano/setup/0d445edef57cb89fd830db70810e38f050b0a268/pants." + fi + echo "${pants_version}" +} + +function set_supported_python_versions { + local pants_version="$1" + local pants_major_version + local pants_minor_version + pants_major_version="$(echo "${pants_version}" | cut -d '.' -f1)" + pants_minor_version="$(echo "${pants_version}" | cut -d '.' -f2)" + if [[ "${pants_major_version}" -eq 1 ]]; then + supported_python_versions_decimal=('3.6' '3.7' '3.8') + supported_python_versions_int=('36' '37' '38') + supported_message='3.6, 3.7, or 3.8' + elif [[ "${pants_major_version}" -eq 2 && "${pants_minor_version}" -eq 0 ]]; then + supported_python_versions_decimal=('3.6' '3.7' '3.8') + supported_python_versions_int=('36' '37' '38') + supported_message='3.6, 3.7, or 3.8' + elif [[ "${pants_major_version}" -eq 2 && "${pants_minor_version}" -eq 1 ]]; then + supported_python_versions_decimal=('3.7' '3.8' '3.6') + supported_python_versions_int=('37' '38' '36') + supported_message='3.7, 3.8, or 3.6 (deprecated)' + elif [[ "${pants_major_version}" -eq 2 && "${pants_minor_version}" -lt 5 ]]; then + supported_python_versions_decimal=('3.8' '3.7') + supported_python_versions_int=('38' '37') + supported_message='3.7 or 3.8' + else + # We put 3.9 first because Apple Silicon only works properly with Python 3.9, even though it's possible to have + # older Pythons installed. This makes it more likely that Pants will work out-of-the-box. + supported_python_versions_decimal=('3.9' '3.8' '3.7') + supported_python_versions_int=('39' '38' '37') + supported_message='3.7, 3.8, or 3.9' + fi +} + +function check_python_exe_compatible_version { + local python_exe="$1" + local major_minor_version + major_minor_version="$(get_python_major_minor_version "${python_exe}")" + for valid_version in "${supported_python_versions_int[@]}"; do + if [[ "${major_minor_version}" == "${valid_version}" ]]; then + echo "${python_exe}" && return 0 + fi + done +} + +function determine_default_python_exe { + for version in "${supported_python_versions_decimal[@]}" "3" ""; do + local interpreter_path + interpreter_path="$(command -v "python${version}")" + if [[ -z "${interpreter_path}" ]]; then + continue + fi + # Check if the Python version is installed via Pyenv but not activated. + if [[ "$("${interpreter_path}" --version 2>&1 > /dev/null)" == "pyenv: python${version}"* ]]; then + continue + fi + if [[ -n "$(check_python_exe_compatible_version "${interpreter_path}")" ]]; then + echo "${interpreter_path}" && return 0 + fi + done +} + +function determine_python_exe { + local pants_version="$1" + set_supported_python_versions "${pants_version}" + local requirement_str="For \`pants_version = \"${pants_version}\"\`, Pants requires Python ${supported_message} to run." + + local python_exe + if [[ "${PYTHON_BIN_NAME}" != 'unspecified' ]]; then + python_exe="$(get_exe_path_or_die "${PYTHON_BIN_NAME}")" || exit 1 + if [[ -z "$(check_python_exe_compatible_version "${python_exe}")" ]]; then + die "Invalid Python interpreter version for ${python_exe}. ${requirement_str}" + fi + else + python_exe="$(determine_default_python_exe)" + if [[ -z "${python_exe}" ]]; then + die "No valid Python interpreter found. ${requirement_str} Please check that a valid interpreter is installed and on your \$PATH." + fi + fi + echo "${python_exe}" +} + +function compute_sha256 { + local python="$1" + local path="$2" + + "$python" <<EOF +import hashlib + +hasher = hashlib.sha256() +with open('${path}', 'rb') as fp: + buf = fp.read() + hasher.update(buf) +print(hasher.hexdigest()) +EOF +} + +# TODO(John Sirois): GC race loser tmp dirs leftover from bootstrap_XXX +# functions. Any tmp dir w/o a symlink pointing to it can go. + +function bootstrap_pex { + local python="$1" + local bootstrapped="${PANTS_BOOTSTRAP}/pex-${_PEX_VERSION}/pex" + if [[ ! -f "${bootstrapped}" ]]; then + ( + green "Downloading the Pex PEX." + mkdir -p "${PANTS_BOOTSTRAP}" + local staging_dir + staging_dir=$(tempdir "${PANTS_BOOTSTRAP}") + cd "${staging_dir}" + curl -LO "${_PEX_URL}" + fingerprint="$(compute_sha256 "${python}" "pex")" + if [[ "${_PEX_EXPECTED_SHA256}" != "${fingerprint}" ]]; then + die "SHA256 of ${_PEX_URL} is not as expected. Aborting." + fi + green "SHA256 fingerprint of ${_PEX_URL} verified." + mkdir -p "$(dirname "${bootstrapped}")" + mv -f "${staging_dir}/pex" "${bootstrapped}" + rmdir "${staging_dir}" + ) 1>&2 || exit 1 + fi + echo "${bootstrapped}" +} + +function scrub_PEX_env_vars { + # Ensure the virtualenv PEX runs as shrink-wrapped. + # See: https://github.com/pantsbuild/setup/issues/105 + if [[ -n "${!PEX_@}" ]]; then + warn "Scrubbing ${!PEX_@}" + unset "${!PEX_@}" + fi +} + +function bootstrap_virtualenv { + local python="$1" + local bootstrapped="${PANTS_BOOTSTRAP}/virtualenv-${VIRTUALENV_VERSION}/virtualenv.pex" + if [[ ! -f "${bootstrapped}" ]]; then + ( + green "Creating the virtualenv PEX." + pex_path="$(bootstrap_pex "${python}")" || exit 1 + mkdir -p "${PANTS_BOOTSTRAP}" + local staging_dir + staging_dir=$(tempdir "${PANTS_BOOTSTRAP}") + cd "${staging_dir}" + echo "${VIRTUALENV_REQUIREMENTS}" > requirements.txt + ( + scrub_PEX_env_vars + "${python}" "${pex_path}" -r requirements.txt -c virtualenv -o virtualenv.pex + ) + mkdir -p "$(dirname "${bootstrapped}")" + mv -f "${staging_dir}/virtualenv.pex" "${bootstrapped}" + rm -rf "${staging_dir}" + ) 1>&2 || exit 1 + fi + echo "${bootstrapped}" +} + +function find_links_url { + local pants_version="$1" + local pants_sha="$2" + echo -n "https://binaries.pantsbuild.org/wheels/pantsbuild.pants/${pants_sha}/${pants_version/+/%2B}/index.html" +} + +function get_version_for_sha { + local sha="$1" + + # Retrieve the Pants version associated with this commit. + local pants_version + pants_version="$(curl --fail -sL "https://raw.githubusercontent.com/pantsbuild/pants/${sha}/src/python/pants/VERSION")" + + # Construct the version as the release version from src/python/pants/VERSION, plus the string `+gitXXXXXXXX`, + # where the XXXXXXXX is the first 8 characters of the SHA. + echo "${pants_version}+git${sha:0:8}" +} + +function bootstrap_pants { + local pants_version="$1" + local python="$2" + local pants_sha="${3:-}" + + local pants_requirement="pantsbuild.pants==${pants_version}" + local maybe_find_links + if [[ -z "${pants_sha}" ]]; then + maybe_find_links="" + else + maybe_find_links="--find-links=$(find_links_url "${pants_version}" "${pants_sha}")" + fi + local python_major_minor_version + python_major_minor_version="$(get_python_major_minor_version "${python}")" + local target_folder_name="${pants_version}_py${python_major_minor_version}" + local bootstrapped="${PANTS_BOOTSTRAP}/${target_folder_name}" + + if [[ ! -d "${bootstrapped}" ]]; then + ( + green "Bootstrapping Pants using ${python}" + local staging_dir + staging_dir=$(tempdir "${PANTS_BOOTSTRAP}") + local virtualenv_path + virtualenv_path="$(bootstrap_virtualenv "${python}")" || exit 1 + green "Installing ${pants_requirement} into a virtual environment at ${bootstrapped}" + ( + scrub_PEX_env_vars + # shellcheck disable=SC2086 + "${python}" "${virtualenv_path}" --no-download "${staging_dir}/install" && \ + "${staging_dir}/install/bin/pip" install -U pip && \ + "${staging_dir}/install/bin/pip" install ${maybe_find_links} --progress-bar off "${pants_requirement}" + ) && \ + ln -s "${staging_dir}/install" "${staging_dir}/${target_folder_name}" && \ + mv "${staging_dir}/${target_folder_name}" "${bootstrapped}" && \ + green "New virtual environment successfully created at ${bootstrapped}." + ) 1>&2 || exit 1 + fi + echo "${bootstrapped}" +} + +# Ensure we operate from the context of the ./pants buildroot. +cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +pants_version="$(determine_pants_version)" +python="$(determine_python_exe "${pants_version}")" +pants_dir="$(bootstrap_pants "${pants_version}" "${python}" "${PANTS_SHA:-}")" || exit 1 + +pants_python="${pants_dir}/bin/python" +pants_binary="${pants_dir}/bin/pants" +pants_extra_args="" +if [[ -n "${PANTS_SHA:-}" ]]; then + pants_extra_args="${pants_extra_args} --python-repos-repos=$(find_links_url "$pants_version" "$PANTS_SHA")" +fi + +# shellcheck disable=SC2086 +exec "${pants_python}" "${pants_binary}" ${pants_extra_args} \ + --pants-bin-name="${PANTS_BIN_NAME}" --pants-version=${pants_version} "$@" diff --git a/pants.toml b/pants.toml new file mode 100644 index 0000000..ca4fd82 --- /dev/null +++ b/pants.toml @@ -0,0 +1,9 @@ +[GLOBAL] +pants_version = "2.7.1" +backend_packages = ["pants.backend.python"] + +[anonymous-telemetry] +enabled = false + +[source] +marker_filenames = [".SOURCE_ROOT"] |