Skip to content

Instantly share code, notes, and snippets.

@janderit
Last active December 18, 2015 21:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save janderit/5849594 to your computer and use it in GitHub Desktop.
Save janderit/5849594 to your computer and use it in GitHub Desktop.
sample rakefile for generating NuGet nupkgs from VS solutions
##########################################################################
# 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