require 'rake' verbose(true) # log sh commands to stdout before running them desc "Default build will run :setup, :clean, :compile, harvest_compile, unit_test" task :default => [:setup, :clean, :assembly_info, :compile, :harvest_compile, :unit_test] desc "Continuous Integration - smoke test. Build, run unit tests. CI server should grab artifacts dir to make available to other CI tasks" task :ci_smoke => [:default] desc "Continuous Integration - integration test. Requires SQL Server on localhost. Relies on artifacts being present, which is CI's job." task :ci_integration_test => [:integration_test] desc "Continuous Integration - NCover-profiling. Requires: NCover license; artifacts." task :ci_ncover => [:ncover_profile, :ncover_summary] desc "Displays a list of tasks" task :help do taskHash = Hash[*(`rake.cmd -T`.split(/\n/).collect { |l| l.match(/rake (\S+)\s+\#\s(.+)/).to_a }.collect { |l| [l[1], l[2]] }).flatten] indent = " " puts "rake #{indent}#Runs the 'default' task" taskHash.each_pair do |key, value| if key.nil? next end puts "rake #{key}#{indent.slice(0, indent.length - key.length)}##{value}" end end desc "Setup build configuration - TODO: read from command line / config" task :setup do @conf = {} @conf[:asm_info_replacements] = {} @conf[:paths] = {} @conf[:msbuild] = {} @conf[:msbuild][:properties] = {} @conf[:xunit] = {} @conf[:svn] = {} @conf[:clrversion] = 'v3.5' @conf[:configuration] = 'Debug' @conf[:product] = 'Thor' @conf[:major] = 0 @conf[:minor] = 1 @conf[:asm_info_replacements][:built_on] = Time.now @conf[:asm_info_replacements][:configuration] = @conf[:configuration] @conf[:asm_info_replacements][:company] = 'Narrowstep' @conf[:asm_info_replacements][:product] = @conf[:product] @conf[:paths][:artifacts] = 'build' @conf[:paths][:artifacts_bin] = "#{@conf[:paths][:artifacts]}/bin/#{@conf[:configuration]}/" @conf[:paths][:artifacts_reports] = "#{@conf[:paths][:artifacts]}/reports/#{@conf[:configuration]}/" @conf[:paths][:lib] = '../_library' @conf[:paths][:src] = 'src' @conf[:paths][:src_asm_info] = @conf[:paths][:src] + '/CommonAssemblyInfo.cs' @conf[:paths][:tests] = 'tests' @conf[:paths][:tests_asm_info] = @conf[:paths][:tests] + '/TestsAssemblyInfo.cs' @conf[:msbuild][:project] = @conf[:product] + '.sln' @conf[:msbuild][:verbosity] = 'n' @conf[:msbuild][:properties][:Configuration] = @conf[:configuration] end desc "Clean output" task :clean => [:setup] do @conf[:msbuild][:targets] = 'Clean' ms = MSBuild.new(@conf[:clrversion], @conf[:msbuild]) ms.run output = @conf[:paths][:artifacts_bin] rm_r output if Dir.exists?(output) rm @conf[:paths][:src_asm_info] if File.exists?(@conf[:paths][:src_asm_info]) rm @conf[:paths][:tests_asm_info] if File.exists?(@conf[:paths][:tests_asm_info]) rm_r @conf[:paths][:artifacts_reports] if Dir.exists?(@conf[:paths][:artifacts_reports]) end desc "Figure out what version (major.minor.build.svn-revision) we're building and store it for other tasks" task :version => [:setup] do build = 0 if ENV['build.number'] build = ENV['build.number'] end svn = "\"#{@conf[:paths][:lib]}/svn/bin/svn.exe\"" svn_revision = get_svn_revision(svn, Dir.pwd) @conf[:version] = "#{@conf.fetch(:major, 0)}.#{@conf.fetch(:minor, 0)}.#{build}.#{svn_revision}" @conf[:asm_info_replacements][:version] = @conf[:version] end desc "Generate the AssemblyInfo.cs files in each project based on build & revision numbers, product name, etc" task :assembly_info => [:setup, :version] do ai = AsmInfo.new(@conf[:paths][:lib], @conf[:paths][:src_asm_info], @conf[:asm_info_replacements]) ai.run ai = AsmInfo.new(@conf[:paths][:lib], @conf[:paths][:tests_asm_info], @conf[:asm_info_replacements]) ai.run end desc "Compile the Thor.sln solution" task :compile => [:setup, :clean] do @conf[:msbuild][:targets] = 'Build' ms = MSBuild.new(@conf[:clrversion], @conf[:msbuild]) ms.run end desc "Publishes output from compile to build artifacts" task :harvest_compile => [:setup] do output = @conf[:paths][:artifacts_bin] rm_r output if Dir.exists?(output) Dir.mkdir_r('.', output) harvest_dir(@conf[:paths][:src], output) harvest_dir(@conf[:paths][:tests], output) end desc "Run unit-tests from unit test assemblies" task :unit_test => [:setup] do reports = @conf[:paths][:artifacts_reports] Dir.mkdir_r('.', reports) x = XUnit.new('unit', @conf[:paths][:lib], @conf[:paths][:artifacts_bin], @conf[:paths][:artifacts_reports], @conf[:xunit]) x.run end desc "Run integration tests" task :integration_test => [:setup] do reports = @conf[:paths][:artifacts_reports] Dir.mkdir_r('.', reports) x = XUnit.new('Integration', @conf[:paths][:lib], @conf[:paths][:artifacts_bin], @conf[:paths][:artifacts_reports], @conf[:xunit]) x.run end desc "Profile assemblies to generate NCover result files" task :ncover_profile do end desc "Report on NCover results" task :ncover_summary do end def harvest_dir(source, dest) files = Dir.glob(source + '/**/bin/**/*.*') files.each do |f| cp_r(f, dest) end end def get_svn_revision(svn, path) cmd = "#{svn} info #{path}" out = `#{svn} info .` rev = out.match(/Revision: (\d+)/)[1] end class AsmInfo def initialize(lib_path, asm_info, replacements) @asm_info = asm_info @template = asm_info + '.template' @built_on = replacements.fetch(:built_on, Time.now) @replacements = replacements end def run content = File.read_to_end(@template) @replacements.each do |key,value| content = content.gsub(/(\$\{#{key}\})/, value.to_s) end File.delete(@asm_info) if File.exists?(@asm_info) File.write(@asm_info, content) end end class Dir def self.exists?(path) File.directory?(path) end def self.mkdir_r(startAt, path) grown = startAt path.split('/').each do |segment| grown += '/' + segment Dir.mkdir(grown) unless Dir.exists?(grown) end grown += '/' end end class File def self.read_to_end(path) content = '' File.open(path, 'r') do |file| while (line = file.gets) content += line end end return content end def self.write(path, content) File.open(path, 'w') do |file| file.puts content end end end class MSBuild attr_accessor :version attr_accessor :project attr_accessor :properties attr_accessor :targets attr_accessor :verbosity def initialize(version, opts) puts opts @version = version @project = opts.fetch(:project, 'default.proj') @properties = opts.fetch(:properties, {}) @targets = opts.fetch(:targets, 'Rebuild') @verbosity = opts.fetch(:verbosity, 'm') end def run frameworkDir = File.join(ENV['windir'].dup, 'Microsoft.NET', 'Framework', @version) msbuildFile = File.join(frameworkDir, 'msbuild.exe') p = [] @properties.each {|key, value| p.push("#{key}=#{value}") } cmd = "#{msbuildFile} #{@project} /maxcpucount /v:#{@verbosity} /property:BuildInParallel=true /p:#{p.join(";")} /t:#{@targets}" puts "running msbuild: #{cmd}" sh cmd end end class NCover def initialize() end end class XUnit def initialize(suite, lib_path, asm_path, report_path, opts) @suite = suite @xml = "#{report_path}#{@suite}.test-results.xml" @html = "#{report_path}#{@suite}.test-results.html" @nunit = "#{report_path}#{@suite}.test-results.nunit.xml" @xunit = "\"#{lib_path}/xunit/xunit.console.exe\"" file_pattern = "#{asm_path}Thor.#{suite}.Tests.dll" @asm = Dir.glob(file_pattern).sort().join(' ') #@teamcity #@wait #@noshadow end def run cmd = "#{@xunit} #{@asm} /xml #{@xml} /html #{@html} /nunit #{@nunit}" puts "running xunit: #{cmd}" sh cmd end end