From 360199b84360df976ca9c9630d93a1018bb1e40d Mon Sep 17 00:00:00 2001
From: "Michael D. M. Dryden" <mdryden@chem.utoronto.ca>
Date: Fri, 5 Jun 2015 10:44:26 -0400
Subject: [PATCH] Repackage for Microdrop v1.0

---
 SConstruct                                |  46 ------
 __init__.py                               | 172 ++++++++++++++++++++++
 hooks/Linux/on_plugin_install.sh          |   3 +
 hooks/Windows/on_plugin_install.bat       |   1 +
 hooks/Windows/on_plugin_install.exe       | Bin 0 -> 48640 bytes
 microdrop/__init__.py                     | 172 ----------------------
 on_plugin_install.py                      |  12 ++
 release.py                                |  22 +++
 requirements.txt                          |   1 +
 site_scons/__init__.py                    |   0
 site_scons/git_util.py                    |  93 ------------
 site_scons/site_tools/disttar/__init__.py |   1 -
 site_scons/site_tools/disttar/disttar.py  | 153 -------------------
 13 files changed, 211 insertions(+), 465 deletions(-)
 delete mode 100644 SConstruct
 create mode 100644 hooks/Linux/on_plugin_install.sh
 create mode 100644 hooks/Windows/on_plugin_install.bat
 create mode 100644 hooks/Windows/on_plugin_install.exe
 delete mode 100644 microdrop/__init__.py
 create mode 100644 on_plugin_install.py
 create mode 100755 release.py
 create mode 100644 requirements.txt
 delete mode 100644 site_scons/__init__.py
 delete mode 100644 site_scons/git_util.py
 delete mode 100644 site_scons/site_tools/disttar/__init__.py
 delete mode 100644 site_scons/site_tools/disttar/disttar.py

diff --git a/SConstruct b/SConstruct
deleted file mode 100644
index aa3fd85..0000000
--- a/SConstruct
+++ /dev/null
@@ -1,46 +0,0 @@
-import re
-import os
-import sys
-
-import yaml
-
-from git_util import GitUtil
-from path_helpers import path
-
-
-def get_plugin_version():
-    version = GitUtil(None).describe()
-    m = re.search('^v(?P<major>\d+)\.(?P<minor>\d+)(-(?P<micro>\d+))?', version)
-    if m.group('micro'):
-        micro = m.group('micro')
-    else:
-        micro = '0'
-    version_string = "%s.%s.%s" % (m.group('major'),
-            m.group('minor'), micro)
-    return version_string
-
-
-SOFTWARE_VERSION = get_plugin_version()
-env = Environment(tools = ["default", "disttar"],
-        DISTTAR_EXCLUDEDIRS=['.git'],
-        DISTTAR_EXCLUDERES=[r'\.sconsign\.dblite'],
-        DISTTAR_EXCLUDEEXTS=['.gz', '.pyc', '.tgz', '.swp'])
-
-plugin_root = path('.').abspath()
-properties_target = plugin_root.joinpath('properties.yml')
-properties = {'plugin_name': 'wheeler.%s' % plugin_root.name,
-              'package_name': str(plugin_root.name),
-              'version': SOFTWARE_VERSION}
-properties_target.write_bytes(yaml.dump(properties))
-archive_name = '%s-%s.tar.gz' % (properties['package_name'], SOFTWARE_VERSION)
-
-# This will build an archive using what ever DISTTAR_FORMAT that is set.
-tar = env.DistTar('%s' % properties['package_name'], [env.Dir('#')])
-renamed_tar = env.Command(env.File(archive_name), None,
-        Move(archive_name, tar[0]))
-Depends(renamed_tar, tar)
-Clean(renamed_tar, tar)
-
-if 'PLUGIN_ARCHIVE_DIR' in os.environ:
-    target_archive_dir = os.environ['PLUGIN_ARCHIVE_DIR']
-    Install(target_archive_dir, renamed_tar)
diff --git a/__init__.py b/__init__.py
index e69de29..9c97ea6 100644
--- a/__init__.py
+++ b/__init__.py
@@ -0,0 +1,172 @@
+"""
+Copyright 2011 Ryan Fobel
+
+This file is part of dmf_control_board.
+
+dmf_control_board is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+dmf_control_board is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with dmf_control_board.  If not, see <http://www.gnu.org/licenses/>.
+"""
+from collections import OrderedDict
+from datetime import datetime
+
+import gtk
+import zmq
+from flatland import String, Boolean, Float, Form
+from logger import logger
+from gui.protocol_grid_controller import ProtocolGridController
+from plugin_helpers import (AppDataController, StepOptionsController,
+                            get_plugin_info)
+from plugin_manager import (IPlugin, IWaveformGenerator, Plugin, implements,
+                            PluginGlobals, ScheduleRequest, emit_signal,
+                            get_service_instance)
+from app_context import get_app
+from path_helpers import path
+
+
+PluginGlobals.push_env('microdrop.managed')
+
+class ZeroMQServicePlugin(Plugin, AppDataController, StepOptionsController):
+    """
+    This class is automatically registered with the PluginManager.
+    """
+    implements(IPlugin)
+    version = get_plugin_info(path(__file__).parent.parent).version
+    plugins_name = get_plugin_info(path(__file__).parent.parent).plugin_name
+
+    '''
+    AppFields
+    ---------
+
+    A flatland Form specifying application options for the current plugin.
+    Note that nested Form objects are not supported.
+
+    Since we subclassed AppDataController, an API is available to access and
+    modify these attributes.  This API also provides some nice features
+    automatically:
+        -all fields listed here will be included in the app options dialog
+            (unless properties=dict(show_in_gui=False) is used)
+        -the values of these fields will be stored persistently in the microdrop
+            config file, in a section named after this plugin's name attribute
+    '''
+    AppFields = Form.of(
+        String.named('service_address').using(default='', optional=True),
+    )
+
+    '''
+    StepFields
+    ---------
+
+    A flatland Form specifying the per step options for the current plugin.
+    Note that nested Form objects are not supported.
+
+    Since we subclassed StepOptionsController, an API is available to access and
+    modify these attributes.  This API also provides some nice features
+    automatically:
+        -all fields listed here will be included in the protocol grid view
+            (unless properties=dict(show_in_gui=False) is used)
+        -the values of these fields will be stored persistently for each step
+    '''
+    StepFields = Form.of(
+        Boolean.named('service_enabled').using(default=False, optional=True),
+    )
+
+    def __init__(self):
+        self.name = self.plugins_name
+        self.context = zmq.Context.instance()
+        self.socks = OrderedDict()
+        self.timeout_id = None
+        self._start_time = None
+
+    def on_plugin_enable(self):
+        # We need to call AppDataController's on_plugin_enable() to update the
+        # application options data.
+        AppDataController.on_plugin_enable(self)
+        self.context = zmq.Context()
+        self.reset_socks()
+        if get_app().protocol:
+            pgc = get_service_instance(ProtocolGridController, env='microdrop')
+            pgc.update_grid()
+
+    def close_socks(self):
+        # Close any currently open sockets.
+        for name, sock in self.socks.iteritems():
+            sock.close()
+        self.socks = OrderedDict()
+
+    def reset_socks(self):
+        self.close_socks()
+        app_values = self.get_app_values()
+        if self.timeout_id is not None:
+            gtk.timeout_remove(self.timeout_id)
+            self.timeout_id = None
+        if app_values['service_address']:
+            # Service address is available
+            self.socks['req'] = zmq.Socket(self.context, zmq.REQ)
+            self.socks['req'].connect(app_values['service_address'])
+
+    def on_app_options_changed(self, plugin_name):
+        if plugin_name == self.name:
+            self.reset_socks()
+
+    def on_plugin_disable(self):
+        self.close_socks()
+        if get_app().protocol:
+            pgc = get_service_instance(ProtocolGridController, env='microdrop')
+            pgc.update_grid()
+
+    def step_complete(self, return_value=None):
+        app = get_app()
+        if app.running or app.realtime_mode:
+            emit_signal('on_step_complete', [self.name, return_value])
+
+    def on_step_run(self):
+        options = self.get_step_options()
+        self.reset_socks()
+        if options['service_enabled'] and self.socks['req'] is None:
+            # Service is supposed to be called for this step, but the socket is
+            # not ready.
+            self.step_complete(return_value='Fail')
+        elif options['service_enabled'] and self.socks['req'] is not None:
+            logger.info('[ZeroMQServicePlugin] Send signal to service to '
+                        'start.')
+            # Request start of service.
+            self.socks['req'].send('start')
+            if not self.socks['req'].poll(timeout=4000):
+                self.reset_socks()
+                logger.error('[ZeroMQServicePlugin] Timed-out waiting for '
+                                'a response.')
+            else:
+                # Response is ready.
+                response = self.socks['req'].recv()
+                if response == 'started':
+                    logger.info('[ZeroMQServicePlugin] Service started '
+                                'successfully.')
+                    self.socks['req'].send('notify_completion')
+
+                    response = self.socks['req'].recv()
+                    logger.info('[ZeroMQServicePlugin] Service response: %s', response)
+                    if response == 'completed':
+                        logger.info('[ZeroMQServicePlugin] Service completed task '
+                                    'successfully.')
+                        self.step_complete()
+                    else:
+                        logger.error('[ZeroMQServicePlugin] Unexpected response: %s' %
+                                     response)
+                        self.step_complete(return_value='Fail')
+        else:
+            self.step_complete()
+
+    def enable_service(self):
+        pass
+
+PluginGlobals.pop_env()
diff --git a/hooks/Linux/on_plugin_install.sh b/hooks/Linux/on_plugin_install.sh
new file mode 100644
index 0000000..19bf666
--- /dev/null
+++ b/hooks/Linux/on_plugin_install.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+PYTHON_EXE=$1
+$PYTHON_EXE ../../on_plugin_install.py
diff --git a/hooks/Windows/on_plugin_install.bat b/hooks/Windows/on_plugin_install.bat
new file mode 100644
index 0000000..617a828
--- /dev/null
+++ b/hooks/Windows/on_plugin_install.bat
@@ -0,0 +1 @@
+%1 ..\..\on_plugin_install.py
diff --git a/hooks/Windows/on_plugin_install.exe b/hooks/Windows/on_plugin_install.exe
new file mode 100644
index 0000000000000000000000000000000000000000..2e6aa4f40e0ba6fabfae628dd3976df1f76b4eb7
GIT binary patch
literal 48640
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

literal 0
HcmV?d00001

diff --git a/microdrop/__init__.py b/microdrop/__init__.py
deleted file mode 100644
index 9c97ea6..0000000
--- a/microdrop/__init__.py
+++ /dev/null
@@ -1,172 +0,0 @@
-"""
-Copyright 2011 Ryan Fobel
-
-This file is part of dmf_control_board.
-
-dmf_control_board is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-dmf_control_board is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with dmf_control_board.  If not, see <http://www.gnu.org/licenses/>.
-"""
-from collections import OrderedDict
-from datetime import datetime
-
-import gtk
-import zmq
-from flatland import String, Boolean, Float, Form
-from logger import logger
-from gui.protocol_grid_controller import ProtocolGridController
-from plugin_helpers import (AppDataController, StepOptionsController,
-                            get_plugin_info)
-from plugin_manager import (IPlugin, IWaveformGenerator, Plugin, implements,
-                            PluginGlobals, ScheduleRequest, emit_signal,
-                            get_service_instance)
-from app_context import get_app
-from path_helpers import path
-
-
-PluginGlobals.push_env('microdrop.managed')
-
-class ZeroMQServicePlugin(Plugin, AppDataController, StepOptionsController):
-    """
-    This class is automatically registered with the PluginManager.
-    """
-    implements(IPlugin)
-    version = get_plugin_info(path(__file__).parent.parent).version
-    plugins_name = get_plugin_info(path(__file__).parent.parent).plugin_name
-
-    '''
-    AppFields
-    ---------
-
-    A flatland Form specifying application options for the current plugin.
-    Note that nested Form objects are not supported.
-
-    Since we subclassed AppDataController, an API is available to access and
-    modify these attributes.  This API also provides some nice features
-    automatically:
-        -all fields listed here will be included in the app options dialog
-            (unless properties=dict(show_in_gui=False) is used)
-        -the values of these fields will be stored persistently in the microdrop
-            config file, in a section named after this plugin's name attribute
-    '''
-    AppFields = Form.of(
-        String.named('service_address').using(default='', optional=True),
-    )
-
-    '''
-    StepFields
-    ---------
-
-    A flatland Form specifying the per step options for the current plugin.
-    Note that nested Form objects are not supported.
-
-    Since we subclassed StepOptionsController, an API is available to access and
-    modify these attributes.  This API also provides some nice features
-    automatically:
-        -all fields listed here will be included in the protocol grid view
-            (unless properties=dict(show_in_gui=False) is used)
-        -the values of these fields will be stored persistently for each step
-    '''
-    StepFields = Form.of(
-        Boolean.named('service_enabled').using(default=False, optional=True),
-    )
-
-    def __init__(self):
-        self.name = self.plugins_name
-        self.context = zmq.Context.instance()
-        self.socks = OrderedDict()
-        self.timeout_id = None
-        self._start_time = None
-
-    def on_plugin_enable(self):
-        # We need to call AppDataController's on_plugin_enable() to update the
-        # application options data.
-        AppDataController.on_plugin_enable(self)
-        self.context = zmq.Context()
-        self.reset_socks()
-        if get_app().protocol:
-            pgc = get_service_instance(ProtocolGridController, env='microdrop')
-            pgc.update_grid()
-
-    def close_socks(self):
-        # Close any currently open sockets.
-        for name, sock in self.socks.iteritems():
-            sock.close()
-        self.socks = OrderedDict()
-
-    def reset_socks(self):
-        self.close_socks()
-        app_values = self.get_app_values()
-        if self.timeout_id is not None:
-            gtk.timeout_remove(self.timeout_id)
-            self.timeout_id = None
-        if app_values['service_address']:
-            # Service address is available
-            self.socks['req'] = zmq.Socket(self.context, zmq.REQ)
-            self.socks['req'].connect(app_values['service_address'])
-
-    def on_app_options_changed(self, plugin_name):
-        if plugin_name == self.name:
-            self.reset_socks()
-
-    def on_plugin_disable(self):
-        self.close_socks()
-        if get_app().protocol:
-            pgc = get_service_instance(ProtocolGridController, env='microdrop')
-            pgc.update_grid()
-
-    def step_complete(self, return_value=None):
-        app = get_app()
-        if app.running or app.realtime_mode:
-            emit_signal('on_step_complete', [self.name, return_value])
-
-    def on_step_run(self):
-        options = self.get_step_options()
-        self.reset_socks()
-        if options['service_enabled'] and self.socks['req'] is None:
-            # Service is supposed to be called for this step, but the socket is
-            # not ready.
-            self.step_complete(return_value='Fail')
-        elif options['service_enabled'] and self.socks['req'] is not None:
-            logger.info('[ZeroMQServicePlugin] Send signal to service to '
-                        'start.')
-            # Request start of service.
-            self.socks['req'].send('start')
-            if not self.socks['req'].poll(timeout=4000):
-                self.reset_socks()
-                logger.error('[ZeroMQServicePlugin] Timed-out waiting for '
-                                'a response.')
-            else:
-                # Response is ready.
-                response = self.socks['req'].recv()
-                if response == 'started':
-                    logger.info('[ZeroMQServicePlugin] Service started '
-                                'successfully.')
-                    self.socks['req'].send('notify_completion')
-
-                    response = self.socks['req'].recv()
-                    logger.info('[ZeroMQServicePlugin] Service response: %s', response)
-                    if response == 'completed':
-                        logger.info('[ZeroMQServicePlugin] Service completed task '
-                                    'successfully.')
-                        self.step_complete()
-                    else:
-                        logger.error('[ZeroMQServicePlugin] Unexpected response: %s' %
-                                     response)
-                        self.step_complete(return_value='Fail')
-        else:
-            self.step_complete()
-
-    def enable_service(self):
-        pass
-
-PluginGlobals.pop_env()
diff --git a/on_plugin_install.py b/on_plugin_install.py
new file mode 100644
index 0000000..80fb487
--- /dev/null
+++ b/on_plugin_install.py
@@ -0,0 +1,12 @@
+from datetime import datetime
+import logging
+
+from path_helpers import path
+from pip_helpers import install
+
+
+if __name__ == '__main__':
+    logging.basicConfig(level=logging.INFO)
+    logging.info(str(datetime.now()))
+    requirements = path(__file__).parent.joinpath('requirements.txt').abspath()
+    logging.info(install(['-U', '-r', requirements]))
diff --git a/release.py b/release.py
new file mode 100755
index 0000000..47788c4
--- /dev/null
+++ b/release.py
@@ -0,0 +1,22 @@
+import tarfile
+import yaml
+
+from microdrop_utility import Version
+
+package_name = 'dstat_zeromq_plugin'
+plugin_name = 'wheeler.dstat_zeromq_plugin'
+
+# create a version sting based on the git revision/branch
+version = str(Version.from_git_repository())
+
+# write the 'properties.yml' file
+properties = {'plugin_name': plugin_name, 'package_name': package_name,
+              'version': version}
+with open('properties.yml', 'w') as f:
+    f.write(yaml.dump(properties))
+
+# create the tar.gz plugin archive
+with tarfile.open("%s-%s.tar.gz" % (package_name, version), "w:gz") as tar:
+    for name in ['__init__.py', 'test_service.py', 'properties.yml',
+                 'requirements.txt']:
+        tar.add(name)
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..02ec117
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1 @@
+pyzmq
diff --git a/site_scons/__init__.py b/site_scons/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/site_scons/git_util.py b/site_scons/git_util.py
deleted file mode 100644
index 6fecf25..0000000
--- a/site_scons/git_util.py
+++ /dev/null
@@ -1,93 +0,0 @@
-import os
-from subprocess import Popen, PIPE, check_call, CalledProcessError
-import re
-
-from path_helpers import path
-
-
-class GitError(Exception):
-    pass
-
-
-class GitUtil(object):
-    def __init__(self, root_path='.'):
-        self.root_path = path(root_path)
-        if root_path is None:
-            dir_node = path(os.getcwd())
-            while not dir_node.dirs('.git') and dir_node:
-                dir_node = dir_node.parent
-            if not dir_node:
-                raise GitError('No git root found.')
-            self.root_path = dir_node
-        self._git = None
-        assert(self.root_path.dirs('.git'))
-
-
-    @property
-    def git(self):
-        if self._git:
-            return self._git
-
-        cmds = ['git']
-        if os.name == 'nt':
-            exceptions = (WindowsError,)
-            cmds += ['git.cmd']
-        else:
-            exceptions = (OSError,)
-
-        valid_cmd = False
-        for cmd in cmds:
-            try:
-                check_call([cmd], stdout=PIPE, stderr=PIPE)
-            except exceptions:
-                # The command was not found, try the next one
-                pass
-            except CalledProcessError:
-                valid_cmd = True
-                break
-        if not valid_cmd:
-            raise GitError, 'No valid git command found'
-        self._git = cmd
-        return self._git
-
-
-    def command(self, x):
-        try:
-            x.__iter__
-        except:
-            x = re.split(r'\s+', x)
-        cwd = os.getcwd()
-
-        os.chdir(self.root_path)
-        cmd = [self.git] + x
-        stdout, stderr = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE).communicate()
-        os.chdir(cwd)
-
-        if stderr:
-            raise GitError('Error executing git %s' % x)
-        return stdout.strip()
-
-
-    def describe(self):
-        return self.command('describe')
-
-
-    def summary(self, color=False):
-        if color:
-            format_ = '''--pretty=format:%Cred%h%Creset - %s %Cgreen(%cr)%Creset'''
-        else:
-            format_ = '''--pretty=format:%h - %s (%cr)'''
-        return self.command(['''log''', '''--graph''', format_,
-                    '''--abbrev-commit''', '''--date=relative'''])
-
-
-    def rev_parse(self, ref='HEAD'):
-        return self.command(['rev-parse', ref])
-
-
-    def show(self, ref='HEAD', color=False, extra_args=None):
-        extra_args = [extra_args, []][extra_args is None]
-        args = ['show', ref]
-        if color:
-            args += ['--color']
-        return self.command(args + extra_args)
diff --git a/site_scons/site_tools/disttar/__init__.py b/site_scons/site_tools/disttar/__init__.py
deleted file mode 100644
index 4b0e0f0..0000000
--- a/site_scons/site_tools/disttar/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from disttar import *
diff --git a/site_scons/site_tools/disttar/disttar.py b/site_scons/site_tools/disttar/disttar.py
deleted file mode 100644
index 7f7a13e..0000000
--- a/site_scons/site_tools/disttar/disttar.py
+++ /dev/null
@@ -1,153 +0,0 @@
-# DistTarBuilder: tool to generate tar files using SCons
-# Copyright (C) 2005, 2006  Matthew A. Nicholson
-# Copyright (C) 2006-2010 John Pye
-#
-# This file is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License version 2.1 as published by the Free Software Foundation.
-#
-# This file is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-#
-
-import os,sys
-from SCons.Script import *
-import re
-
-
-def file_handler(fpath, source, excluderes, excludeexts):
-    from path import path
-
-    fpath = path(fpath)
-    if not fpath.ext in excludeexts:
-        failre = False
-        for r in excluderes:
-            #print "Match(  %s   against   %s)" % (r,relpath)
-            if r.search(fpath):
-                failre = True
-                #print "Excluding '%s' from tarball" % fpath
-                break
-        if not failre:
-            #print "Adding source",fpath
-            source.append(str(fpath))
-
-
-def disttar_emitter(target, source, env):
-    source,origsource = [], source
-
-    excludeexts = env.Dictionary().get('DISTTAR_EXCLUDEEXTS',[])
-    excludedirs = env.Dictionary().get('DISTTAR_EXCLUDEDIRS',[])
-    re1 = env.Dictionary().get('DISTTAR_EXCLUDERES',[])
-    excluderes = [re.compile(r) for r in re1]
-
-    # assume the sources are directories... need to check that
-    for item in origsource:
-        if os.path.isfile(str(item)):
-            file_handler(item, source, excluderes, excludeexts)
-        else:
-            for root, dirs, files in os.walk(str(item)):
-                # don't make directory dependences as that triggers full build
-                # of that directory
-                if root in source:
-                    #print "Removing directory %s" % root
-                    source.remove(root)
-
-                # loop through files in a directory
-                for name in files:
-                    relpath = os.path.join(root,name)
-                    file_handler(relpath, source, excluderes, excludeexts)
-
-                for d in excludedirs:
-                    if d in dirs:
-                        dirs.remove(d)  # don't visit CVS directories etc
-
-    return target, source
-
-def disttar_string(target, source, env):
-    """This is what gets printed on the console. We'll strip out the list
-        or source files, since it tends to get very long. If you want to see the 
-        contents, the easiest way is to uncomment the line 'Adding to TAR file'
-        below. """
-    return 'DistTar(%s,...)' % target[0]
-
-def disttar(target, source, env):
-        """tar archive builder"""
-
-        import tarfile
-
-        env_dict = env.Dictionary()
-
-        if env_dict.get("DISTTAR_FORMAT") in ["gz", "bz2"]:
-                tar_format = env_dict["DISTTAR_FORMAT"]
-        else:
-                tar_format = ""
-
-        # split the target directory, filename, and stuffix
-        base_name = str(target[0]).split('.tar')[0]
-        (target_dir, dir_name) = os.path.split(base_name)
-
-        # create the target directory if it does not exist
-        if target_dir and not os.path.exists(target_dir):
-                os.makedirs(target_dir)
-
-        # open our tar file for writing
-        print >> sys.stderr, 'DistTar: Writing %s' % str(target[0])
-        print >> sys.stderr, '  with contents: %s' % [str(s) for s in source]
-        tar = tarfile.open(str(target[0]), "w:%s" % tar_format)
-
-        # write sources to our tar file
-        for item in source:
-                item = str(item)
-                sys.stderr.write(".")
-                #print "Adding to TAR file: %s/%s" % (dir_name,item)
-                tar.add(item,'%s/%s' % (dir_name,item))
-
-        # all done
-        sys.stderr.write("\n") #print "Closing TAR file"
-        tar.close()
-
-def disttar_suffix(env, sources):
-        """tar archive suffix generator"""
-
-        env_dict = env.Dictionary()
-        if env_dict.has_key("DISTTAR_FORMAT") and env_dict["DISTTAR_FORMAT"] in ["gz", "bz2"]:
-                return ".tar." + env_dict["DISTTAR_FORMAT"]
-        else:
-                return ".tar"
-
-def generate(env):
-    """
-    Add builders and construction variables for the DistTar builder.
-    """
-
-    disttar_action=SCons.Action.Action(disttar, disttar_string)
-    env['BUILDERS']['DistTar'] =  Builder(
-            action=disttar_action
-            , emitter=disttar_emitter
-            , suffix = disttar_suffix
-            , target_factory = env.fs.Entry
-    )
-
-    env.AppendUnique(
-        DISTTAR_FORMAT = 'gz'
-    )
-
-def exists(env):
-        """
-        Make sure this tool exists.
-        """
-        try:
-                import os
-                import tarfile
-        except ImportError:
-                return False
-        else:
-                return True
-
-# vim:set ts=4 sw=4 noexpandtab:
-- 
GitLab