From 6f827104098d9fe38a27d4b028e609e417f8baec Mon Sep 17 00:00:00 2001 From: Benjy Date: Tue, 7 May 2013 14:27:01 -0700 Subject: [PATCH] A RunInfo file. This is a small textfile-backed data structure containing info about a pants run, such as the timestamp, commit sha etc. This supercedes the mostly unused BuildInfo functionality, which was removed, to avoid confusion. Auditors: markcc (sapling split of d74c0960a79c1a60aa9293cf6f627fc5b654f899) --- src/python/twitter/pants/base/build_info.py | 45 ------------ src/python/twitter/pants/base/run_info.py | 71 +++++++++++++++++++ .../twitter/pants/base/test_run_info.py | 33 +++++++++ 3 files changed, 104 insertions(+), 45 deletions(-) delete mode 100644 src/python/twitter/pants/base/build_info.py create mode 100644 src/python/twitter/pants/base/run_info.py create mode 100644 tests/python/twitter/pants/base/test_run_info.py diff --git a/src/python/twitter/pants/base/build_info.py b/src/python/twitter/pants/base/build_info.py deleted file mode 100644 index a58db608f4c..00000000000 --- a/src/python/twitter/pants/base/build_info.py +++ /dev/null @@ -1,45 +0,0 @@ -# ================================================================================================== -# Copyright 2012 Twitter, Inc. -# -------------------------------------------------------------------------------------------------- -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this work except in compliance with the License. -# You may obtain a copy of the License in the LICENSE file, or 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 getpass -import socket - -from collections import namedtuple -from time import strftime, localtime - -from twitter.pants import get_buildroot, get_scm - -BuildInfo = namedtuple('BuildInfo', 'date time timestamp branch tag sha user machine path') - -def get_build_info(scm=None): - """Calculates the current BuildInfo using the supplied scm or else the globally configured one.""" - buildroot = get_buildroot() - scm = scm or get_scm() - - revision = scm.commit_id - tag = scm.tag_name or 'none' - branchname = scm.branch_name or revision - now = localtime() - return BuildInfo( - date=strftime('%A %b %d, %Y', now), - time=strftime('%H:%M:%S', now), - timestamp=strftime('%m.%d.%Y %H:%M', now), - branch=branchname, - tag=tag, - sha=revision, - user=getpass.getuser(), - machine=socket.gethostname(), - path=buildroot) diff --git a/src/python/twitter/pants/base/run_info.py b/src/python/twitter/pants/base/run_info.py new file mode 100644 index 00000000000..9e1832dadfa --- /dev/null +++ b/src/python/twitter/pants/base/run_info.py @@ -0,0 +1,71 @@ +import getpass +import os +import re +import socket +import time + +from twitter.common.dirutil import safe_mkdir_for +from twitter.pants import get_scm, get_buildroot + + +class RunInfo(object): + """A little plaintext file containing very basic info about a pants run. + + Can only be appended to, never edited.""" + def __init__(self, info_file): + self._info_file = info_file + safe_mkdir_for(self._info_file) + self._info = {} + if os.path.exists(self._info_file): + with open(self._info_file, 'r') as infile: + info = infile.read() + for m in re.finditer("""^([^:]+):(.*)$""", info, re.MULTILINE): + self._info[m.group(1).strip()] = m.group(2).strip() + + def path(self): + return self._info_file + + def get_info(self, key): + return self._info.get(key, None) + + def __getitem__(self, key): + ret = self.get_info(key) + if ret is None: + raise KeyError(key) + return ret + + def get_as_dict(self): + return self._info.copy() + + def add_info(self, key, val): + self.add_infos((key, val)) + + def add_infos(self, *keyvals): + with open(self._info_file, 'a') as outfile: + for key, val in keyvals: + key = key.strip() + val = val.strip() + if ':' in key: + raise Exception, 'info key must not contain a colon' + outfile.write('%s: %s\n' % (key, val)) + self._info[key] = val + + def add_basic_info(self, run_id, timestamp): + """A helper function to add basic build info.""" + datetime = time.strftime('%A %b %d, %Y %H:%M:%S', time.localtime(timestamp)) + user = getpass.getuser() + machine = socket.gethostname() + path = get_buildroot() + self.add_infos(('id', run_id), ('timestamp', timestamp), ('datetime', datetime), + ('user', user), ('machine', machine), ('path', path)) + + def add_scm_info(self): + """A helper function to add SCM-related info.""" + scm = get_scm() + if scm: + revision = scm.commit_id + tag = scm.tag_name or 'none' + branch = scm.branch_name or revision + else: + revision, tag, branch = 'none', 'none', 'none' + self.add_infos(('revision', revision), ('tag', tag), ('branch', branch)) diff --git a/tests/python/twitter/pants/base/test_run_info.py b/tests/python/twitter/pants/base/test_run_info.py new file mode 100644 index 00000000000..11926b77384 --- /dev/null +++ b/tests/python/twitter/pants/base/test_run_info.py @@ -0,0 +1,33 @@ +import unittest2 as unittest + +from twitter.common.contextutil import temporary_file_path +from twitter.pants.base.run_info import RunInfo + + +class RunInfoTest(unittest.TestCase): + def test_run_info_read(self): + with temporary_file_path() as tmppath: + with open(tmppath, 'w') as tmpfile: + tmpfile.write('foo:bar\n baz :qux quux') + ri = RunInfo(tmppath) + self.assertEquals(ri.path(), tmppath) + + # Test get_info access. + self.assertEquals(ri.get_info('foo'), 'bar') + self.assertEquals(ri.get_info('baz'), 'qux quux') + self.assertIsNone(ri.get_info('nonexistent')) + + # Test dict-like access. + self.assertEquals(ri['foo'], 'bar') + self.assertEquals(ri['baz'], 'qux quux') + + def test_write_run_info(self): + with temporary_file_path() as tmppath: + ri = RunInfo(tmppath) + ri.add_info('key1', 'val1') + ri.add_infos(('key2', ' val2'), (' key3 ', 'val3 ')) + self.assertEquals({'key1': 'val1', 'key2': 'val2', 'key3': 'val3'}, ri.get_as_dict()) + + with open(tmppath, 'r') as tmpfile: + contents = tmpfile.read() + self.assertEquals('key1: val1\nkey2: val2\nkey3: val3\n', contents)