From 9fcbe1084b17027e003c62043d764ed5551ddadc Mon Sep 17 00:00:00 2001 From: Bob Adolf Date: Tue, 12 Nov 2013 16:22:33 -0500 Subject: [PATCH] Major shift to direct script execution instead of a helper binary. Importing protos now hijacks control from the script and invokes an internal workflow manager. Removed the old helper program 'protos_agent.py' Added the internal workflow manager. It builds the workflow DAG and work queue, then executes all of the work items one by one. Changed configuration file options around so that config files are now no longer specified in protocol files. They should be taken from either the command line or an environment variable. Added a topological sort stub to core/workflow. This should return a serialization of all of the tasks in a workflow DAG. Added explicit documentation for configuration file options. --- __init__.py | 20 ++++++++++++++++++++ agent/protos_agent.py | 17 ----------------- core/agent.py | 28 ++++++++++++++++++++++++++++ core/config.py | 38 +++++++++++++++++++++++++++----------- core/protos_api.py | 2 +- core/workflow.py | 4 ++++ docs/config.md | 41 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 121 insertions(+), 29 deletions(-) delete mode 100755 agent/protos_agent.py create mode 100644 core/agent.py create mode 100644 docs/config.md diff --git a/__init__.py b/__init__.py index 0eb0a17..04c8cec 100644 --- a/__init__.py +++ b/__init__.py @@ -6,6 +6,8 @@ # Instead, we define it one place, separately, then jump through some hoops # here to import and define all those functions. +import core.agent + # Pull the Protos API from core.protos_api import FUNCTIONS @@ -20,3 +22,21 @@ # Now clean up our namespace #del core + +################################################################################ +# Hijack control +# +# Unlike normal modules, protos doesn't provide functions. It assumes control of +# the program and executes a dependent chain of protocols. The original script +# is merely a convenient way of pointing protos to the last dependency in the +# series. + +# This is a way to get the name of the original file caused us to be imported. +# Protos should not be executed from an interactive session (you will not get +# control returned to you). +from __main__ import __file__ as root_protocol_file + +core.agent.Agent(root_protocol_file) + +# Control does not return, nothing should be below this line. +################################################################################ diff --git a/agent/protos_agent.py b/agent/protos_agent.py deleted file mode 100755 index 1ad6722..0000000 --- a/agent/protos_agent.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env python - -import os -import sys -import protos - - - -if __name__=='__main__': - if len(sys.argv)<2: - print 'Usage: '+os.path.basename(sys.argv[0])+' ' - sys.exit(-1) - final_protocol_file = sys.argv[1] - - # Build workflow - workflow = protos.core.workflow.Workflow(final_protocol_file) - diff --git a/core/agent.py b/core/agent.py new file mode 100644 index 0000000..349cbf0 --- /dev/null +++ b/core/agent.py @@ -0,0 +1,28 @@ +# Agent +# +# The work manager, invoked when the first protos library import is called. +# Instead of loading functions, the protos package __init__ function hijacks +# control of the program and invokes this agent instead. The agent then +# builds a complete workflow DAG from static analysis of the original file +# and all of its specified dependencies (through protos.require()). Using +# a topological sort of the DAG, the agent then executes all of the specied +# scripts in order. + +import sys +import workflow + +class Agent(): + def __init__(self, root_protocol_file): + self._flow = workflow.Workflow(root_protocol_file) + self._workq = self._flow.toposort() + self.run() # Does not return + + def run(self): + assert self._flow is not None + assert self._workq is not None + + for work_item in self._workq: + print 'EXEC: ',work_item + + # All execution is complete. Do not return control to the user. Exit. + sys.exit(0) diff --git a/core/config.py b/core/config.py index ebe94c3..711650f 100644 --- a/core/config.py +++ b/core/config.py @@ -11,17 +11,33 @@ # Dict of dicts CONFIG_DEFAULTS = { 'project' : { - 'repo' : '', - # FIXME: protocol-path deprecated in favor of protocol-dir. - # need to migrate expand_protocol_path once we write - # the software controlling git clones. - 'protocol-path' : '', # absolute location of protocol files - 'protocol-dir' : '', # relative to project root (git clone root) - }, - 'log' : { - 'repo' : '', - }, - } + # Valid types are 'files','git','svn' + 'type' : 'files', + + # If project-type is 'git' or 'svn', project will be created if it does not + # exist at that location already. + 'root' : '', + + # The path prefix(es) to search, in order, for protocol files. + # Relative paths are relative to the + 'protocol-path' : '', + + # Options for project-type=='git': + 'git-repo' : '', + # Options + }, 'log' : { + # Valid types are 'files','git','svn' + 'type' : 'files', + + # If project-type is 'git' or 'svn', project will be created if it does not + # exist at that location already. + 'log-root' : '', + + # Options for log-type=='git': + 'git-repo' : '', + #'git-branch-by-project-revision' : False, FIXME? + }, +} def expand_config_path(s): ''' diff --git a/core/protos_api.py b/core/protos_api.py index d43f1ff..c7de30a 100644 --- a/core/protos_api.py +++ b/core/protos_api.py @@ -1,3 +1,3 @@ # Protos user interface -FUNCTIONS = [ 'config', 'require', 'log', 'var' ] +FUNCTIONS = [ 'config', 'require', 'cache', 'log', 'var' ] diff --git a/core/workflow.py b/core/workflow.py index 55e5099..097bb3d 100644 --- a/core/workflow.py +++ b/core/workflow.py @@ -104,5 +104,9 @@ def build_dependencies(self,given_protocol_file): return dict([(k,v[1]) for (k,v) in dependencies.items()]), filename_cache + def toposort(self): + warnings.warn('FIXME: replace with actual implementation') # FIXME + return self._deps.keys() # NOT topo-sorted + def __repr__(self): return str(self._deps) diff --git a/docs/config.md b/docs/config.md new file mode 100644 index 0000000..a3fdfad --- /dev/null +++ b/docs/config.md @@ -0,0 +1,41 @@ +# Configuration File Options + +---- + +### Section: `project` +Projects are a collection of related files to be scripted and controlled by a set of protos scripts. + +`project-type` +> **values:** `files` `git` `svn`
+> **default:** `'files'`
+> Describes how the project is stored. If the type is `'git'` or `'svn'`, the project will be cloned or checked out if it doesn't already exist. + +`project-root'` +> **values:** absolute path
+> **default:** `''`
+> The project root directory. Must be an absolute path. + +`protocol-path` +> **values:** `:`-separated absolute or relative path prefix strings
+> **default:** `''`
+> Searched left-to-right. Relative paths are relative to `project-root`. Empty paths between `:`'s or at the beginning or end signify the project directory. + +`git-repo` +> **values:** git repository location
+> **default:** `''`
+> If `project-type` is `git`, the location for the project's git repository. If `project-type` is anything else, the option is ignored. Should be something that git understands, which includes usernames embedded in ssh URLs and absolute pathnames. Relative pathnames are not a good idea. + +### Section: `log` + +Logs are a complete record of experimental results, archived somewhere permanent. + +`log-type` +> **values:** `files` `git` `svn`
+> **default:** `files` +> Describes how the log is stored. If the type is `git` or `svn`, the project will be cloned or checked out if it doesn't already exist. + +`git-repo` +> **values:** git repository location
+> **default:** `''`
+> If `log-type` is `git`, the location for the log's git repository. If `log-type` is anything else, the option is ignored. Should be something that git understands, which includes usernames embedded in ssh URLs and absolute pathnames. Relative pathnames are not a good idea. +