CORD-2741 implement VERSION file discovery for xos python libraries

Change-Id: Ife67b0066d5fd54958e1b1de3c5b3758ac9d3ca6
diff --git a/.gitignore b/.gitignore
index 2b7bf1a..338f566 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,4 +41,4 @@
 
 lib/xos-genx/cover/
 lib/xos-genx/.coverage
-lib/xos-genx/coverage.xml
\ No newline at end of file
+lib/xos-genx/coverage.xml
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..519af9a
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+6.0.0.dev
diff --git a/containers/xos/Dockerfile.client b/containers/xos/Dockerfile.client
index 338e3ff..53f2aaa 100644
--- a/containers/xos/Dockerfile.client
+++ b/containers/xos/Dockerfile.client
@@ -18,10 +18,13 @@
 FROM xosproject/xos-libraries:candidate
 
 # Install XOS client
-ADD xos/xos_client /tmp/xos_client
+COPY xos/xos_client /tmp/xos_client
+
+# Install the VERSION file
+COPY VERSION tmp/xos_client
 
 # Install chameleon
-ADD containers/xos/tmp.chameleon /tmp/xos_client/xosapi/chameleon
+COPY containers/xos/tmp.chameleon /tmp/xos_client/xosapi/chameleon
 
 #ENV HOME /root
 #WORKDIR /opt/xos
diff --git a/containers/xos/Dockerfile.libraries b/containers/xos/Dockerfile.libraries
index 67c64c4..6fd424a 100644
--- a/containers/xos/Dockerfile.libraries
+++ b/containers/xos/Dockerfile.libraries
@@ -19,9 +19,13 @@
 # Add libraries
 COPY lib /opt/xos/lib
 
+COPY VERSION /opt/xos
+
 # Install the config module
 # and the xosgenx library
-RUN cd /opt/xos/lib/xos-config/; \
+RUN cd /opt/xos/lib/xos-util; \
+        python setup.py install; \
+        cd /opt/xos/lib/xos-config/; \
 	python setup.py install; \
 	cd /opt/xos/lib/xos-genx/; \
 	python setup.py install
diff --git a/lib/xos-config/setup.py b/lib/xos-config/setup.py
index f14ef8a..098f544 100644
--- a/lib/xos-config/setup.py
+++ b/lib/xos-config/setup.py
@@ -1,3 +1,4 @@
+#!/usr/bin/env python
 
 # Copyright 2017-present Open Networking Foundation
 #
@@ -13,13 +14,19 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-
-#!/usr/bin/env python
-
 from setuptools import setup
 
+try:
+    from xosutil.autoversion_setup import setup_with_auto_version as setup
+except ImportError:
+    # xosutil is not installed. Expect this to happen when we build an egg, in which case xosgenx.version will
+    # automatically have the right version.
+    from setuptools import setup
+
+from xosconfig.version import __version__
+
 setup(name='XosConfig',
-      version='1.0',
+      version=__version__,
       description='XOS Config Library',
       author='Matteo Scandolo',
       author_email='teo@onlab.us',
diff --git a/lib/xos-config/xosconfig/version.py b/lib/xos-config/xosconfig/version.py
new file mode 100644
index 0000000..a118c43
--- /dev/null
+++ b/lib/xos-config/xosconfig/version.py
@@ -0,0 +1,19 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+
+# This file will be replaced by setup.py
+__version__ = "unknown"
diff --git a/lib/xos-genx/setup.py b/lib/xos-genx/setup.py
index 7aaa9bc..4bb1825 100644
--- a/lib/xos-genx/setup.py
+++ b/lib/xos-genx/setup.py
@@ -1,3 +1,4 @@
+#!/usr/bin/env python
 
 # Copyright 2017-present Open Networking Foundation
 #
@@ -13,13 +14,19 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-
-#!/usr/bin/env python
-
 from setuptools import setup
 
+try:
+    from xosutil.autoversion_setup import setup_with_auto_version as setup
+except ImportError:
+    # xosutil is not installed. Expect this to happen when we build an egg, in which case xosgenx.version will
+    # automatically have the right version.
+    from setuptools import setup
+
+from xosgenx.version import __version__
+
 setup(name='XosGenX',
-      version='1.0',
+      version=__version__,
       description='XOS Generative Toolchain',
       author='Sapan Bhatia, Matteo Scandolo',
       author_email='sapan@opennetworking.org, teo@opennetworking.org',
diff --git a/lib/xos-genx/xosgenx/version.py b/lib/xos-genx/xosgenx/version.py
new file mode 100644
index 0000000..a118c43
--- /dev/null
+++ b/lib/xos-genx/xosgenx/version.py
@@ -0,0 +1,19 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+
+# This file will be replaced by setup.py
+__version__ = "unknown"
diff --git a/lib/xos-util/Makefile b/lib/xos-util/Makefile
new file mode 100644
index 0000000..4143dba
--- /dev/null
+++ b/lib/xos-util/Makefile
@@ -0,0 +1,2 @@
+test:
+	nosetests -s -v --with-id --with-coverage --cover-html --cover-erase --cover-xml --cover-package="xosutil"
diff --git a/lib/xos-util/setup.py b/lib/xos-util/setup.py
new file mode 100644
index 0000000..b60435d
--- /dev/null
+++ b/lib/xos-util/setup.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python 
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import os
+
+from setuptools import setup
+
+from xosutil.autoversion_setup import setup_with_auto_version
+from xosutil.version import __version__
+
+setup_with_auto_version(name='XosUtil',
+      version=__version__,
+      description='XOS Utility Library',
+      author='Scott Baker',
+      author_email='scottb@opennetworking.org',
+      packages=['xosutil'],
+      include_package_data=True
+     )
diff --git a/lib/xos-util/tests/test_util.py b/lib/xos-util/tests/test_util.py
new file mode 100644
index 0000000..ff1ce58
--- /dev/null
+++ b/lib/xos-util/tests/test_util.py
@@ -0,0 +1,48 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import unittest
+
+from xosutil import autodiscover_version
+
+test_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
+
+class XOSUtilTest(unittest.TestCase):
+    """
+    Testing the XOS Util Module
+    """
+
+    def setUp(self):
+        pass
+
+    def tearDown(self):
+        pass
+
+    def test_autodiscover_version_of_caller(self):
+        version = open(os.path.join(test_path, "../../../VERSION")).readline().strip()
+        self.assertEqual(version, autodiscover_version.autodiscover_version_of_caller())
+
+    def test_autodiscover_version_of_caller_save_to(self):
+        version = open(os.path.join(test_path, "../../../VERSION")).readline().strip()
+        test_save_fn = os.path.join(test_path, "test_version.py")
+        if os.path.exists(test_save_fn):
+            os.remove(test_save_fn)
+        self.assertEqual(version, autodiscover_version.autodiscover_version_of_caller(save_to="test_version.py"))
+        self.assertTrue(os.path.exists(test_save_fn))
+        self.assertTrue(version in open(test_save_fn).read())
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/lib/xos-util/xosutil/__init__.py b/lib/xos-util/xosutil/__init__.py
new file mode 100644
index 0000000..42722a8
--- /dev/null
+++ b/lib/xos-util/xosutil/__init__.py
@@ -0,0 +1,14 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
diff --git a/lib/xos-util/xosutil/autodiscover_version.py b/lib/xos-util/xosutil/autodiscover_version.py
new file mode 100644
index 0000000..023aa79
--- /dev/null
+++ b/lib/xos-util/xosutil/autodiscover_version.py
@@ -0,0 +1,51 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+   xosutil/autodiscover_version.py
+
+   This module implements support for recursively searching for a VERSION file and extracting the version number from
+   it. Search starts from the directory of this file, or if autodiscover_version_of_caller is called, the directory
+   of the caller.
+"""
+
+import inspect
+import os
+
+def autodiscover_version(caller_filename=None, save_to=None):
+    """ walk back along the path to the current module, searching for a VERSION file """
+    if not caller_filename:
+        caller_filename = os.path.realpath(__file__)
+    file_path = os.path.abspath(os.path.dirname(caller_filename))
+    cur_path = file_path
+    while True:
+        version_fn = os.path.join(cur_path, "VERSION")
+        if os.path.exists(version_fn):
+            version = open(version_fn, "rt").readline().strip()
+            if save_to:
+                f = open(os.path.join(file_path, save_to), "wt")
+                f.write("# This file is autogenerated. Do not edit.\n")
+                f.write("__version__ = '%s'\n" % version)
+                f.close()
+            return version
+
+        (cur_path, remainder) = os.path.split(cur_path)
+        if not remainder:
+            return None
+
+def autodiscover_version_of_caller(*args, **kwargs):
+    frame = inspect.stack()[1]
+    module = inspect.getmodule(frame[0])
+    return autodiscover_version(module.__file__, *args, **kwargs)
diff --git a/lib/xos-util/xosutil/autoversion_setup.py b/lib/xos-util/xosutil/autoversion_setup.py
new file mode 100644
index 0000000..5a7ea44
--- /dev/null
+++ b/lib/xos-util/xosutil/autoversion_setup.py
@@ -0,0 +1,74 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+ xosutil/autoversion_setup.py
+
+ This module exports a function, setup_with_auto_version(), that will automatically generate a version.py file
+ dynamically from the version option passed to the setup function. It does this without having to modify the
+ source copy of version.py.
+
+ It also automatically searches for VERSION files in the directory of the caller and its parent hierarchy, and will
+ automatically load the version number from the VERSION file, if one is detected.
+"""
+
+import os
+from setuptools import setup
+
+from setuptools.command.sdist import sdist
+from setuptools.command.build_py import build_py
+
+import inspect
+from autodiscover_version import autodiscover_version
+
+class SdistCommand(sdist):
+    def copy_file(self, infile, outfile, *args, **kwargs):
+        if kwargs.get("dry_run"):
+            return (outfile, 1)
+        if (os.path.split(outfile)[1] == "version.py"):
+            open(outfile, "w").write("# do not edit. Autogenerated file.\n" \
+                                     "__version__ = '%s'\n" % self.distribution.metadata.version)
+            return (outfile, 1)
+        else:
+            return sdist.copy_file(self, infile, outfile, *args, **kwargs)
+
+class BuildPyCommand(build_py):
+    def copy_file(self, infile, outfile, *args, **kwargs):
+        if kwargs.get("dry_run"):
+            return (outfile, 1)
+        if (os.path.split(outfile)[1] == "version.py"):
+            open(outfile, "w").write("# do not edit. Autogenerated file.\n" \
+                                     "__version__ = '%s'\n" % self.distribution.metadata.version)
+            return (outfile, 1)
+        else:
+            return build_py.copy_file(self, infile, outfile, *args, **kwargs)
+
+def setup_with_auto_version(*args, **kwargs):
+    # Learn the module that called this function, so we can search for any VERSION files in it.
+    frame = inspect.stack()[1]
+    caller_module = inspect.getmodule(frame[0])
+
+    # Search for a VERSION file and extract the version number from it.
+    version = autodiscover_version(caller_filename = caller_module.__file__)
+    if version:
+        kwargs["version"] = version
+
+    cmdclass = kwargs.get("cmdclass", {}).copy()
+    cmdclass.update( {"sdist": SdistCommand,
+                "build_py": BuildPyCommand} )
+    kwargs["cmdclass"] = cmdclass
+
+    return setup(*args, **kwargs)
+
diff --git a/lib/xos-util/xosutil/version.py b/lib/xos-util/xosutil/version.py
new file mode 100644
index 0000000..57833b8
--- /dev/null
+++ b/lib/xos-util/xosutil/version.py
@@ -0,0 +1,16 @@
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This file is autogenerated. Do not edit.
+__version__ = 'unknown'
diff --git a/xos/xos_client/setup.py b/xos/xos_client/setup.py
index 077660f..1c52a1a 100644
--- a/xos/xos_client/setup.py
+++ b/xos/xos_client/setup.py
@@ -1,3 +1,4 @@
+#!/usr/bin/env python
 
 # Copyright 2017-present Open Networking Foundation
 #
@@ -13,13 +14,20 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-
-#! /usr/bin/env python
-
 import os
 import sys
 import site
-from distutils.core import setup
+from setuptools.command.install import install
+
+
+try:
+    from xosutil.autoversion_setup import setup_with_auto_version as setup
+except ImportError:
+    # xosutil is not installed. Expect this to happen when we build an egg, in which case xosgenx.version will
+    # automatically have the right version.
+    from setuptools import setup
+
+from xosapi.version import __version__
 
 CHAMELEON_DIR='xosapi/chameleon'
 
@@ -29,7 +37,18 @@
 if not os.path.exists(os.path.join(CHAMELEON_DIR, "protos/schema_pb2.py")):
     raise Exception("Please make the chameleon protos")
 
-setup(name='xosapi',
+# Chameleon requires these files have executable permission set.
+class InstallFixChameleonPermissions(install):
+    def run(self):
+        install.run(self)
+        for filepath in self.get_outputs():
+            if filepath.endswith("chameleon/protoc_plugins/gw_gen.py") or \
+               filepath.endswith("chameleon/protoc_plugins/swagger_gen.py"):
+               os.chmod(filepath, 0777)
+
+setup_result = setup(name='xosapi',
+      version=__version__,
+      cmdclass={"install": InstallFixChameleonPermissions},
       description='XOS api client',
       package_dir= {'xosapi.chameleon': CHAMELEON_DIR},
       packages=['xosapi.chameleon.grpc_client',
@@ -49,24 +68,5 @@
       scripts = ['xossh'],
      )
 
-# If we're not running a Virtual Env
-if not hasattr(sys, 'real_prefix'):
-    # Chameleon needs the following files set as executable
-    for dir in site.getsitepackages():
-       fn = os.path.join(dir, "xosapi/chameleon/protoc_plugins/gw_gen.py")
-       if os.path.exists(fn):
-           os.chmod(fn, 0777)
-       fn = os.path.join(dir, "xosapi/chameleon/protoc_plugins/swagger_gen.py")
-       if os.path.exists(fn):
-           os.chmod(fn, 0777)
-
-
-"""
-from twisted.internet import reactor
-from xosapi.xos_grpc_client import InsecureClient
-client = InsecureClient(endpoint="xos-core.cord.lab:50055")
-client.start()
-reactor.run()
-"""
 
 
diff --git a/xos/xos_client/xosapi/version.py b/xos/xos_client/xosapi/version.py
new file mode 100644
index 0000000..a118c43
--- /dev/null
+++ b/xos/xos_client/xosapi/version.py
@@ -0,0 +1,19 @@
+
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+
+# This file will be replaced by setup.py
+__version__ = "unknown"