Last active
December 18, 2015 21:48
-
-
Save janderit/5849594 to your computer and use it in GitHub Desktop.
sample rakefile for generating NuGet nupkgs from VS solutions
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
########################################################################## | |
# RAKE BUILD SCRIPT # | |
# (c) Philip Jander 2012, 2013 # | |
# Jander.IT # | |
########################################################################## | |
RAKEFILE_REVISION = "2.3" | |
# | |
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED | |
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |
# IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, | |
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | |
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING | |
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
# POSSIBILITY OF SUCH DAMAGE. | |
# | |
# While it has been in production use for some time, this build script | |
# is highly specific to my own projects and is provided for informational | |
# purposes only. You have been warned! | |
# | |
########################################################################## | |
############## CONFIGURATION ############################################# | |
DOTNET_FRAMEWORK_VERSION="v4.0.30319" # for nUnit runner | |
SOLUTIONINFOSOURCE = "build/solutioninfo.src" | |
ARTIFACTINFOSOURCE = "Properties/artifactinfo.src" | |
#task :default => [:clobber,:clean,:tests,:package] | |
task :default => :help | |
desc "Package artifacts" | |
task :package => [:nuget,:zip] | |
desc "Deploy artifacts" | |
task :deploy => [:clobber, :publish_nupkg, :publish_zip] | |
additionaltemporaries=['*.mm.dll', '*.mm.exe*', '*.nuspec'] | |
additionalartifacts=['*.pdb', '*.xml', '*.nupkg'] | |
SCAN_FOR_NUNIT=["c:/Programme", "c:/Program Files", "c:/Program Files (x86)"] | |
############## ENVIRONMENT ############################################### | |
WORKSPACE = ENV["WORKSPACE"] || "." | |
NUGETREPOTARGET = ENV["NUGETREPOTARGET"] || "nul:" | |
DEPLOYTARGET = ENV["DEPLOYTARGET"] || "nul:" | |
VERSION_BUILD = (ENV["BUILD_NUMBER"] || "0").rjust(5,"0") | |
VERSION_BRANCH = ENV["BRANCH"] || "development" | |
VERSION_BUILDDATE = Time.now.strftime("%Y-%m-%d %H:%M:%S") | |
solution_config_file = File.join(WORKSPACE,SOLUTIONINFOSOURCE) | |
if (File.exists?(solution_config_file)) | |
File.open( solution_config_file ).each do |line| | |
parts = line.strip.split(/\s+/,2) | |
VERSION_SHORT = parts[1] if parts[0]=="VERSION_SHORT" | |
PRODUCT_COMPONENT = parts[1] if parts[0]=="PRODUCT_COMPONENT" | |
PRODUCT_TITLE = parts[1] if parts[0]=="PRODUCT_TITLE" | |
PRODUCT_VERSION = parts[1] if parts[0]=="PRODUCT_VERSION" | |
PRODUCT_COMPANY = parts[1] if parts[0]=="PRODUCT_COMPANY" | |
PRODUCT_COPYRIGHT = parts[1] if parts[0]=="PRODUCT_COPYRIGHT" | |
end | |
else | |
VERSION_SHORT = "0.0.0" | |
PRODUCT_COMPONENT = "unnamed" | |
PRODUCT_TITLE = "unnamed" | |
PRODUCT_VERSION = "x" | |
PRODUCT_COMPANY = "" | |
PRODUCT_COPYRIGHT = "" | |
end | |
VERSION_LONG = VERSION_SHORT+"."+VERSION_BUILD | |
VERSION_FULL = VERSION_LONG + "-" + VERSION_BRANCH if VERSION_BRANCH != "release" | |
VERSION_FULL = VERSION_LONG if VERSION_BRANCH == "release" | |
relnotefile = File.join(WORKSPACE,"build","releasenotes-"+VERSION_SHORT+".txt") | |
NOTES = "" if !File.exists? relnotefile | |
NOTES = open(relnotefile, &:read) if File.exists? relnotefile | |
NUNIT_ARGS=" /labels" | |
########################################################################## | |
############## NOTHING OF INTEREST BELOW HERE ############################ | |
########################################################################## | |
# just kidding ;) | |
########################################################################## | |
puts | |
puts "Jander.IT common rakefile #{RAKEFILE_REVISION}" | |
puts "Build # "+VERSION_BUILD+" "+VERSION_BRANCH+" -> "+PRODUCT_TITLE+" "+PRODUCT_VERSION+" Komponente "+PRODUCT_COMPONENT+" "+VERSION_LONG | |
puts | |
########################################################################## | |
puts "loading dependencies..." | |
require 'albacore' | |
gem 'rubyzip' | |
require 'zip/zip' | |
require 'zip/zipfilesystem' | |
require 'rake/clean' | |
require 'erb' | |
require 'rexml/document' | |
########################################################################## | |
# library | |
nugetprojects = [] | |
zipprojects = [] | |
def suppress_warnings | |
original_verbosity = $VERBOSE | |
$VERBOSE = nil | |
result = yield | |
$VERBOSE = original_verbosity | |
return result | |
end | |
def write_erb(templateFile,outputFile,identifier) | |
suppress_warnings { | |
self.class.const_set("IDENTIFIER",identifier) | |
} | |
erb=open(templateFile, &:read) | |
render=ERB.new(erb).result() | |
File.open(outputFile, "w+") do |f| | |
f.write(render) | |
f.write("\n") | |
f.close() | |
end | |
end | |
def add2zip(zipfile, source, targetdir="", exclude=[]) | |
# enumerate files except for exclude mask | |
files=(Dir.entries(source)-[".",".."]).select{|x|!File.directory?(File.join(source,x))} | |
exclude.each do |ex| | |
files = files.reject{|x| x=~/\.#{ex}$/} | |
end | |
files.each do |file| | |
dest=targetdir=="" ? file : File.join(targetdir,file) | |
unless zipfile.find_entry(dest)!=nil | |
puts " + "+File.join(source,file) | |
zipfile.add(dest,File.join(source,file)) | |
end | |
end | |
end | |
def scan_for_executable(basepath, variablepath_regex, extendedpath, executable) | |
begin | |
return nil unless File.exists?(basepath) | |
return (Dir.entries(basepath)-[".",".."]) | |
.select{|x| x=~variablepath_regex} | |
.map{|x| File.join(basepath,x,extendedpath,executable)} | |
.select{|x| File.exists?(x)} | |
rescue | |
return nil | |
end | |
end | |
########################################################################## | |
# MSBUILD | |
msbuild :CleanDebug do |msb| | |
puts "-------------------------------------" | |
puts "calling MSBUILD..." | |
msb.properties :configuration => :Debug | |
msb.targets :Clean | |
msb.solution = SOLUTION | |
end | |
msbuild :CleanRelease do |msb| | |
puts "-------------------------------------" | |
puts "calling MSBUILD..." | |
msb.properties :configuration => :Release | |
msb.targets :Clean | |
msb.solution = SOLUTION | |
end | |
msbuild :Debug => [:allinfo] do |msb| | |
puts "-------------------------------------" | |
puts "calling MSBUILD..." | |
msb.properties :configuration => :Debug | |
msb.targets :Build | |
msb.solution = SOLUTION | |
end | |
msbuild :Release => [:allinfo] do |msb| | |
puts "-------------------------------------" | |
puts "calling MSBUILD..." | |
msb.properties :configuration => :Release | |
msb.targets :Build | |
msb.solution = SOLUTION | |
end | |
desc "Build debug configuration" | |
task :build => :Debug | |
desc "Build debug+release configuration" | |
task :buildall => [:Debug,:Release] | |
desc "Clean artifacts" | |
task :clean => [:CleanDebug,:CleanRelease] | |
########################################################################## | |
# AssemblyInfo.cs / artifactinfo.src | |
def assemblyInfo(project) | |
task project[:path] => project[:path]+"-assemblyinfo" | |
task :allinfo => project[:path]+"-assemblyinfo" | |
task project[:path]+"-assemblyinfo" do | |
puts "Writing AssemblyInfo.cs for #{project[:path]}..." | |
suppress_warnings { | |
self.class.const_set("ARTIFACT_TITLE",project[:title]) | |
self.class.const_set("ARTIFACT_DESCRIPTION",project[:description]) | |
} | |
template = File.join(WORKSPACE,"build","AssemblyInfo.cs.erb") | |
output = File.join(WORKSPACE,project[:path],"Properties","AssemblyInfo.cs") | |
write_erb(template, output, project[:title]) | |
end | |
end | |
def libraryArtifact(project, target) | |
target << project | |
task :nuget => project[:path] | |
end | |
desc "Generate assemblyinfo.cs files" | |
task :allinfo | |
desc "basic usage information" | |
task :help do | |
puts "" | |
puts "this rakefile assumes:" | |
puts "" | |
puts "1. A folder 'build' at solution level with files 'solutioninfo.src', 'AssemblyInfo.erb', and 'nuspec.erb'." | |
puts " Additionally, 'releasenotes-0.0.0.txt' files may be used." | |
puts "" | |
puts "2. A file artifactinfo.src in every project's Properties folder, which shall be deployed as either a NuGet package or a zip package or contains a nUnit test." | |
puts "" | |
puts "3. nunit-console.exe runner somewhere in a 'NUnit 2.*/bin' folder within either of these: #{SCAN_FOR_NUNIT}, if tests are to be run" | |
puts "" | |
puts "4. MSBuild accessible in its common location" | |
puts "" | |
puts "use 'rake -T' to show all commands" | |
puts "use 'rake build' or 'rake tests' for a fast debug build (+test run)" | |
puts "use 'rake package' to generate artifact packages" | |
puts "use 'rake deploy' on build server (and for testing) to publish artifacts" | |
puts "use 'rake clean' or 'rake clobber' to clean up all temporaries / build results" | |
end | |
desc "Sample artifactinfo.src" | |
task :artifactinfo do | |
puts "#" | |
puts "# sample artifactifo.src (place in Properties folder of each relevant project)" | |
puts "#" | |
puts "" | |
puts "TITLE assembly title" | |
puts "DESCRIPTION long assembly description" | |
puts "" | |
puts "# deployment: choose either NUGET + name and RUNTIME + runtime spec, " | |
puts "# TEST, or ZIP" | |
puts "#" | |
puts "# adds this product and all dependencies to a nuget package with the given name" | |
puts "# if the nupkg does not yet exist, it is created" | |
puts "NUGET valid_filename_part" | |
puts "" | |
puts "# use RUNTIME together with NUGET to specify the target: " | |
puts "# sl3, sl4, sl5, net20, net30, net35, net40, net45 or corenet45 (==winrt)" | |
puts "# defaults to net40" | |
puts "RUNTIME net40" | |
puts "" | |
puts "#" | |
puts "# invokes nunit test runner on this assembly but does not publish it" | |
puts "TEST" | |
puts "" | |
puts "#" | |
puts "# publishes the bin/Release folder into a zip file" | |
puts "ZIP" | |
puts "" | |
end | |
########################################################################## | |
# NUnit support | |
task :find_nunit_runner do | |
candidates = SCAN_FOR_NUNIT | |
.map{|x| scan_for_executable(x,/^NUnit\W2.+/,"bin","nunit-console.exe")} | |
.select{|x| x != nil} | |
NUNIT_EXE = candidates.last().last() if candidates.any?() | |
raise "No nunit-console.exe test runner found!" unless candidates.any?() | |
end | |
def nunitArtifact(project) | |
desc "Run tests: #{project[:path]}" | |
nunit project[:path]+"-test" => [:find_nunit_runner, project[:path]] do |nunit| | |
puts "-------------------------------------" | |
puts "running nUnit tests for #{project[:path]}..." | |
nunit.command = NUNIT_EXE | |
nunit.options '/framework '+DOTNET_FRAMEWORK_VERSION+NUNIT_ARGS+" /xml=#{project[:path]}-testresult.xml" | |
nunit.assemblies "#{WORKSPACE}/#{project[:path]}/Bin/Debug/#{project[:path]}.dll" | |
end | |
task :tests => [:build, project[:path]+"-test"] | |
end | |
desc "Run all unit tests" | |
task :tests | |
########################################################################## | |
# publish to .nupkg (NuGet) | |
def nugetArtifact(project, target) | |
target << project | |
task :nuget => project[:path] | |
end | |
NugetTarget=Struct.new(:framework,:packages) | |
NugetDependency=Struct.new(:id,:version) | |
def addfile(project,files,debugs) | |
files << "<file src=\"#{project[:path]}\\bin\\Release\\#{project[:path]}.dll\" target=\"lib\\release\\"+project[:runtime]+"\\\" />" | |
debugs << "<file src=\"#{project[:path]}\\bin\\Debug\\#{project[:path]}.pdb\" target=\"lib\\debug\\"+project[:runtime]+"\\\" />" | |
debugs << "<file src=\"#{project[:path]}\\bin\\Debug\\#{project[:path]}.dll\" target=\"lib\\debug\\"+project[:runtime]+"\\\" />" | |
debugs << "<file src=\"#{project[:path]}\\bin\\Debug\\#{project[:path]}.pdb\" target=\"lib\\"+project[:runtime]+"\\\" />" | |
debugs << "<file src=\"#{project[:path]}\\bin\\Debug\\#{project[:path]}.dll\" target=\"lib\\"+project[:runtime]+"\\\" />" | |
end | |
def addnuget(project,requires) | |
packagesconfig = File.join(WORKSPACE,project[:path],"packages.config") | |
if File.exists? packagesconfig | |
text=open(packagesconfig, &:read) | |
xml = REXML::Document.new(text).elements.first | |
raise("Invalid packages.config file for #{project[:path]}") if xml.name!="packages" | |
xml.elements.each{|package| | |
pid=package.attributes["id"] | |
pver=package.attributes["version"] | |
ptgt=package.attributes["targetFramework"] | |
puts "nuget dependency: "+pid+" "+ptgt+" "+pver | |
tgt=requires.select{|r|r.framework==ptgt}.first | |
if tgt==nil | |
tgt=NugetTarget.new(ptgt,[]) | |
requires << tgt | |
end | |
pkg=tgt.packages.select{|p|p.id==pid} | |
if pkg.count!=0 | |
raise "nuget dependency version conflict for package #{pid}" if pkg.any? {|p|p.version!=pver} | |
else | |
tgt.packages+=[NugetDependency.new(pid,pver)] | |
end | |
} | |
end | |
end | |
def addtorequirements(project,files,debugs,packages,visited,projects,config) | |
if visited.include?(project[:path]) | |
return | |
end | |
visited << project[:path] | |
addfile(project,files,debugs) | |
addnuget(project,packages) | |
puts " scanning "+project[:path] | |
path = File.join(WORKSPACE,project[:path]) | |
pattern = File.join(path,"Bin",config,'*.dll') | |
pattern.gsub!('\\','/') | |
dlls = Dir[pattern].select{|dll|!(dll=~/\.mm\.dll$/)} | |
dlls.each{|dll| | |
file = File.basename(dll,".dll") | |
case | |
when project[:path]==file | |
puts" ? "+file+" self "+project[:path] | |
when projects.any?{|p|p[:path]==file} | |
if visited.include?(file) | |
puts" ? "+file+" (already included)" | |
else | |
puts" ? "+file+" solution" | |
addtorequirements(projects.select{|p|p[:path]==file}.first,files,debugs,packages,visited,projects,config) | |
end | |
else | |
puts" ? "+file+" assumed nuget" | |
end | |
} | |
puts " end "+project[:path] | |
end | |
task :nuget => [:buildall, :tests] do | |
nugetpkgs = [] | |
nugetprojects.each {|project| | |
if (!nugetpkgs.include?(project[:export])) | |
nugetpkgs << project[:export] | |
end | |
} | |
nugetpkgs.each {|pkg| | |
puts "-------------------------------------" | |
puts "Generating NUGET Package '"+pkg+"' ..." | |
requires=[] | |
files=[] | |
debugs=[] | |
nuf="" | |
nud="" | |
nudep="" | |
nugetprojects.each {|project| | |
addtorequirements(project,files,debugs,requires,[],PROJECTS,"Debug") | |
} | |
files.each{|f| nuf+=f+"\n"} | |
debugs.each{|f| nud+=f+"\n"} | |
requires.each{|tgtf| | |
nudep+="<group targetFramework=\"#{tgtf.framework}\">" | |
tgtf.packages.each{|pkg| | |
nudep+="<dependency id=\"#{pkg.id}\" version=\"#{pkg.version}\" />" | |
} | |
nudep+="</group>" | |
} | |
if nudep!="" | |
nudep="<dependencies>"+nudep+"</dependencies>" | |
end | |
suppress_warnings { | |
self.class.const_set("NUGET_FILES",nuf) | |
self.class.const_set("NUGET_DBGFILES",nud) | |
self.class.const_set("NUGET_DEPENDENCIES",nudep) | |
} | |
template = File.join(WORKSPACE,"build","nuspec.erb") | |
output = File.join(WORKSPACE,pkg+".nuspec") | |
write_erb(template, output, pkg) | |
cmd = ".nuget/nuget.exe pack "+pkg+".nuspec -Verbose" | |
puts ": "+cmd | |
system(cmd) | |
} | |
end | |
task :publish_nupkg => [:nuget] do | |
puts "-------------------------------------" | |
pattern = File.join(WORKSPACE,'*.nupkg') | |
pattern.gsub!('\\','/') | |
puts "Looking for NUPKG files to deploy...: "+pattern | |
sources = Dir[pattern] | |
sources.each do |f| | |
puts " +copying "+f+" to "+ NUGETREPOTARGET | |
cp f,NUGETREPOTARGET, :verbose => true | |
end | |
end | |
########################################################################## | |
# publish to .pkg.zip (proprietary) | |
def zipArtifact(project, target) | |
target << project | |
task :zip => project[:path] | |
end | |
task :zip => [:Release, :tests] do | |
puts "-------------------------------------" | |
puts "Generating .pkg.zip packages..." | |
puts "" | |
zipprojects.each{|project| | |
target = File.join(WORKSPACE,PRODUCT_TITLE+"-"+PRODUCT_VERSION+"-"+project[:title]+"_"+VERSION_BUILD+".pkg.zip") | |
puts " - project "+project[:path]+" -> "+target | |
source = File.join(WORKSPACE,project[:path],"Bin","Release") | |
source.gsub!('\\','/') | |
Zip::ZipFile.open(target, 'w') do |zipfile| | |
add2zip(zipfile, source, "", ["mm.exe","mm.dll","zip","xml","vshost.exe","vshost.exe.config","vshost.config.manifest"]) | |
end | |
} | |
end | |
task :publish_zip => [:zip] do | |
puts "-------------------------------------" | |
pattern = File.join(WORKSPACE,'*.pkg.zip') | |
pattern.gsub!('\\','/') | |
puts "Looking for ZIP files to deploy...: "+pattern | |
sources = Dir[pattern] | |
sources.each do |f| | |
puts " +copying "+f+" to "+ DEPLOYTARGET | |
cp f,DEPLOYTARGET, :verbose => true | |
end | |
end | |
########################################################################## | |
# MAIN | |
def find_solution() | |
(Dir.entries(WORKSPACE)-[".",".."]).select{|x|x=~/\.sln$/}[0] | |
end | |
def find_csprojs() | |
(Dir.entries(WORKSPACE)-[".",".."]) | |
.select{|x|File.directory?(File.join(WORKSPACE,x))} | |
.select{|x|File.exists?(File.join(WORKSPACE,x,x+".csproj"))} | |
end | |
########################################################################## | |
puts "setting up..." | |
additionalartifacts.each{|pattern|CLOBBER.include(pattern)} | |
additionaltemporaries.each{|pattern|CLEAN.include(pattern)} | |
########################################################################## | |
puts "scanning artifacts..." | |
# find solution | |
SOLUTION = find_solution() | |
puts "Solution: "+SOLUTION | |
# find c# projects | |
csprojectDirectories = find_csprojs() | |
# load artifactinfo.src files | |
projects = csprojectDirectories.map {|project| | |
artifact_title=project | |
artifact_description=project | |
artifact_runtime="net40" | |
artifact_deployment="LIBRARY" | |
nuget_export = "" | |
artifactinfofile = File.join(WORKSPACE,project,ARTIFACTINFOSOURCE) | |
if (File.exists?(artifactinfofile)) | |
File.open(artifactinfofile).each do |line| | |
if (line[0].ord==239) | |
line=line[3..-1] | |
end | |
parts = line.strip.split(/\s+/,2) | |
artifact_title = parts[1] if parts[0]=="TITLE" | |
artifact_description = parts[1] if parts[0]=="DESCRIPTION" | |
artifact_runtime = parts[1] if parts[0]=="RUNTIME" | |
artifact_deployment = "ZIP" if parts[0]=~/^ZIP$/ | |
artifact_deployment = "TEST" if parts[0]=~/^TEST$/ | |
artifact_deployment = "NUGET" if parts[0]=~/^NUGET$/ | |
nuget_export = parts[1] if parts[0]=~/^NUGET$/ | |
end | |
end | |
{ | |
:path => project, | |
:title => artifact_title, | |
:description => artifact_description, | |
:runtime => artifact_runtime, | |
:deploy => artifact_deployment, | |
:export => nuget_export | |
} | |
} | |
puts "Found #{projects.count} c# artifact(s) with #{projects.select{|p|p[:deploy]!=""}.count} deploy rules." | |
projects.each{|project| | |
puts " - "+project[:path]+" "+project[:title]+" "+project[:deploy] | |
assemblyInfo(project) | |
nugetArtifact(project, nugetprojects) if project[:deploy]=="NUGET" | |
nunitArtifact(project) if project[:deploy]=="TEST" | |
zipArtifact(project, zipprojects) if project[:deploy]=="ZIP" | |
} | |
PROJECTS=projects | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment