GitHub Viewer
#!/usr/bin/env python
"""Script to create self contained install.
The goal of this script is simple:
* Create a self contained install of the CLI that
has requires no external resources during installation.
It does this by using all the normal python tooling
(virtualenv, pip) but provides a simple, easy to use
interface for those not familiar with the python
ecosystem.
"""
import os
import sys
import subprocess
import shutil
import tempfile
import zipfile
from contextlib import contextmanager
EXTRA_RUNTIME_DEPS = [
# Use an up to date virtualenv/pip/setuptools on > 2.6.
('virtualenv', '16.7.8'),
('jmespath', '0.10.0'),
]
BUILDTIME_DEPS = [
('setuptools-scm', '3.3.3'),
('wheel', '0.33.6'),
]
PIP_DOWNLOAD_ARGS = '--no-binary :all:'
class BadRCError(Exception):
pass
@contextmanager
def cd(dirname):
original = os.getcwd()
os.chdir(dirname)
try:
yield
finally:
os.chdir(original)
def run(cmd):
sys.stdout.write("Running cmd: %s\n" % cmd)
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
rc = p.wait()
if p.returncode != 0:
raise BadRCError("Bad rc (%s) for cmd '%s': %s" % (
rc, cmd, stderr + stdout))
return stdout
def create_scratch_dir():
# This creates the dir where all the bundling occurs.
# First we need a top level dir.
dirname = tempfile.mkdtemp(prefix='bundle')
# Then we need to create a dir where all the packages
# will come from.
os.mkdir(os.path.join(dirname, 'packages'))
os.mkdir(os.path.join(dirname, 'packages', 'setup'))
return dirname
def download_package_tarballs(dirname, packages):
with cd(dirname):
for package, package_version in packages:
run('%s -m pip download %s==%s %s' % (
sys.executable, package, package_version, PIP_DOWNLOAD_ARGS
))
def download_cli_deps(scratch_dir):
awscli_dir = os.path.dirname(
os.path.dirname(os.path.abspath(__file__)))
with cd(scratch_dir):
run('pip download %s %s' % (
PIP_DOWNLOAD_ARGS, awscli_dir))
def _remove_cli_zip(scratch_dir):
clidir = [f for f in os.listdir(scratch_dir) if f.startswith('awscli')]
assert len(clidir) == 1
os.remove(os.path.join(scratch_dir, clidir[0]))
def add_cli_sdist(scratch_dir):
awscli_dir = os.path.dirname(
os.path.dirname(os.path.abspath(__file__)))
if os.path.exists(os.path.join(awscli_dir, 'dist')):
shutil.rmtree(os.path.join(awscli_dir, 'dist'))
with cd(awscli_dir):
run('%s setup.py sdist' % sys.executable)
filename = os.listdir('dist')[0]
shutil.move(os.path.join('dist', filename),
os.path.join(scratch_dir, filename))
def create_bootstrap_script(scratch_dir):
install_script = os.path.join(
os.path.dirname(os.path.abspath(__file__)), 'install')
shutil.copy(install_script, os.path.join(scratch_dir, 'install'))
def zip_dir(scratch_dir):
basename = 'awscli-bundle.zip'
dirname, tmpdir = os.path.split(scratch_dir)
final_dir_name = os.path.join(dirname, 'awscli-bundle')
if os.path.isdir(final_dir_name):
shutil.rmtree(final_dir_name)
shutil.move(scratch_dir, final_dir_name)
with cd(dirname):
with zipfile.ZipFile(basename, 'w', zipfile.ZIP_DEFLATED) as zipped:
for root, dirnames, filenames in os.walk('awscli-bundle'):
for filename in filenames:
zipped.write(os.path.join(root, filename))
return os.path.join(dirname, basename)
def verify_preconditions():
# The pip version looks like:
# 'pip 1.4.1 from ....'
pip_version = run(
'%s -m pip --version' % sys.executable).strip().split()[1]
# Virtualenv version just has the version string: '1.14.5\n'
virtualenv_version = run(
'%s -m virtualenv --version' % sys.executable).strip()
_min_version_required('9.0.1', pip_version, 'pip')
_min_version_required('15.1.0', virtualenv_version, 'virtualenv')
def _min_version_required(min_version, actual_version, name):
# precondition: min_version is major.minor.patch
# actual_version is major.minor.patch
min_split = min_version.split('.')
actual_split = actual_version.decode('utf-8').split('.')
for min_version_part, actual_version_part in zip(min_split, actual_split):
if int(actual_version_part) >= int(min_version_part):
return
raise ValueError("%s requires at least version %s, but version %s was "
"found." % (name, min_version, actual_version))
def main():
verify_preconditions()
scratch_dir = create_scratch_dir()
package_dir = os.path.join(scratch_dir, 'packages')
print("Bundle dir at: %s" % scratch_dir)
download_package_tarballs(
package_dir,
packages=EXTRA_RUNTIME_DEPS,
)
# Some packages require setup time dependencies, and so we will need to
# manually install them. We isolate them to a particular directory so we
# can run the install before the things they're dependent on. We have to do
# this because pip won't actually find them since it doesn't handle build
# dependencies.
setup_dir = os.path.join(package_dir, 'setup')
download_package_tarballs(
setup_dir,
packages=BUILDTIME_DEPS,
)
download_cli_deps(package_dir)
add_cli_sdist(package_dir)
create_bootstrap_script(scratch_dir)
zip_filename = zip_dir(scratch_dir)
print("Zipped bundle installer is at: %s" % zip_filename)
if __name__ == '__main__':
main()