2015-08-07 15:51:56 +08:00
|
|
|
import asyncio
|
2015-02-22 11:34:31 +08:00
|
|
|
import os
|
2015-08-07 15:51:56 +08:00
|
|
|
import tempfile
|
|
|
|
import shutil
|
2015-07-15 16:54:44 +08:00
|
|
|
import logging
|
2015-02-22 11:34:31 +08:00
|
|
|
|
|
|
|
from artiq.protocols.sync_struct import Notifier
|
2015-07-15 16:54:44 +08:00
|
|
|
from artiq.master.worker import Worker
|
2015-02-22 11:34:31 +08:00
|
|
|
|
|
|
|
|
2015-07-15 16:54:44 +08:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
@asyncio.coroutine
|
2015-08-07 15:51:56 +08:00
|
|
|
def _scan_experiments(wd, log):
|
2015-02-22 11:34:31 +08:00
|
|
|
r = dict()
|
2015-08-07 15:51:56 +08:00
|
|
|
for f in os.listdir(wd):
|
2015-02-22 11:34:31 +08:00
|
|
|
if f.endswith(".py"):
|
|
|
|
try:
|
2015-07-23 23:06:15 +08:00
|
|
|
worker = Worker({"log": lambda message: log("scan", message)})
|
2015-07-15 16:54:44 +08:00
|
|
|
try:
|
2015-08-07 15:51:56 +08:00
|
|
|
description = yield from worker.examine(os.path.join(wd, f))
|
2015-07-15 16:54:44 +08:00
|
|
|
finally:
|
|
|
|
yield from worker.close()
|
|
|
|
for class_name, class_desc in description.items():
|
|
|
|
name = class_desc["name"]
|
|
|
|
arguments = class_desc["arguments"]
|
|
|
|
if name in r:
|
|
|
|
logger.warning("Duplicate experiment name: '%s'", name)
|
|
|
|
basename = name
|
|
|
|
i = 1
|
|
|
|
while name in r:
|
|
|
|
name = basename + str(i)
|
|
|
|
i += 1
|
2015-02-22 11:34:31 +08:00
|
|
|
entry = {
|
2015-08-07 15:51:56 +08:00
|
|
|
"file": f,
|
2015-07-15 16:54:44 +08:00
|
|
|
"class_name": class_name,
|
|
|
|
"arguments": arguments
|
2015-02-22 11:34:31 +08:00
|
|
|
}
|
2015-03-08 22:43:04 +08:00
|
|
|
r[name] = entry
|
2015-07-15 16:54:44 +08:00
|
|
|
except:
|
|
|
|
logger.warning("Skipping file '%s'", f, exc_info=True)
|
2015-02-22 11:34:31 +08:00
|
|
|
return r
|
|
|
|
|
|
|
|
|
2015-07-15 16:54:44 +08:00
|
|
|
def _sync_explist(target, source):
|
|
|
|
for k in list(target.read.keys()):
|
|
|
|
if k not in source:
|
|
|
|
del target[k]
|
|
|
|
for k in source.keys():
|
|
|
|
if k not in target.read or target.read[k] != source[k]:
|
|
|
|
target[k] = source[k]
|
|
|
|
|
|
|
|
|
2015-01-26 23:37:33 +08:00
|
|
|
class Repository:
|
2015-08-07 15:51:56 +08:00
|
|
|
def __init__(self, backend, log_fn):
|
|
|
|
self.backend = backend
|
|
|
|
self.log_fn = log_fn
|
|
|
|
|
|
|
|
self.head_rev = self.backend.get_head_rev()
|
|
|
|
self.backend.request_rev(self.head_rev)
|
2015-07-15 16:54:44 +08:00
|
|
|
self.explist = Notifier(dict())
|
2015-08-07 15:51:56 +08:00
|
|
|
|
2015-07-18 00:55:48 +08:00
|
|
|
self._scanning = False
|
2015-02-22 11:34:31 +08:00
|
|
|
|
2015-07-15 16:54:44 +08:00
|
|
|
@asyncio.coroutine
|
|
|
|
def scan(self):
|
2015-07-18 00:55:48 +08:00
|
|
|
if self._scanning:
|
|
|
|
return
|
|
|
|
self._scanning = True
|
2015-08-07 15:51:56 +08:00
|
|
|
|
|
|
|
new_head_rev = self.backend.get_head_rev()
|
|
|
|
wd = self.backend.request_rev(new_head_rev)
|
|
|
|
self.backend.release_rev(self.head_rev)
|
|
|
|
self.head_rev = new_head_rev
|
|
|
|
new_explist = yield from _scan_experiments(wd, self.log_fn)
|
|
|
|
|
2015-07-15 16:54:44 +08:00
|
|
|
_sync_explist(self.explist, new_explist)
|
2015-07-18 00:55:48 +08:00
|
|
|
self._scanning = False
|
|
|
|
|
|
|
|
def scan_async(self):
|
|
|
|
asyncio.async(self.scan())
|
2015-08-07 15:51:56 +08:00
|
|
|
|
|
|
|
|
|
|
|
class FilesystemBackend:
|
|
|
|
def __init__(self, root):
|
|
|
|
self.root = os.path.abspath(root)
|
|
|
|
|
|
|
|
def get_head_rev(self):
|
|
|
|
return "N/A"
|
|
|
|
|
|
|
|
def request_rev(self, rev):
|
|
|
|
return self.root
|
|
|
|
|
|
|
|
def release_rev(self, rev):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class _GitCheckout:
|
|
|
|
def __init__(self, git, rev):
|
|
|
|
self.path = tempfile.mkdtemp()
|
|
|
|
git.checkout_tree(git.get(rev), directory=self.path)
|
|
|
|
self.ref_count = 1
|
|
|
|
logger.info("checked out revision %s into %s", rev, self.path)
|
|
|
|
|
|
|
|
def dispose(self):
|
|
|
|
logger.info("disposing of checkout in folder %s", self.path)
|
|
|
|
shutil.rmtree(self.path)
|
|
|
|
|
|
|
|
|
|
|
|
class GitBackend:
|
|
|
|
def __init__(self, root):
|
|
|
|
# lazy import - make dependency optional
|
|
|
|
import pygit2
|
|
|
|
|
|
|
|
self.git = pygit2.Repository(root)
|
|
|
|
self.checkouts = dict()
|
|
|
|
|
|
|
|
def get_head_rev(self):
|
|
|
|
return str(self.git.head.target)
|
|
|
|
|
|
|
|
def request_rev(self, rev):
|
|
|
|
if rev in self.checkouts:
|
|
|
|
co = self.checkouts[rev]
|
|
|
|
co.ref_count += 1
|
|
|
|
else:
|
|
|
|
co = _GitCheckout(self.git, rev)
|
|
|
|
self.checkouts[rev] = co
|
|
|
|
return co.path
|
|
|
|
|
|
|
|
def release_rev(self, rev):
|
|
|
|
co = self.checkouts[rev]
|
|
|
|
co.ref_count -= 1
|
|
|
|
if not co.ref_count:
|
|
|
|
co.dispose()
|
|
|
|
del self.checkouts[rev]
|