Skip to content

Commit 38e11a8

Browse files
committed
Pants for CI.
1 parent f048771 commit 38e11a8

File tree

1 file changed

+294
-0
lines changed

1 file changed

+294
-0
lines changed

pants

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
#!/usr/bin/env bash
2+
# Copyright 2020 Pants project contributors (see CONTRIBUTORS.md).
3+
# Licensed under the Apache License, Version 2.0 (see LICENSE).
4+
5+
# =============================== NOTE ===============================
6+
# This ./pants bootstrap script comes from the pantsbuild/setup
7+
# project. It is intended to be checked into your code repository so
8+
# that other developers have the same setup.
9+
#
10+
# Learn more here: https://www.pantsbuild.org/docs/installation
11+
# ====================================================================
12+
13+
set -eou pipefail
14+
15+
# NOTE: To use an unreleased version of Pants from the pantsbuild/pants master branch,
16+
# locate the master branch SHA, set PANTS_SHA=<SHA> in the environment, and run this script as usual.
17+
#
18+
# E.g., PANTS_SHA=725fdaf504237190f6787dda3d72c39010a4c574 ./pants --version
19+
20+
PYTHON_BIN_NAME="${PYTHON:-unspecified}"
21+
22+
# Set this to specify a non-standard location for this script to read the Pants version from.
23+
# NB: This will *not* cause Pants itself to use this location as a config file.
24+
# You can use PANTS_CONFIG_FILES or --pants-config-files to do so.
25+
PANTS_TOML=${PANTS_TOML:-pants.toml}
26+
27+
PANTS_BIN_NAME="${PANTS_BIN_NAME:-$0}"
28+
29+
PANTS_SETUP_CACHE="${PANTS_SETUP_CACHE:-${XDG_CACHE_HOME:-$HOME/.cache}/pants/setup}"
30+
# If given a relative path, we fix it to be absolute.
31+
if [[ "$PANTS_SETUP_CACHE" != /* ]]; then
32+
PANTS_SETUP_CACHE="${PWD}/${PANTS_SETUP_CACHE}"
33+
fi
34+
35+
PANTS_BOOTSTRAP="${PANTS_SETUP_CACHE}/bootstrap-$(uname -s)-$(uname -m)"
36+
37+
VENV_VERSION=${VENV_VERSION:-20.2.2}
38+
39+
VENV_PACKAGE=virtualenv-${VENV_VERSION}
40+
VENV_TARBALL=${VENV_PACKAGE}.tar.gz
41+
42+
COLOR_RED="\x1b[31m"
43+
COLOR_GREEN="\x1b[32m"
44+
COLOR_RESET="\x1b[0m"
45+
46+
function log() {
47+
echo -e "$@" 1>&2
48+
}
49+
50+
function die() {
51+
(($# > 0)) && log "${COLOR_RED}$*${COLOR_RESET}"
52+
exit 1
53+
}
54+
55+
function green() {
56+
(($# > 0)) && log "${COLOR_GREEN}$*${COLOR_RESET}"
57+
}
58+
59+
function tempdir {
60+
mktemp -d "$1"/pants.XXXXXX
61+
}
62+
63+
function get_exe_path_or_die {
64+
local exe="$1"
65+
if ! command -v "${exe}"; then
66+
die "Could not find ${exe}. Please ensure ${exe} is on your PATH."
67+
fi
68+
}
69+
70+
function get_pants_config_value {
71+
local config_key="$1"
72+
local optional_space="[[:space:]]*"
73+
local prefix="^${config_key}${optional_space}=${optional_space}"
74+
local raw_value
75+
raw_value="$(sed -ne "/${prefix}/ s#${prefix}##p" "${PANTS_TOML}")"
76+
echo "${raw_value}" | tr -d \"\' && return 0
77+
return 0
78+
}
79+
80+
function get_python_major_minor_version {
81+
local python_exe="$1"
82+
"$python_exe" <<EOF
83+
import sys
84+
major_minor_version = ''.join(str(version_num) for version_num in sys.version_info[0:2])
85+
print(major_minor_version)
86+
EOF
87+
}
88+
89+
# The high-level flow:
90+
#
91+
# 1.) Resolve the Pants version from config so that we know what interpreters we can use, what to name the venv,
92+
# and what to install via pip.
93+
# 2.) Resolve the Python interpreter, first reading from the env var $PYTHON, then using a default based on the Pants
94+
# version.
95+
# 3.) Check if the venv already exists via a naming convention, and create the venv if not found.
96+
# 4.) Execute Pants with the resolved Python interpreter and venv.
97+
#
98+
# After that, Pants itself will handle making sure any requested plugins
99+
# are installed and up to date.
100+
101+
function determine_pants_version {
102+
if [ -n "${PANTS_SHA:-}" ]; then
103+
# get_version_for_sha will echo the version, thus "returning" it from this function.
104+
get_version_for_sha "$PANTS_SHA"
105+
return
106+
fi
107+
108+
pants_version="$(get_pants_config_value 'pants_version')"
109+
if [[ -z "${pants_version}" ]]; then
110+
die "Please explicitly specify the \`pants_version\` in your \`pants.toml\` under the
111+
\`[GLOBAL]\` scope. See https://pypi.org/project/pantsbuild.pants/#history for all released
112+
versions and https://www.pantsbuild.org/docs/installation for more instructions."
113+
fi
114+
pants_major_version="$(echo "${pants_version}" | cut -d '.' -f1)"
115+
pants_minor_version="$(echo "${pants_version}" | cut -d '.' -f2)"
116+
# 1.26 is the first version to support `pants.toml`, so we fail eagerly if using an outdated version.
117+
if [[ "${pants_major_version}" -eq 1 && "${pants_minor_version}" -le 25 ]]; then
118+
die "This version of the \`./pants\` script does not work with Pants <= 1.25.0 (and it also requires using \`pants.toml\`,
119+
rather than \`pants.ini\`). Instead, either upgrade your \`pants_version\` or use the version of the \`./pants\` script
120+
at https://raw.githubusercontent.com/Eric-Arellano/setup/0d445edef57cb89fd830db70810e38f050b0a268/pants."
121+
fi
122+
echo "${pants_version}"
123+
}
124+
125+
function set_supported_python_versions {
126+
local pants_version="$1"
127+
local pants_major_version
128+
local pants_minor_version
129+
pants_major_version="$(echo "${pants_version}" | cut -d '.' -f1)"
130+
pants_minor_version="$(echo "${pants_version}" | cut -d '.' -f2)"
131+
if [[ "${pants_major_version}" -eq 1 ]]; then
132+
supported_python_versions_decimal=('3.6' '3.7' '3.8')
133+
supported_python_versions_int=('36' '37' '38')
134+
supported_message='3.6, 3.7, or 3.8'
135+
elif [[ "${pants_major_version}" -eq 2 && "${pants_minor_version}" -eq 0 ]]; then
136+
supported_python_versions_decimal=('3.6' '3.7' '3.8')
137+
supported_python_versions_int=('36' '37' '38')
138+
supported_message='3.6, 3.7, or 3.8'
139+
elif [[ "${pants_major_version}" -eq 2 && "${pants_minor_version}" -eq 1 ]]; then
140+
supported_python_versions_decimal=('3.7' '3.8' '3.6')
141+
supported_python_versions_int=('37' '38' '36')
142+
supported_message='3.7, 3.8, or 3.6 (deprecated)'
143+
else
144+
supported_python_versions_decimal=('3.7' '3.8')
145+
supported_python_versions_int=('37' '38')
146+
supported_message='3.7 or 3.8'
147+
fi
148+
}
149+
150+
function determine_default_python_exe {
151+
for version in "${supported_python_versions_decimal[@]}"; do
152+
local interpreter_path
153+
interpreter_path="$(command -v "python${version}")"
154+
if [[ -z "${interpreter_path}" ]]; then
155+
continue
156+
fi
157+
# Check if the Python version is installed via Pyenv but not activated.
158+
if [[ "$("${interpreter_path}" --version 2>&1 > /dev/null)" == "pyenv: python${version}"* ]]; then
159+
continue
160+
fi
161+
echo "${interpreter_path}" && return 0
162+
done
163+
}
164+
165+
function determine_python_exe {
166+
local pants_version="$1"
167+
set_supported_python_versions "${pants_version}"
168+
local requirement_str="For \`pants_version = \"${pants_version}\"\`, Pants requires Python ${supported_message} to run."
169+
170+
local python_bin_name
171+
if [[ "${PYTHON_BIN_NAME}" != 'unspecified' ]]; then
172+
python_bin_name="${PYTHON_BIN_NAME}"
173+
else
174+
python_bin_name="$(determine_default_python_exe)"
175+
if [[ -z "${python_bin_name}" ]]; then
176+
die "No valid Python interpreter found. ${requirement_str} Please check that a valid interpreter is installed and on your \$PATH."
177+
fi
178+
fi
179+
local python_exe
180+
python_exe="$(get_exe_path_or_die "${python_bin_name}")"
181+
local major_minor_version
182+
major_minor_version="$(get_python_major_minor_version "${python_exe}")"
183+
for valid_version in "${supported_python_versions_int[@]}"; do
184+
if [[ "${major_minor_version}" == "${valid_version}" ]]; then
185+
echo "${python_exe}" && return 0
186+
fi
187+
done
188+
die "Invalid Python interpreter version for ${python_exe}. ${requirement_str}"
189+
}
190+
191+
# TODO(John Sirois): GC race loser tmp dirs leftover from bootstrap_XXX
192+
# functions. Any tmp dir w/o a symlink pointing to it can go.
193+
194+
function bootstrap_venv {
195+
if [[ ! -d "${PANTS_BOOTSTRAP}/${VENV_PACKAGE}" ]]; then
196+
(
197+
mkdir -p "${PANTS_BOOTSTRAP}"
198+
local staging_dir
199+
staging_dir=$(tempdir "${PANTS_BOOTSTRAP}")
200+
cd "${staging_dir}"
201+
curl -LO "https://pypi.io/packages/source/v/virtualenv/${VENV_TARBALL}"
202+
tar -xzf "${VENV_TARBALL}"
203+
ln -s "${staging_dir}/${VENV_PACKAGE}" "${staging_dir}/latest"
204+
mv "${staging_dir}/latest" "${PANTS_BOOTSTRAP}/${VENV_PACKAGE}"
205+
) 1>&2
206+
fi
207+
208+
local venv_path="${PANTS_BOOTSTRAP}/${VENV_PACKAGE}"
209+
local venv_entry_point
210+
211+
# shellcheck disable=SC2086
212+
if [[ -f "${venv_path}/virtualenv.py" ]]; then
213+
venv_entry_point="${venv_path}/virtualenv.py"
214+
elif [[ -f "${venv_path}/src/virtualenv/__main__.py" ]]; then
215+
venv_entry_point="${venv_path}/src/virtualenv/__main__.py"
216+
else
217+
die "Could not find virtualenv entry point for version $VENV_VERSION"
218+
fi
219+
220+
echo "${venv_entry_point}"
221+
}
222+
223+
function find_links_url {
224+
local pants_version="$1"
225+
local pants_sha="$2"
226+
echo -n "https://binaries.pantsbuild.org/wheels/pantsbuild.pants/${pants_sha}/${pants_version/+/%2B}/index.html"
227+
}
228+
229+
function get_version_for_sha {
230+
local sha="$1"
231+
232+
# Retrieve the Pants version associated with this commit.
233+
local pants_version
234+
pants_version="$(curl --fail -sL "https://raw.githubusercontent.com/pantsbuild/pants/${sha}/src/python/pants/VERSION")"
235+
236+
# Construct the version as the release version from src/python/pants/VERSION, plus the string `+gitXXXXXXXX`,
237+
# where the XXXXXXXX is the first 8 characters of the SHA.
238+
echo "${pants_version}+git${sha:0:8}"
239+
}
240+
241+
function bootstrap_pants {
242+
local pants_version="$1"
243+
local python="$2"
244+
local pants_sha="${3:-}"
245+
246+
local pants_requirement="pantsbuild.pants==${pants_version}"
247+
local maybe_find_links
248+
if [[ -z "${pants_sha}" ]]; then
249+
maybe_find_links=""
250+
else
251+
maybe_find_links="--find-links=$(find_links_url "${pants_version}" "${pants_sha}")"
252+
fi
253+
local python_major_minor_version
254+
python_major_minor_version="$(get_python_major_minor_version "${python}")"
255+
local target_folder_name
256+
target_folder_name="${pants_version}_py${python_major_minor_version}"
257+
258+
if [[ ! -d "${PANTS_BOOTSTRAP}/${target_folder_name}" ]]; then
259+
(
260+
local venv_entry_point="$(bootstrap_venv)"
261+
local staging_dir
262+
staging_dir=$(tempdir "${PANTS_BOOTSTRAP}")
263+
"${python}" "${venv_entry_point}" --no-download "${staging_dir}/install" && \
264+
"${staging_dir}/install/bin/pip" install -U pip && \
265+
"${staging_dir}/install/bin/pip" install ${maybe_find_links} --progress-bar off "${pants_requirement}" && \
266+
ln -s "${staging_dir}/install" "${staging_dir}/${target_folder_name}" && \
267+
mv "${staging_dir}/${target_folder_name}" "${PANTS_BOOTSTRAP}/${target_folder_name}" && \
268+
green "New virtual environment successfully created at ${PANTS_BOOTSTRAP}/${target_folder_name}."
269+
) 1>&2
270+
fi
271+
echo "${PANTS_BOOTSTRAP}/${target_folder_name}"
272+
}
273+
274+
# Ensure we operate from the context of the ./pants buildroot.
275+
cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
276+
pants_version="$(determine_pants_version)"
277+
python="$(determine_python_exe "${pants_version}")"
278+
pants_dir="$(bootstrap_pants "${pants_version}" "${python}" "${PANTS_SHA:-}")"
279+
pants_python="${pants_dir}/bin/python"
280+
pants_binary="${pants_dir}/bin/pants"
281+
pants_extra_args=""
282+
if [[ -n "${PANTS_SHA:-}" ]]; then
283+
pants_extra_args="${pants_extra_args} --python-repos-repos=$(find_links_url "$pants_version" "$PANTS_SHA")"
284+
fi
285+
286+
# We set the env var no_proxy to '*', to work around an issue with urllib using non
287+
# async-signal-safe syscalls after we fork a process that has already spawned threads.
288+
#
289+
# See https://blog.phusion.nl/2017/10/13/why-ruby-app-servers-break-on-macos-high-sierra-and-what-can-be-done-about-it/
290+
export no_proxy='*'
291+
292+
# shellcheck disable=SC2086
293+
exec "${pants_python}" "${pants_binary}" ${pants_extra_args} \
294+
--pants-bin-name="${PANTS_BIN_NAME}" --pants-version=${pants_version} "$@"

0 commit comments

Comments
 (0)