Created
July 19, 2011 19:06
-
-
Save rcravens/1093434 to your computer and use it in GitHub Desktop.
Python Script to Automate MSBuild Tasks
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Generic build script that builds, tests, and creates nuget packages. | |
# | |
# INSTRUCTIONS: | |
# Update the following project paths: | |
# proj Path to the project file (.csproj) | |
# test Path to the test project (.csproj) | |
# nuspec Path to the package definition for NuGet. | |
# | |
# delete any of the lines if not applicable | |
# | |
# | |
# Update the paths to the build tools: | |
# msbuild Path to msbuild | |
# test Path to mstest.exe (requires Visual Studio) (optional - delete line) | |
# nuget Path to nuget.exe (requires NuGet command line tool) (optional - delete line) | |
# trx2html Path to trx2html.exe (http://trx2html.codeplex.com/) (optional - delete line) | |
# | |
# | |
# USAGE: | |
# | |
# proj = r'path to project (.csproj)' | |
# test = r'path to project containing test (.csproj)' | |
# nuspec = r'path to nuspec definition (.nuspec)' | |
# | |
# | |
# msbuild = r'C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe' | |
# mstest = r'C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\MSTest.exe' | |
# nuget = r'C:\BuildTools\nuget\2199eada12ce\nuget.exe' | |
# trx2html = r'C:\BuildTools\trx2html\0.6\trx2html.exe' | |
# | |
# bld = MsBuilder(msbuild=msbuild, mstest=mstest, nuget=nuget, trx2html=trx2html) | |
# bld.run(proj, test, nuspec) | |
# | |
import os, shlex, subprocess, re, datetime | |
class MsBuilder: | |
def __init__(self, msbuild=None, mstest=None, nuget=None, trx2html=None): | |
# The following dictionary holds the location of the various | |
# msbuild.exe paths for the .net framework versions | |
if msbuild==None: | |
self.msbuild = r'C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe' | |
else: | |
self.msbuild = msbuild | |
# Path to mstest (this requires vs2010 to be installed | |
if mstest==None: | |
self.mstest = r'C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\MSTest.exe' | |
else: | |
self.mstest = mstest | |
# Path to nuget packager | |
if nuget==None: | |
self.nuget = r'C:\BuildTools\nuget\2199eada12ce\nuget.exe' | |
else: | |
self.nuget = nuget | |
# Path to trx2html transformation tool | |
if trx2html==None: | |
self.trx2html = r'C:\BuildTools\trx2html\0.6\trx2html.exe' | |
else: | |
self.trx2html = trx2html | |
def build(self, projPath): | |
# Ensure msbuild exists | |
if not os.path.isfile(self.msbuild): | |
raise Exception('MsBuild.exe not found. path=' + self.msbuild) | |
arg1 = '/t:Rebuild' | |
arg2 = '/p:Configuration=Release' | |
p = subprocess.call([self.msbuild, projPath, arg1, arg2]) | |
if p==1: return False # exit early | |
return True | |
def test(self, testProject): | |
if not os.path.isfile(self.msbuild): | |
raise Exception('MsBuild.exe not found. path=' + self.msbuild) | |
if not os.path.isfile(self.mstest): | |
raise Exception('MsTest.exe not found. path=' + self.mstest) | |
# build the test project | |
arg1 = '/t:Rebuild' | |
arg2 = '/p:Configuration=Release' | |
p = subprocess.call([self.msbuild, testProject, arg1, arg2]) | |
# find the test dll | |
f = open(testProject) | |
xml = f.read() | |
f.close() | |
match = re.search(r'<AssemblyName>(.*)</AssemblyName>', xml) | |
if not match: | |
print 'Could not find "AssemblyName" in test project file.' | |
return False | |
outputFolder = os.path.dirname(testProject) + '\\bin\\Release\\' | |
dll = outputFolder + match.groups()[0] + '.dll' | |
resultFile = outputFolder + 'testResults.trx' | |
if os.path.isfile(resultFile): | |
os.remove(resultFile) | |
# execute the tests in the container | |
arg1 = '/testcontainer:' + dll | |
arg2 = '/resultsfile:' + resultFile | |
p = subprocess.call([self.mstest, arg1, arg2]) | |
# convert the results file | |
if os.path.isfile(self.trx2html): | |
subprocess.call([self.trx2html, resultFile]) | |
else: | |
print 'TRX to HTML converter not found. path=' + self.trx2html | |
if p==1: return False # exit early | |
return True | |
def pack(self, packageSpec, version = '0.0.0.0'): | |
if not os.path.isfile(self.nuget): | |
raise Exception('Nuget.exe not found. path=' + self.nuget) | |
outputFolder = os.path.dirname(packageSpec) + '\\artifacts\\' | |
if not os.path.exists(outputFolder): | |
os.makedirs(outputFolder) | |
p = subprocess.call([self.nuget, 'pack', packageSpec, '-Version', version, '-Symbols', '-o', outputFolder]) | |
if p==1: return False #exit early | |
return True | |
def validate(self, projectPath): | |
packFile = os.path.dirname(projectPath) + '\\packages.config' | |
if os.path.isfile(packFile): | |
f = open(packFile) | |
xml = f.read() | |
f.close() | |
print xml | |
match = re.search(r'version="0.0.0.0"', xml) | |
if match: | |
# Found a non-versioned package being used by this project | |
return False | |
else: | |
print 'No "packages.config" file was found. path=' + packFile | |
return True | |
def run(self, proj=None, test=None, nuspec=None): | |
summary = ''; | |
# File header | |
start = datetime.datetime.now() | |
print '\n'*5 | |
summary += self.log('STARTED BUILD - ' + start.strftime("%Y-%m-%d %H:%M:%S")) | |
# Build the project | |
if proj is not None: | |
buildOk = self.build(proj) | |
if not buildOk: | |
self.log('BUILD: FAILED', start) | |
sys.exit(100) | |
summary += self.log('BUILD: SUCCEEDED', start) | |
else: | |
summary += self.log('BUILD: NOT SPECIFIED') | |
# Build the tests and run them | |
if test is not None: | |
testOk = self.test(test) | |
if not testOk: | |
print self.log('TESTS: FAILED', start) | |
sys.exit(100) | |
summary += self.log('TESTS: PASSED', start) | |
else: | |
summary += self.log('TESTS: NOT SPECIFIED') | |
# Package up the artifacts | |
if nuspec is not None: | |
packOk = self.pack(nuspec, '0.0.0.0') | |
if not packOk: | |
print self.log('NUGET PACK: FAILED', start) | |
sys.exit(100) | |
summary += self.log('NUGET PACK: SUCCEEDED', start) | |
else: | |
summary += self.log('NUGET PACK: NOT SPECIFIED') | |
# Validate dependencies | |
if not self.validate(proj): | |
print self.log('DEPENDENCIES: NOT VALIDATED - DETECTED UNVERSIONED DEPENDENCY', start) | |
sys.exit(100) | |
summary += self.log('DEPENDENCIES: VALIDATED', start) | |
# Build footer | |
stop = datetime.datetime.now() | |
diff = stop - start | |
summary += self.log('FINISHED BUILD', start) | |
# Build summary | |
print '\n\n' + '-'*80 | |
print summary | |
print '-'*80 | |
def log(self, message, start=None): | |
timestamp = '' | |
numsecs = '' | |
if start is not None: | |
split = datetime.datetime.now() | |
diff = split - start | |
timestamp = split.strftime("%Y-%m-%d %H:%M:%S") + '\t' | |
numsecs = ' (' + str(diff.seconds) + ' seconds)' | |
msg = timestamp + message + numsecs + '\n\n' | |
print '='*10 + '> ' + msg | |
return msg |
It doesn't do any publishing. Right now, it only builds, tests, and creates nugget packages.
…On Wednesday, July 20, 2011 at 11:33 AM, ha25gmail wrote:
@bob - Just looking into you build process above, if you wanted to publish a asp.net (http://asp.net) mvc application (to local directory), is this currently done with the above code? Can you use this with .net 3.5?
##
Reply to this email directly or view it on GitHub:
https://gist.github.com/1093434
Hi Bob
How do you manage to run msbuild without first running vcvarsall.bat to setup the environment?
Best regards
David
Thanks for sharing! That is cool stuff.
I have a comment about the default paths though. I think all those '' are interpreted by python as "escape" characters, so if you write 'C:\Windows...' will be readed as C:Windows. Even more, \n (in "...\nuget.exe"), \t (in "...\trx2html.exe"), \21 (in "...\2199eada12ce\nuget.exe") etc are real escape characters (being \n line break and \t tab, and \21 i guess should be some ascii character?).
Anyway, I think you should use the double escape () to escape the escape character: "C:\Windows...".
Cheers!
Thank you!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@bob - Just looking into you build process above, if you wanted to publish a asp.net mvc application (to local directory), is this currently done with the above code? Can you use this with .net 3.5?