Skip to content

Instantly share code, notes, and snippets.

@thorsummoner
Last active August 30, 2015 23:26
Show Gist options
  • Save thorsummoner/f1fc7acf94cf9696ef75 to your computer and use it in GitHub Desktop.
Save thorsummoner/f1fc7acf94cf9696ef75 to your computer and use it in GitHub Desktop.
SublimeText build service respawner
#!/usr/bin/env python2
#
# LICENSE
# This is free and unencumbered software released into the public domain.
#
# Anyone is free to copy, modify, publish, use, compile, sell, or
# distribute this software, either in source code form or as a compiled
# binary, for any purpose, commercial or non-commercial, and by any
# means.
#
# In jurisdictions that recognize copyright laws, the author or authors
# of this software dedicate any and all copyright interest in the
# software to the public domain. We make this dedication for the benefit
# of the public at large and to the detriment of our heirs and
# successors. We intend this dedication to be an overt act of
# relinquishment in perpetuity of all present and future rights to this
# software under copyright law.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
#
# For more information, please refer to <http://unlicense.org>
"""
SublimeText build service respawner
Spawns a service, and writes a pid file for that service.
Before spawning service, checks if past service exists and kills it.
"""
import os
import shlex
import signal
import subprocess
from time import sleep
BUILD_SERVICE = 'python -m SimpleHTTPServer'
BUILD_SERVICE_NAME = 'python'
PID_FILE = '~/.sublime-build.pid'
TEST_BUILD_SERVICE_NAME = True
class ServiceGoneError(SystemError):
"""
Indicates a service exited without cleaning up its pid file.
"""
def __init__(self):
super(ServiceGoneError, self).__init__()
class MissingPidFile(OSError):
"""
Indicates the specified pid file does not exist.
"""
def __init__(self):
super(MissingPidFile, self).__init__()
def check_process(pid):
"""
Test if pid exists
Returns:
bool True: If process does exist
bool False: If process does not exist
"""
# Test id pid exists, by sending it signal 0, which should be ignored
try:
os.kill(pid, signal.SIG_DFL) # DFL: abbreviation of `default`
except OSError:
return False
else:
return True
def check_service(pid_file=None, build_service_name=None):
"""
Checks if a process with a pid file exists.
Args:
pid_file str: Path to pid file.
build_service_name str: Name of process, do not kill if not match.
Returns:
int (positive, non-zero): pid, if pid is alive
bool False: if pid is not alive
Raises:
ServiceGoneError: If pid name mismatches expected name
"""
if pid_file is None:
pid_file = PID_FILE
if build_service_name is None:
build_service_name = BUILD_SERVICE_NAME
if not os.path.exists(pid_file):
# No pid to search
raise MissingPidFile()
with open(os.path.expanduser(pid_file), 'r') as file_handle:
pid = int(file_handle.read())
# Optional: check that the process name matches a known
# before killing something unknown.
if TEST_BUILD_SERVICE_NAME:
# This would be unnecessary if the service manages its own pid
# file (and is not killed abruptly).
with open('/proc/{pid}/comm'.format(pid=pid)) as file_handle:
ps_name = file_handle.read()
if ps_name != build_service_name:
# Exit if what we would kill is not what we expect
raise ServiceGoneError('Unexpected process name `{}`, for pid `{}`'.format(
ps_name,
pid,
))
return (pid if check_process(pid) else False)
def close_service(pid):
"""
Close a process by `pid` if it matches `build_service_name`
Args:
pid int (positive, non-zero): Process id to kill
Returns: None
Raises:
SystemError: on missing or mismatched pid
"""
# Assumed process is alive
# Try to kill the program softly, then forcefully
# Ask the program to quit
os.kill(pid, signal.SIGTERM)
sleep(0.001)
if check_process(pid):
# Process did not exit (yet?)
os.kill(pid, signal.SIGTERM)
sleep(0.01) # Wait ten times longer
if check_process(pid):
# Process did not exit, ask the kernel to kill it.
os.kill(pid, signal.SIGKILL)
sleep(0.001)
if check_process(pid):
raise SystemError("Unable to kill process `{}`!".format(pid))
def spawn_service():
"""
Start BUILD_SERVICE and record its pid to PID_FILE
Assumes BUILD_SERVICE does not detach from its original pid
"""
service = subprocess.Popen(shlex.split(BUILD_SERVICE))
with open(os.path.expanduser(PID_FILE), 'w') as file_handle:
file_handle.write('{}\n'.format(service.pid))
def post_spawn_tasks():
"""
called after service spawned.
"""
# subprocess.Popen(shlex.split('my build cmd'))
pass
def main():
"""
Main build script, execute this file on build with your
BUILD_SERVICE and or modifications.
"""
try:
pid = check_service()
if pid:
# Previous service instance still alive
close_service(pid)
except (ServiceGoneError, MissingPidFile) as err:
# Its okay if the service went away without us asking it to.
print(err)
spawn_service()
post_spawn_tasks()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment