prereqs.py 4.44 KB
Newer Older
Will Daly committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
"""
Install Python, Ruby, and Node prerequisites.
"""

import os
import hashlib
from distutils import sysconfig
from paver.easy import *
from .utils.envs import Env


PREREQS_MD5_DIR = os.getenv('PREREQ_CACHE_DIR', Env.REPO_ROOT / '.prereqs_cache')
NPM_REGISTRY = "http://registry.npmjs.org/"
PYTHON_REQ_FILES = [
    'requirements/edx/pre.txt',
16 17
    'requirements/edx/github.txt',
    'requirements/edx/local.txt',
Will Daly committed
18
    'requirements/edx/base.txt',
19
    'requirements/edx/post.txt',
Will Daly committed
20 21
]

22 23 24 25 26 27
# Developers can have private requirements, for local copies of github repos,
# or favorite debugging tools, etc.
PRIVATE_REQS = 'requirements/private.txt'
if os.path.exists(PRIVATE_REQS):
    PYTHON_REQ_FILES.append(PRIVATE_REQS)

Will Daly committed
28 29 30 31 32 33 34 35 36 37 38

def compute_fingerprint(path_list):
    """
    Hash the contents of all the files and directories in `path_list`.
    Returns the hex digest.
    """

    hasher = hashlib.sha1()

    for path in path_list:

39 40
        # For directories, create a hash based on the modification times
        # of first-level subdirectories
Will Daly committed
41
        if os.path.isdir(path):
42 43 44 45
            for dirname in sorted(os.listdir(path)):
                p = os.path.join(path, dirname)
                if os.path.isdir(p):
                    hasher.update(str(os.stat(p).st_mtime))
Will Daly committed
46 47 48 49

        # For files, hash the contents of the file
        if os.path.isfile(path):
            with open(path, "rb") as file_handle:
50
                hasher.update(file_handle.read())
Will Daly committed
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87

    return hasher.hexdigest()


def prereq_cache(cache_name, paths, install_func):
    """
    Conditionally execute `install_func()` only if the files/directories
    specified by `paths` have changed.

    If the code executes successfully (no exceptions are thrown), the cache
    is updated with the new hash.
    """
    # Retrieve the old hash
    cache_filename = cache_name.replace(" ", "_")
    cache_file_path = os.path.join(PREREQS_MD5_DIR, "{}.sha1".format(cache_filename))
    old_hash = None
    if os.path.isfile(cache_file_path):
        with open(cache_file_path) as cache_file:
            old_hash = cache_file.read()

    # Compare the old hash to the new hash
    # If they do not match (either the cache hasn't been created, or the files have changed),
    # then execute the code within the block.
    new_hash = compute_fingerprint(paths)
    if new_hash != old_hash:
        install_func()

        # Update the cache with the new hash
        # If the code executed within the context fails (throws an exception),
        # then this step won't get executed.
        try:
            os.makedirs(PREREQS_MD5_DIR)
        except OSError:
            if not os.path.isdir(PREREQS_MD5_DIR):
                raise

        with open(cache_file_path, "w") as cache_file:
88 89 90 91
            # Since the pip requirement files are modified during the install
            # process, we need to store the hash generated AFTER the installation
            post_install_hash = compute_fingerprint(paths)
            cache_file.write(post_install_hash)
Will Daly committed
92
    else:
93
        print('{cache} unchanged, skipping...'.format(cache=cache_name))
Will Daly committed
94 95


96
def ruby_prereqs_installation():
Will Daly committed
97 98 99 100 101 102
    """
    Installs Ruby prereqs
    """
    sh('bundle install --quiet')


103
def node_prereqs_installation():
Will Daly committed
104 105 106 107 108 109 110
    """
    Installs Node prerequisites
    """
    sh("npm config set registry {}".format(NPM_REGISTRY))
    sh('npm install')


111
def python_prereqs_installation():
Will Daly committed
112 113 114 115 116 117 118 119
    """
    Installs Python prerequisites
    """
    for req_file in PYTHON_REQ_FILES:
        sh("pip install -q --exists-action w -r {req_file}".format(req_file=req_file))


@task
120 121 122 123
def install_ruby_prereqs():
    """
    Installs Ruby prereqs
    """
124 125 126
    if os.environ.get("NO_PREREQ_INSTALL", False):
        return

127 128 129 130 131 132 133 134
    prereq_cache("Ruby prereqs", ["Gemfile"], ruby_prereqs_installation)


@task
def install_node_prereqs():
    """
    Installs Node prerequisites
    """
135 136 137
    if os.environ.get("NO_PREREQ_INSTALL", False):
        return

138 139 140 141 142 143 144 145
    prereq_cache("Node prereqs", ["package.json"], node_prereqs_installation)


@task
def install_python_prereqs():
    """
    Installs Python prerequisites
    """
146 147 148
    if os.environ.get("NO_PREREQ_INSTALL", False):
        return

149 150 151 152
    prereq_cache("Python prereqs", PYTHON_REQ_FILES + [sysconfig.get_python_lib()], python_prereqs_installation)


@task
Will Daly committed
153 154 155 156
def install_prereqs():
    """
    Installs Ruby, Node and Python prerequisites
    """
157 158 159
    if os.environ.get("NO_PREREQ_INSTALL", False):
        return

160 161 162
    install_ruby_prereqs()
    install_node_prereqs()
    install_python_prereqs()