As you can infer from my latest posts, I am now working with Flex. And Java Servlets, using Resin as the server. That environment is not even close to be as developer-friendly as Django, or Rails or many others modern web frameworks. Perhaps I am being a bit unfair, because Resin tries to be developer-friendly, not only reloading the context when something changes, but also providing a special directory where you can put your java sources and it even compiles them. But the project layout is not configured the way Resin likes it, so it did not work.
Other developers use a IDE plugin to deploy the compiled classes automatically. But, is really an IDE needed to simply (re)build a project after some file(s) changes? I do not think so. After all, it is just matter of monitoring the file system and firing the build process if a change is detected. So I came with a simple, handy utility, which monitor a specified directory and run an arbitrary command when a change is detected.
Integrating it with our build script is trivial:
> cd \eclipse3.3\eclipse\workspace\MyProject
> python dirwatch.py -c "ant deploy-development"
Moreover, Eclipse integration is easy too: Run-> External Tools -> Open External Tools Dialog -> Program -> New:
Location: c:\python25\python.exe
Working Directory: ${workspace_loc:/MyProject}
Arguments: -u "c:\path\to\dirwatch.py" -c "ant deploy-development"
The whole point of the exercise? Decouple the convenience of automatic deploymeny from a particular IDE, where it does not belong. Just like building.
So here is the program (currently only works on Windows, but should be easy to port to Unix-like systems, using inotify or FAM):
#!/usr/bin/env python
"""
dirwatch:
Monitor a directory for changes. Executes a command after a change is detected.
"""
import os
import time
import win32file
import win32event
import win32con
from optparse import OptionParser
VERSION = "1.0"
def dirwatch(path_to_watch, seconds_to_wait, commands):
print time.asctime(), "Watching %s" % path_to_watch
import sys
change_handle = win32file.FindFirstChangeNotification (
path_to_watch,
1, # => recursive
win32con.FILE_NOTIFY_CHANGE_FILE_NAME |
win32con.FILE_NOTIFY_CHANGE_DIR_NAME |
win32con.FILE_NOTIFY_CHANGE_SIZE |
win32con.FILE_NOTIFY_CHANGE_LAST_WRITE
)
# Loop forever, listing any file changes.
try:
last_change_time = None
while True:
result = win32event.WaitForSingleObject(change_handle, 1000)
if result == win32con.WAIT_OBJECT_0:
if last_change_time:
if time.time() - last_change_time > seconds_to_wait:
msg = "More change(s) detected, " \
"waiting %d more second(s)"
else:
msg = None
else:
msg = "Change detected, waiting %d second(s)"
if msg:
print time.asctime(), msg % seconds_to_wait
last_change_time = time.time()
win32file.FindNextChangeNotification(change_handle)
if last_change_time:
if time.time() - last_change_time > seconds_to_wait:
for command in commands:
print time.asctime(), "Executing '%s'" % command
os.system(command)
last_change_time = None
finally:
win32file.FindCloseChangeNotification(change_handle)
def parse_options(args):
parser = OptionParser(usage="%prog [-c command[;command...]] [directory]",
version="%prog " + VERSION)
parser.add_option("-w", "--wait", dest="seconds_to_wait", metavar="SECONDS",
default="1",
help="seconds to wait after last change before the "
"of the command(s).")
parser.add_option("-c", "--command", dest="command",
default="echo change detected",
help="command to execute after a change is detected")
return parser.parse_args(args)
def main(args):
options, args = parse_options(args)
try: path_to_watch = args[1] or "."
except: path_to_watch = "."
dirwatch(path_to_watch, int(options.seconds_to_wait),
options.command.split(';'))
if __name__ == "__main__":
import sys
main(sys.argv)

