From dee61df274ff33ac6da962cdc0743d0199d71bb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ho=C5=99e=C5=88ovsk=C3=BD?= Date: Thu, 24 Aug 2017 21:59:06 +0200 Subject: [PATCH] Refactor release scripts, automatically update Wandbox on release --- scripts/generateSingleHeader.py | 210 ++++++++++++++++---------------- scripts/majorRelease.py | 9 +- scripts/minorRelease.py | 9 +- scripts/patchRelease.py | 9 +- scripts/releaseCommon.py | 93 ++++++++------ scripts/updateWandbox.py | 47 +++++++ 6 files changed, 217 insertions(+), 160 deletions(-) create mode 100644 scripts/updateWandbox.py diff --git a/scripts/generateSingleHeader.py b/scripts/generateSingleHeader.py index d4be7404..1b9b10e7 100755 --- a/scripts/generateSingleHeader.py +++ b/scripts/generateSingleHeader.py @@ -10,117 +10,119 @@ import string from glob import glob from scriptCommon import catchPath -from releaseCommon import Version +def generate(v): + includesParser = re.compile( r'\s*#\s*include\s*"(.*)"' ) + guardParser = re.compile( r'\s*#.*(TWOBLUECUBES_)?CATCH_.*_INCLUDED') + defineParser = re.compile( r'\s*#define\s+(TWOBLUECUBES_)?CATCH_.*_INCLUDED') + ifParser = re.compile( r'\s*#ifndef (TWOBLUECUBES_)?CATCH_.*_INCLUDED') + endIfParser = re.compile( r'\s*#endif // (TWOBLUECUBES_)?CATCH_.*_INCLUDED') + ifImplParser = re.compile( r'\s*#ifdef CATCH_CONFIG_RUNNER' ) + commentParser1 = re.compile( r'^\s*/\*') + commentParser2 = re.compile( r'^ \*') + blankParser = re.compile( r'^\s*$') -includesParser = re.compile( r'\s*#\s*include\s*"(.*)"' ) -guardParser = re.compile( r'\s*#.*(TWOBLUECUBES_)?CATCH_.*_INCLUDED') -defineParser = re.compile( r'\s*#define\s+(TWOBLUECUBES_)?CATCH_.*_INCLUDED') -ifParser = re.compile( r'\s*#ifndef (TWOBLUECUBES_)?CATCH_.*_INCLUDED') -endIfParser = re.compile( r'\s*#endif // (TWOBLUECUBES_)?CATCH_.*_INCLUDED') -ifImplParser = re.compile( r'\s*#ifdef CATCH_CONFIG_RUNNER' ) -commentParser1 = re.compile( r'^\s*/\*') -commentParser2 = re.compile( r'^ \*') -blankParser = re.compile( r'^\s*$') + seenHeaders = set([]) + rootPath = os.path.join( catchPath, 'include/' ) + outputPath = os.path.join( catchPath, 'single_include/catch.hpp' ) -seenHeaders = set([]) -rootPath = os.path.join( catchPath, 'include/' ) -outputPath = os.path.join( catchPath, 'single_include/catch.hpp' ) + globals = { + 'includeImpl' : True, + 'ifdefs' : 0, + 'implIfDefs' : -1 + } -includeImpl = True - -for arg in sys.argv[1:]: - arg = string.lower(arg) - if arg == "noimpl": - includeImpl = False - print( "Not including impl code" ) - else: - print( "\n** Unrecognised argument: " + arg + " **\n" ) - exit(1) - - -# ensure that the output directory exists (hopefully no races) -outDir = os.path.dirname(outputPath) -if not os.path.exists(outDir): - os.makedirs(outDir) -out = open( outputPath, 'w' ) -ifdefs = 0 -implIfDefs = -1 - -def write( line ): - if includeImpl or implIfDefs == -1: - out.write( line ) - -def insertCpps(): - dirs = [os.path.join( rootPath, s) for s in ['', 'internal', 'reporters']] - cppFiles = [] - for dir in dirs: - cppFiles += glob(os.path.join(dir, '*.cpp')) - for fname in cppFiles: - dir, name = fname.rsplit(os.path.sep, 1) - dir += os.path.sep - parseFile(dir, name) - -def parseFile( path, filename ): - global ifdefs - global implIfDefs - - f = open( os.path.join(path, filename), 'r' ) - blanks = 0 - write( "// start {0}\n".format( filename ) ) - for line in f: - if '// ~*~* CATCH_CPP_STITCH_PLACE *~*~' in line: - insertCpps() - continue - elif ifParser.match( line ): - ifdefs = ifdefs + 1 - elif endIfParser.match( line ): - ifdefs = ifdefs - 1 - if ifdefs == implIfDefs: - implIfDefs = -1 - m = includesParser.match( line ) - if m: - header = m.group(1) - headerPath, sep, headerFile = header.rpartition( "/" ) - if not headerFile in seenHeaders: - if headerFile != "tbc_text_format.h" and headerFile != "clara.h": - seenHeaders.add( headerFile ) - if headerPath == "internal" and path.endswith("internal/"): - headerPath = "" - sep = "" - if os.path.exists( path + headerPath + sep + headerFile ): - parseFile( path + headerPath + sep, headerFile ) - else: - parseFile( rootPath + headerPath + sep, headerFile ) + for arg in sys.argv[1:]: + arg = string.lower(arg) + if arg == "noimpl": + globals['includeImpl'] = False + print( "Not including impl code" ) else: - if ifImplParser.match(line): - implIfDefs = ifdefs - if (not guardParser.match( line ) or defineParser.match( line ) ) and not commentParser1.match( line )and not commentParser2.match( line ): - if blankParser.match( line ): - blanks = blanks + 1 - else: - blanks = 0 - if blanks < 2 and not defineParser.match(line): - write( line.rstrip() + "\n" ) - write( '// end {}\n'.format(filename) ) + print( "\n** Unrecognised argument: " + arg + " **\n" ) + exit(1) -v = Version() -out.write( "/*\n" ) -out.write( " * Catch v{0}\n".format( v.getVersionString() ) ) -out.write( " * Generated: {0}\n".format( datetime.datetime.now() ) ) -out.write( " * ----------------------------------------------------------\n" ) -out.write( " * This file has been merged from multiple headers. Please don't edit it directly\n" ) -out.write( " * Copyright (c) {} Two Blue Cubes Ltd. All rights reserved.\n".format( datetime.date.today().year ) ) -out.write( " *\n" ) -out.write( " * Distributed under the Boost Software License, Version 1.0. (See accompanying\n" ) -out.write( " * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n" ) -out.write( " */\n" ) -out.write( "#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED\n" ) -out.write( "#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED\n" ) + # ensure that the output directory exists (hopefully no races) + outDir = os.path.dirname(outputPath) + if not os.path.exists(outDir): + os.makedirs(outDir) + out = open( outputPath, 'w' ) -parseFile( rootPath, 'catch.hpp' ) + def write( line ): + if globals['includeImpl'] or globals['implIfDefs'] == -1: + out.write( line ) -out.write( "#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED\n\n" ) + def insertCpps(): + dirs = [os.path.join( rootPath, s) for s in ['', 'internal', 'reporters']] + cppFiles = [] + for dir in dirs: + cppFiles += glob(os.path.join(dir, '*.cpp')) + for fname in cppFiles: + dir, name = fname.rsplit(os.path.sep, 1) + dir += os.path.sep + parseFile(dir, name) -print ("Generated single include for Catch v{0}\n".format( v.getVersionString() ) ) + def parseFile( path, filename ): + f = open( os.path.join(path, filename), 'r' ) + blanks = 0 + write( "// start {0}\n".format( filename ) ) + for line in f: + if '// ~*~* CATCH_CPP_STITCH_PLACE *~*~' in line: + insertCpps() + continue + elif ifParser.match( line ): + globals['ifdefs'] += 1 + elif endIfParser.match( line ): + globals['ifdefs'] -= 1 + if globals['ifdefs'] == globals['implIfDefs']: + globals['implIfDefs'] = -1 + m = includesParser.match( line ) + if m: + header = m.group(1) + headerPath, sep, headerFile = header.rpartition( "/" ) + if not headerFile in seenHeaders: + if headerFile != "tbc_text_format.h" and headerFile != "clara.h": + seenHeaders.add( headerFile ) + if headerPath == "internal" and path.endswith("internal/"): + headerPath = "" + sep = "" + if os.path.exists( path + headerPath + sep + headerFile ): + parseFile( path + headerPath + sep, headerFile ) + else: + parseFile( rootPath + headerPath + sep, headerFile ) + else: + if ifImplParser.match(line): + globals['implIfDefs'] = globals['ifdefs'] + if (not guardParser.match( line ) or defineParser.match( line ) ) and not commentParser1.match( line )and not commentParser2.match( line ): + if blankParser.match( line ): + blanks = blanks + 1 + else: + blanks = 0 + if blanks < 2 and not defineParser.match(line): + write( line.rstrip() + "\n" ) + write( '// end {}\n'.format(filename) ) + + + out.write( "/*\n" ) + out.write( " * Catch v{0}\n".format( v.getVersionString() ) ) + out.write( " * Generated: {0}\n".format( datetime.datetime.now() ) ) + out.write( " * ----------------------------------------------------------\n" ) + out.write( " * This file has been merged from multiple headers. Please don't edit it directly\n" ) + out.write( " * Copyright (c) {} Two Blue Cubes Ltd. All rights reserved.\n".format( datetime.date.today().year ) ) + out.write( " *\n" ) + out.write( " * Distributed under the Boost Software License, Version 1.0. (See accompanying\n" ) + out.write( " * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n" ) + out.write( " */\n" ) + out.write( "#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED\n" ) + out.write( "#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED\n" ) + + parseFile( rootPath, 'catch.hpp' ) + + out.write( "#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED\n\n" ) + out.close() + print ("Generated single include for Catch v{0}\n".format( v.getVersionString() ) ) + + +if __name__ == '__main__': + from releaseCommon import Version + generate(Version()) diff --git a/scripts/majorRelease.py b/scripts/majorRelease.py index e8116d3b..8da34066 100755 --- a/scripts/majorRelease.py +++ b/scripts/majorRelease.py @@ -1,13 +1,10 @@ #!/usr/bin/env python from __future__ import print_function -from releaseCommon import Version +import releaseCommon -v = Version() +v = releaseCommon.Version() v.incrementMajorVersion() -v.updateVersionFile() -v.updateReadmeFile() -v.updateConanFile() -v.updateConanTestFile() +releaseCommon.performUpdates(v) print( "Updated Version.hpp, README and Conan to v{0}".format( v.getVersionString() ) ) diff --git a/scripts/minorRelease.py b/scripts/minorRelease.py index dff2d8c8..6e71cd80 100755 --- a/scripts/minorRelease.py +++ b/scripts/minorRelease.py @@ -1,13 +1,10 @@ #!/usr/bin/env python from __future__ import print_function -from releaseCommon import Version +import releaseCommon -v = Version() +v = releaseCommon.Version() v.incrementMinorVersion() -v.updateVersionFile() -v.updateReadmeFile() -v.updateConanFile() -v.updateConanTestFile() +releaseCommon.performUpdates(v) print( "Updated Version.hpp, README and Conan to v{0}".format( v.getVersionString() ) ) diff --git a/scripts/patchRelease.py b/scripts/patchRelease.py index e33e4064..14176420 100755 --- a/scripts/patchRelease.py +++ b/scripts/patchRelease.py @@ -1,13 +1,10 @@ #!/usr/bin/env python from __future__ import print_function -from releaseCommon import Version +import releaseCommon -v = Version() +v = releaseCommon.Version() v.incrementPatchNumber() -v.updateVersionFile() -v.updateReadmeFile() -v.updateConanFile() -v.updateConanTestFile() +releaseCommon.performUpdates(v) print( "Updated Version.hpp, README and Conan to v{0}".format( v.getVersionString() ) ) diff --git a/scripts/releaseCommon.py b/scripts/releaseCommon.py index 6737f13b..6ec175f6 100644 --- a/scripts/releaseCommon.py +++ b/scripts/releaseCommon.py @@ -6,6 +6,8 @@ import re import string from scriptCommon import catchPath +import generateSingleHeader +import updateWandbox versionParser = re.compile( r'(\s*static\sVersion\sversion)\s*\(\s*(.*)\s*,\s*(.*)\s*,\s*(.*)\s*,\s*\"(.*)\"\s*,\s*(.*)\s*\).*' ) rootPath = os.path.join( catchPath, 'include/' ) @@ -76,44 +78,59 @@ class Version: for line in lines: f.write( line + "\n" ) - def updateReadmeFile(self): - downloadParser = re.compile( r'' ) - f = open( readmePath, 'r' ) - lines = [] - for line in f: +def updateReadmeFile(version): + downloadParser = re.compile( r'' ) + success, wandboxLink = updateWandbox.uploadFiles() + if not success: + print('Error when uploading to wandbox: {}'.format(wandboxLink)) + exit(1) + f = open( readmePath, 'r' ) + lines = [] + for line in f: + lines.append( line.rstrip() ) + f.close() + f = open( readmePath, 'w' ) + for line in lines: + line = downloadParser.sub( r''.format(version.getVersionString()) , line) + if '[![Try online](https://img.shields.io/badge/try-online-blue.svg)]' in line: + line = '[![Try online](https://img.shields.io/badge/try-online-blue.svg)]({0})'.format(wandboxLink) + f.write( line + "\n" ) + +def updateConanFile(version): + conanParser = re.compile( r' version = "\d+\.\d+\.\d+.*"') + f = open( conanPath, 'r' ) + lines = [] + for line in f: + m = conanParser.match( line ) + if m: + lines.append( ' version = "{0}"'.format(format(version.getVersionString())) ) + else: lines.append( line.rstrip() ) - f.close() - f = open( readmePath, 'w' ) - for line in lines: - line = downloadParser.sub( r''.format(self.getVersionString()) , line) - f.write( line + "\n" ) + f.close() + f = open( conanPath, 'w' ) + for line in lines: + f.write( line + "\n" ) - def updateConanFile(self): - conanParser = re.compile( r' version = "\d+\.\d+\.\d+.*"') - f = open( conanPath, 'r' ) - lines = [] - for line in f: - m = conanParser.match( line ) - if m: - lines.append( ' version = "{0}"'.format(format(self.getVersionString())) ) - else: - lines.append( line.rstrip() ) - f.close() - f = open( conanPath, 'w' ) - for line in lines: - f.write( line + "\n" ) +def updateConanTestFile(version): + conanParser = re.compile( r' requires = \"Catch\/\d+\.\d+\.\d+.*@%s\/%s\" % \(username, channel\)') + f = open( conanTestPath, 'r' ) + lines = [] + for line in f: + m = conanParser.match( line ) + if m: + lines.append( ' requires = "Catch/{0}@%s/%s" % (username, channel)'.format(format(version.getVersionString())) ) + else: + lines.append( line.rstrip() ) + f.close() + f = open( conanTestPath, 'w' ) + for line in lines: + f.write( line + "\n" ) - def updateConanTestFile(self): - conanParser = re.compile( r' requires = \"Catch\/\d+\.\d+\.\d+.*@%s\/%s\" % \(username, channel\)') - f = open( conanTestPath, 'r' ) - lines = [] - for line in f: - m = conanParser.match( line ) - if m: - lines.append( ' requires = "Catch/{0}@%s/%s" % (username, channel)'.format(format(self.getVersionString())) ) - else: - lines.append( line.rstrip() ) - f.close() - f = open( conanTestPath, 'w' ) - for line in lines: - f.write( line + "\n" ) +def performUpdates(version): + # First update version file, so we can regenerate single header and + # have it ready for upload to wandbox, when updating readme + version.updateVersionFile() + generateSingleHeader.generate(version) + updateReadmeFile(version) + updateConanFile(version) + updateConanTestFile(version) diff --git a/scripts/updateWandbox.py b/scripts/updateWandbox.py new file mode 100644 index 00000000..25a74631 --- /dev/null +++ b/scripts/updateWandbox.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python + +import json +import os +import urllib2 + +from scriptCommon import catchPath + +def upload(options): + request = urllib2.Request('http://melpon.org/wandbox/api/compile.json') + request.add_header('Content-Type', 'application/json') + response = urllib2.urlopen(request, json.dumps(options)) + return json.loads(response.read()) + +main_file = ''' +#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file +#include "catch.hpp" + +unsigned int Factorial( unsigned int number ) { + return number <= 1 ? number : Factorial(number-1)*number; +} + +TEST_CASE( "Factorials are computed", "[factorial]" ) { + REQUIRE( Factorial(1) == 1 ); + REQUIRE( Factorial(2) == 2 ); + REQUIRE( Factorial(3) == 6 ); + REQUIRE( Factorial(10) == 3628800 ); +} +''' + +def uploadFiles(): + response = upload({ + 'compiler': 'gcc-head', + 'code': main_file, + 'codes': [{ + 'file': 'catch.hpp', + 'code': open(os.path.join(catchPath, 'single_include', 'catch.hpp')).read() + }], + 'options': 'c++11,cpp-no-pedantic,boost-nothing', + 'compiler-option-raw': '-DCATCH_CONFIG_FAST_COMPILE', + 'save': True + }) + + if 'status' in response and not 'compiler_error' in response: + return True, response['url'] + else: + return False, response