File: lib.py

package info (click to toggle)
toil 9.1.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 13,908 kB
  • sloc: python: 58,029; makefile: 313; sh: 168
file content (177 lines) | stat: -rw-r--r-- 5,775 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
"""
Utility functions for hook scripts.
"""

import sys
import os
import subprocess
import socket
import functools

from subprocess import CalledProcessError, TimeoutExpired
from typing import Tuple, Optional

def complain(message):
    sys.stderr.flush()
    sys.stderr.write(message)
    sys.stderr.write('\n')
    sys.stderr.flush()

def announce(message):
    sys.stderr.flush()
    sys.stderr.write(message)
    sys.stderr.write('\n')
    sys.stderr.flush()

@functools.lru_cache(maxsize=None)
def get_hostname() -> str:
    """
    Get the current hostname, or fall back to localhost.
    """
    try:
        return socket.getfqdn()
    except:
        return 'localhost'

def file_link(path: str, text: Optional[str] = None) -> str:
    """
    Produce a string we can print to a terminal to create a hyperlink.

    Uses OSC 8 (operating system command number 8) as documented at
    <https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda>
    """

    # Determine link protocol to use
    if 'SSH_TTY' in os.environ:
        # If the user is SSH-ing in, link to the file over SFTP so their
        # desktop's virtual filesystem can go get it.
        protocol = 'sftp'
    else:
        protocol = 'file'

    if not text:
        # If we didn't get any link text, use the provided path.
        text = path

    # We always want to link to a full path name
    realpath = os.path.realpath(path)

    # This is the Operating System Command escape sequence
    OSC = '\033]'
    # This is the standard String Terminator for ending arguments to operating
    # system commands.
    ST = '\033\\'
    # This is the command to set a link format on characters you print.
    # It takes as an argument some optional params, a semicolon, and a URL.
    # The argument is terminated with the String Terminator.
    # Set the URL to an empty string to turn off linkification.
    LINK_COMMAND=f'{OSC}8;'

    # This is the full URL we want to link to.
    # We include a hostname even for file:// URLs because that's what Gnome
    # terminal seems to expect. This may or may not work well elsewhere.
    url = f'{protocol}://{get_hostname()}{realpath}'

    # This is what we print to turn on linking to the URL
    turn_on_link = f'{LINK_COMMAND};{url}{ST}'
    # And this is what we print to stop linking.
    turn_off_link = f'{LINK_COMMAND};{ST}'

    return f'{turn_on_link}{text}{turn_off_link}'

def in_acceptable_environment() -> bool:
    """
    Determine if we are in an environment where we ought to be able to run mypy
    type checking.
    """
    try:
        # We need to be able to get at Toil, and we need to be in a virtual
        # environment.
        from toil import inVirtualEnv
        return inVirtualEnv()
    except:
        # If we can't do that, either we're not in a Toil dev environment or
        # Toil is Very Broken and that will be caught other ways.
        return False

def get_current_commit() -> str:
    """
    Get the currently checked-out commit.
    """
    return subprocess.check_output(['git', 'rev-parse', 'HEAD']).decode('utf-8').strip()

def is_rebase():
    """
    Return true if we think we are currently rebasing.
    """
    git_dir = os.getenv('GIT_DIR', '.git')
    return os.path.exists(os.path.join(git_dir, 'rebase-merge')) or os.path.exists(os.path.join(git_dir, 'rebase-apply'))

# We have a cache for mypy results so we can compute them in advance.
CACHE_DIR = '.mypy_toil_result_cache'
# But we don't want it to last too long.
USER_DIR = f'/var/run/user/{os.getuid()}'
if os.path.isdir(USER_DIR):
    CACHE_DIR = os.path.join(USER_DIR, CACHE_DIR)

def write_cache(commit: str, result: bool, log: str) -> str:
    """
    Save the given status and log to the cache for the given commit.
    Returns cache filename for log text.
    """

    os.makedirs(CACHE_DIR, exist_ok=True)
    basename = os.path.join(CACHE_DIR, commit)
    if os.path.exists(basename + '.fail.txt'):
        os.unlink(basename + '.fail.txt')
    if os.path.exists(basename + '.success.txt'):
        os.unlink(basename + '.success.txt')
    fullname = basename + ('.success.txt' if result else '.fail.txt')
    with open(fullname, 'w') as f:
        f.write(log)
    return fullname

def read_cache(commit: str) -> Tuple[Optional[bool], Optional[str]]:
    """
    Read the status and log from the cache for the given commit.
    """

    status = None
    log = None

    basename = os.path.join(CACHE_DIR, commit)
    fullname = None
    if os.path.exists(basename + '.fail.txt'):
        # We have a cached failure.
        fullname = basename + '.fail.txt'
        status = False
    elif os.path.exists(basename + '.success.txt'):
        # We have a cached success
        fullname = basename + '.success.txt'
        status = True
    if fullname:
        log = open(fullname).read()
    return status, log

def check_to_cache(local_object, timeout: float = None) -> Tuple[Optional[bool], Optional[str], Optional[str]]:
    """
    Type-check current commit and save result to cache. Return status, log, and log filename, or None, None, None if a timeout is hit.
    """

    try:
        # As a hook we know we're in the project root when running.
        mypy_output = subprocess.check_output(['make', 'mypy'], stderr=subprocess.STDOUT, timeout=timeout)
        log = mypy_output.decode('utf-8')
        # If we get here it passed
        filename = write_cache(local_object, True, log)
        return True, log, filename
    except CalledProcessError as e:
        # It did not work.
        log = e.output.decode('utf-8')
        # Save this in a cache
        filename = write_cache(local_object, False, log)
        return False, log, filename
    except TimeoutExpired:
        return None, None, None