-
Notifications
You must be signed in to change notification settings - Fork 64
Upgrading from v1.0
I got a legacy codebase; how can I upgrade it to use the latest version of albacore?
You can see the work in progress at feature/albacore2.
Let's take this verbose Rakefile as an example:
COPYRIGHT = "Copyright 2012 Chris Patterson, Dru Sellers, Travis Smith, All rights reserved."
require File.dirname(__FILE__) + "/build_support/BuildUtils.rb"
require File.dirname(__FILE__) + "/build_support/util.rb"
include FileTest
require 'albacore'
require File.dirname(__FILE__) + "/build_support/versioning.rb"
PRODUCT = 'Topshelf'
CLR_TOOLS_VERSION = 'v4.0.30319'
OUTPUT_PATH = 'bin/Release'
props = {
:src => File.expand_path("src"),
:nuget => File.join(File.expand_path("src"), ".nuget", "nuget.exe"),
:output => File.expand_path("build_output"),
:artifacts => File.expand_path("build_artifacts"),
:lib => File.expand_path("lib"),
:projects => ["Topshelf"],
:keyfile => File.expand_path("Topshelf.snk")
}
desc "Cleans, compiles, il-merges, unit tests, prepares examples, packages zip"
task :all => [:default, :package]
desc "**Default**, compiles and runs tests"
task :default => [:clean, :nuget_restore, :compile, :package]
desc "Update the common version information for the build. You can call this task without building."
assemblyinfo :global_version do |asm|
# Assembly file config
asm.product_name = PRODUCT
asm.description = "Topshelf is an open source project for hosting services without friction. By referencing Topshelf, your console application *becomes* a service installer with a comprehensive set of command-line options for installing, configuring, and running your application as a service."
asm.version = FORMAL_VERSION
asm.file_version = FORMAL_VERSION
asm.custom_attributes :AssemblyInformationalVersion => "#{BUILD_VERSION}",
:ComVisibleAttribute => false,
:CLSCompliantAttribute => true
asm.copyright = COPYRIGHT
asm.output_file = 'src/SolutionVersion.cs'
asm.namespaces "System", "System.Reflection", "System.Runtime.InteropServices"
end
desc "Prepares the working directory for a new build"
task :clean do
FileUtils.rm_rf props[:output]
waitfor { !exists?(props[:output]) }
FileUtils.rm_rf props[:artifacts]
waitfor { !exists?(props[:artifacts]) }
Dir.mkdir props[:output]
Dir.mkdir props[:artifacts]
end
desc "Cleans, versions, compiles the application and generates build_output/."
task :compile => [:versioning, :global_version, :build4, :tests4, :copy4, :build35, :tests35, :copy35]
task :copy35 => [:build35] do
copyOutputFiles File.join(props[:src], "Topshelf/bin/Release/v3.5"), "Topshelf.{dll,pdb,xml}", File.join(props[:output], 'net-3.5')
copyOutputFiles File.join(props[:src], "Topshelf.Log4Net/bin/Release/v3.5"), "Topshelf.Log4Net.{dll,pdb,xml}", File.join(props[:output], 'net-3.5')
copyOutputFiles File.join(props[:src], "Topshelf.NLog/bin/Release/v3.5"), "Topshelf.NLog.{dll,pdb,xml}", File.join(props[:output], 'net-3.5')
copyOutputFiles File.join(props[:src], "Topshelf.Rehab/bin/Release/v3.5"), "Topshelf.Rehab.{dll,pdb,xml}", File.join(props[:output], 'net-3.5')
copyOutputFiles File.join(props[:src], "Topshelf.Supervise/bin/Release/v3.5"), "Topshelf.Supervise.{dll,pdb,xml}", File.join(props[:output], 'net-3.5')
end
task :copy4 => [:build4] do
copyOutputFiles File.join(props[:src], "Topshelf/bin/Release"), "Topshelf.{dll,pdb,xml}", File.join(props[:output], 'net-4.0-full')
copyOutputFiles File.join(props[:src], "Topshelf.Log4Net/bin/Release"), "Topshelf.Log4Net.{dll,pdb,xml}", File.join(props[:output], 'net-4.0-full')
copyOutputFiles File.join(props[:src], "Topshelf.NLog/bin/Release"), "Topshelf.NLog.{dll,pdb,xml}", File.join(props[:output], 'net-4.0-full')
copyOutputFiles File.join(props[:src], "Topshelf.Rehab/bin/Release"), "Topshelf.Rehab.{dll,pdb,xml}", File.join(props[:output], 'net-4.0-full')
copyOutputFiles File.join(props[:src], "Topshelf.Supervise/bin/Release"), "Topshelf.Supervise.{dll,pdb,xml}", File.join(props[:output], 'net-4.0-full')
end
desc "Only compiles the application."
msbuild :build35 do |msb|
msb.properties :Configuration => "Release",
:Platform => 'Any CPU',
:TargetFrameworkVersion => "v3.5"
msb.use :net4
msb.targets :Clean, :Build
msb.properties[:SignAssembly] = 'true'
msb.properties[:AssemblyOriginatorKeyFile] = props[:keyfile]
msb.solution = 'src/Topshelf.sln'
end
desc "Only compiles the application."
msbuild :build4 do |msb|
msb.properties :Configuration => "Release",
:Platform => 'Any CPU'
msb.use :net4
msb.targets :Clean, :Build
msb.properties[:SignAssembly] = 'true'
msb.properties[:AssemblyOriginatorKeyFile] = props[:keyfile]
msb.solution = 'src/Topshelf.sln'
end
def copyOutputFiles(fromDir, filePattern, outDir)
FileUtils.mkdir_p outDir unless exists?(outDir)
Dir.glob(File.join(fromDir, filePattern)){|file|
copy(file, outDir) if File.file?(file)
}
end
desc "Runs unit tests"
nunit :tests35 => [:build35] do |nunit|
nunit.command = File.join('src', 'packages','NUnit.Runners.2.6.3', 'tools', 'nunit-console.exe')
nunit.options = "/framework=#{CLR_TOOLS_VERSION}", '/nothread', '/nologo', '/labels', "\"/xml=#{File.join(props[:artifacts], 'nunit-test-results-net-3.5.xml')}\""
nunit.assemblies = FileList[File.join(props[:src], "Topshelf.Tests/bin/Release", "Topshelf.Tests.dll")]
end
desc "Runs unit tests"
nunit :tests4 => [:build4] do |nunit|
nunit.command = File.join('src', 'packages','NUnit.Runners.2.6.3', 'tools', 'nunit-console.exe')
nunit.options = "/framework=#{CLR_TOOLS_VERSION}", '/nothread', '/nologo', '/labels', "\"/xml=#{File.join(props[:artifacts], 'nunit-test-results-net-4.0.xml')}\""
nunit.assemblies = FileList[File.join(props[:src], "Topshelf.Tests/bin/Release", "Topshelf.Tests.dll")]
end
task :package => [:nuget, :zip_output]
desc "ZIPs up the build results."
zip :zip_output => [:versioning] do |zip|
zip.directories_to_zip = [props[:output]]
zip.output_file = "Topshelf-#{NUGET_VERSION}.zip"
zip.output_path = props[:artifacts]
end
desc "restores missing packages"
msbuild :nuget_restore do |msb|
msb.use :net4
msb.targets :RestorePackages
msb.solution = File.join(props[:src], "Topshelf.Tests", "Topshelf.Tests.csproj")
end
desc "restores missing packages"
msbuild :nuget_restore do |msb|
msb.use :net4
msb.targets :RestorePackages
msb.solution = File.join(props[:src], "Topshelf.Log4Net", "Topshelf.Log4Net.csproj")
end
desc "restores missing packages"
msbuild :nuget_restore do |msb|
msb.use :net4
msb.targets :RestorePackages
msb.solution = File.join(props[:src], "Topshelf.NLog", "Topshelf.NLog.csproj")
end
desc "Builds the nuget package"
task :nuget => [:versioning, :create_nuspec] do
sh "#{props[:nuget]} pack #{props[:artifacts]}/Topshelf.nuspec /Symbols /OutputDirectory #{props[:artifacts]}"
sh "#{props[:nuget]} pack #{props[:artifacts]}/Topshelf.Log4Net.nuspec /Symbols /OutputDirectory #{props[:artifacts]}"
sh "#{props[:nuget]} pack #{props[:artifacts]}/Topshelf.NLog.nuspec /Symbols /OutputDirectory #{props[:artifacts]}"
sh "#{props[:nuget]} pack #{props[:artifacts]}/Topshelf.Rehab.nuspec /Symbols /OutputDirectory #{props[:artifacts]}"
sh "#{props[:nuget]} pack #{props[:artifacts]}/Topshelf.Supervise.nuspec /Symbols /OutputDirectory #{props[:artifacts]}"
end
nuspec :create_nuspec do |nuspec|
nuspec.id = 'Topshelf'
nuspec.version = NUGET_VERSION
nuspec.authors = 'Chris Patterson, Dru Sellers, Travis Smith'
nuspec.summary = 'Topshelf, Friction-free Windows Services'
nuspec.description = 'Topshelf is an open source project for hosting services without friction. By referencing Topshelf, your console application *becomes* a service installer with a comprehensive set of command-line options for installing, configuring, and running your application as a service.'
nuspec.title = 'Topshelf'
nuspec.projectUrl = 'http://github.com/Topshelf/Topshelf'
nuspec.iconUrl = 'http://topshelf-project.com/wp-content/themes/pandora/slide.1.png'
nuspec.language = "en-US"
nuspec.licenseUrl = "http://www.apache.org/licenses/LICENSE-2.0"
nuspec.requireLicenseAcceptance = "false"
nuspec.output_file = File.join(props[:artifacts], 'Topshelf.nuspec')
add_files props[:output], 'Topshelf.{dll,pdb,xml}', nuspec
nuspec.file(File.join(props[:src], "Topshelf\\**\\*.cs").gsub("/","\\"), "src")
end
nuspec :create_nuspec do |nuspec|
nuspec.id = 'Topshelf.Log4Net'
nuspec.version = NUGET_VERSION
nuspec.authors = 'Chris Patterson, Dru Sellers, Travis Smith'
nuspec.summary = 'Topshelf, Friction-free Windows Services'
nuspec.description = 'Log4Net Logging Integration for Topshelf. Topshelf is an open source project for hosting services without friction. By referencing Topshelf, your console application *becomes* a service installer with a comprehensive set of command-line options for installing, configuring, and running your application as a service.'
nuspec.title = 'Topshelf.Log4Net'
nuspec.projectUrl = 'http://github.com/Topshelf/Topshelf'
nuspec.iconUrl = 'http://topshelf-project.com/wp-content/themes/pandora/slide.1.png'
nuspec.language = "en-US"
nuspec.licenseUrl = "http://www.apache.org/licenses/LICENSE-2.0"
nuspec.requireLicenseAcceptance = "false"
nuspec.dependency "Topshelf", NUGET_VERSION
nuspec.dependency "Log4Net", "2.0.3"
nuspec.output_file = File.join(props[:artifacts], 'Topshelf.Log4Net.nuspec')
add_files props[:output], 'Topshelf.Log4Net.{dll,pdb,xml}', nuspec
nuspec.file(File.join(props[:src], "Topshelf.Log4Net\\**\\*.cs").gsub("/","\\"), "src")
end
nuspec :create_nuspec do |nuspec|
nuspec.id = 'Topshelf.NLog'
nuspec.version = NUGET_VERSION
nuspec.authors = 'Chris Patterson, Dru Sellers, Travis Smith'
nuspec.summary = 'Topshelf, Friction-free Windows Services'
nuspec.description = 'NLog Logging Integration for Topshelf. Topshelf is an open source project for hosting services without friction. By referencing Topshelf, your console application *becomes* a service installer with a comprehensive set of command-line options for installing, configuring, and running your application as a service.'
nuspec.title = 'Topshelf.NLog'
nuspec.projectUrl = 'http://github.com/Topshelf/Topshelf'
nuspec.iconUrl = 'http://topshelf-project.com/wp-content/themes/pandora/slide.1.png'
nuspec.language = "en-US"
nuspec.licenseUrl = "http://www.apache.org/licenses/LICENSE-2.0"
nuspec.requireLicenseAcceptance = "false"
nuspec.dependency "Topshelf", NUGET_VERSION
nuspec.dependency "NLog", "2.1.0"
nuspec.output_file = File.join(props[:artifacts], 'Topshelf.NLog.nuspec')
add_files props[:output], 'Topshelf.NLog.{dll,pdb,xml}', nuspec
nuspec.file(File.join(props[:src], "Topshelf.NLog\\**\\*.cs").gsub("/","\\"), "src")
end
nuspec :create_nuspec do |nuspec|
nuspec.id = 'Topshelf.Rehab'
nuspec.version = NUGET_VERSION
nuspec.authors = 'Chris Patterson, Dru Sellers, Travis Smith'
nuspec.summary = 'Topshelf, Friction-free Windows Services'
nuspec.description = 'Rehab provides automatic updates to services. Topshelf is an open source project for hosting services without friction. By referencing Topshelf, your console application *becomes* a service installer with a comprehensive set of command-line options for installing, configuring, and running your application as a service.'
nuspec.title = 'Topshelf.Rehab'
nuspec.projectUrl = 'http://github.com/Topshelf/Topshelf'
nuspec.iconUrl = 'http://topshelf-project.com/wp-content/themes/pandora/slide.1.png'
nuspec.language = "en-US"
nuspec.licenseUrl = "http://www.apache.org/licenses/LICENSE-2.0"
nuspec.requireLicenseAcceptance = "false"
nuspec.dependency "Topshelf", NUGET_VERSION
nuspec.output_file = File.join(props[:artifacts], 'Topshelf.Rehab.nuspec')
add_files props[:output], 'Topshelf.Rehab.{dll,pdb,xml}', nuspec
nuspec.file(File.join(props[:src], "Topshelf.Rehab\\**\\*.cs").gsub("/","\\"), "src")
end
nuspec :create_nuspec do |nuspec|
nuspec.id = 'Topshelf.Supervise'
nuspec.version = NUGET_VERSION
nuspec.authors = 'Chris Patterson, Dru Sellers, Travis Smith'
nuspec.summary = 'Topshelf, Supervised Services'
nuspec.description = 'Supervise provides automatic recovery, memory and CPU monitoring, and scheduled restarting to services. Topshelf is an open source project for hosting services without friction. By referencing Topshelf, your console application *becomes* a service installer with a comprehensive set of command-line options for installing, configuring, and running your application as a service.'
nuspec.title = 'Topshelf.Supervise'
nuspec.projectUrl = 'http://github.com/Topshelf/Topshelf'
nuspec.iconUrl = 'http://topshelf-project.com/wp-content/themes/pandora/slide.1.png'
nuspec.language = "en-US"
nuspec.licenseUrl = "http://www.apache.org/licenses/LICENSE-2.0"
nuspec.requireLicenseAcceptance = "false"
nuspec.dependency "Topshelf", NUGET_VERSION
nuspec.output_file = File.join(props[:artifacts], 'Topshelf.Supervise.nuspec')
add_files props[:output], 'Topshelf.Supervise.{dll,pdb,xml}', nuspec
nuspec.file(File.join(props[:src], "Topshelf.Supervise\\**\\*.cs").gsub("/","\\"), "src")
end
def project_outputs(props)
props[:projects].map{ |p| "src/#{p}/bin/#{BUILD_CONFIG}/#{p}.dll" }.
concat( props[:projects].map{ |p| "src/#{p}/bin/#{BUILD_CONFIG}/#{p}.exe" } ).
find_all{ |path| exists?(path) }
end
def get_commit_hash_and_date
begin
commit = `git log -1 --pretty=format:%H`
git_date = `git log -1 --date=iso --pretty=format:%ad`
commit_date = DateTime.parse( git_date ).strftime("%Y-%m-%d %H%M%S")
rescue
commit = "git unavailable"
end
[commit, commit_date]
end
def add_files stage, what_dlls, nuspec
[['net35', 'net-3.5'], ['net40', 'net-4.0'], ['net40-full', 'net-4.0-full']].each{|fw|
takeFrom = File.join(stage, fw[1], what_dlls)
Dir.glob(takeFrom).each do |f|
nuspec.file(f.gsub("/", "\\"), "lib\\#{fw[0]}")
end
}
end
def waitfor(&block)
checks = 0
until block.call || checks >10
sleep 0.5
checks += 1
end
raise 'Waitfor timeout expired. Make sure that you aren\'t running something from the build output folders, or that you have browsed to it through Explorer.' if checks > 10
end
It contains these concepts, in order:
- the versioning helpers
- strong naming
- assembly info generation
- a clean task
- a compile task for .Net 4.0 and .Net 3.5
- a task that copies files for .Net 3.5
- a task that copies files for .Net 4.5
- a task that builds .Net 3.5
- a task that builds .Net 4.5
- a task that runs tests for .Net 3.5
- a task that runs tests for .Net 4.5
- a task that zips the output
- tasks to restore nugets
- tasks to create nuspecs
- tasks to package those into nupkgs
- inline functions that handle getting details from git
We're going to improve on it a bit.
The first part of the file is:
COPYRIGHT = "Copyright 2012 Chris Patterson, Dru Sellers, Travis Smith, All rights reserved."
require File.dirname(__FILE__) + "/build_support/BuildUtils.rb"
require File.dirname(__FILE__) + "/build_support/util.rb"
include FileTest
require 'albacore'
require File.dirname(__FILE__) + "/build_support/versioning.rb"
This is a bad way of writing it because a) it's unidiomatic - instead require_relative
should be used: require_relative 'build_support/build_utils'
(and snake_case should be used for the file names instead of PascalCase).
That's not as interesting as how the functionality can be made. I'm going to ignore utils, as that's mostly helper functions, which are better suited as pull-requests towards this repository or as a piece of the 'Albacore::Tools' namespace.
versioning.rb
is my own creation, by an early self. It's been incorporated into v2.0 with require 'albacore/tasks/versionizer'
-- and you can create a Task with:
Albacore::Tasks::Versionizer.new :versioning
which you now can depend on.
Continuing:
PRODUCT = 'Topshelf'
CLR_TOOLS_VERSION = 'v4.0.30319'
OUTPUT_PATH = 'bin/Release'
These ruby constants are better defined in the appspecs; with a loop to avoid repetitions (after all all rake files are pure ruby with some helper methods thrown into the global scope). And output path is better specified in the XXproj files of your project, having more semantic sounding environment variables defined with build.prop 'CI_SIGN_AUTHENTICODE', '\\share\corp.pfx'
or similar - letting the individual project files handle the output. This is especially important as MsBuild/XBuild copies the DLLs around from the original output folder, and hence it is very hard to get the full copying + signing + modifying + ilmerging + whatever done after the project has finished building. Have a look at how latest Logary handles signing with Authenticode to get an idea of this.
props = {
:src => File.expand_path("src"),
:nuget => File.join(File.expand_path("src"), ".nuget", "nuget.exe"),
:output => File.expand_path("build_output"),
:artifacts => File.expand_path("build_artifacts"),
:lib => File.expand_path("lib"),
:projects => ["Topshelf"],
:keyfile => File.expand_path("Topshelf.snk")
}
Having 'global vars collected in a hash map' is something I played with initially, because it 'feels good to have it in a single place' - but again, the cohesion you lose isn't worth it, and you can avoid repeating yourself through looping and the new .appspec
and reading of XXproj file support instead.
Note how we now have two confusing concepts of output path. In general, you need to keep your outputs immutable; this means that you should absolutely not build everything into a single folder through MsBuild, but rather, after the execution has finished, move everything once!, no overwriting of identical files, to its final resting place. While Albacore doesn't provide similar functionality as Haskell's Shake, it helps thinking about files as immutable and writing code to the same extent.
The keyfile should be specified in the XXproj files, not in this has map, unless there are automated facilities in place for replacing that keyfile during build.
The next:
desc "Cleans, compiles, il-merges, unit tests, prepares examples, packages zip"
task :all => [:default, :package]
desc "**Default**, compiles and runs tests"
task :default => [:clean, :nuget_restore, :compile, :package]
is identical in v2. So far we have:
Rakefile:
require 'bundler/setup'
require 'albacore'
require 'albacore/tasks/versionizer'
Albacore::Tasks::Versionizer.new :versioning
Gemfile:
source 'https://rubygems.org
gem 'albacore', '~> 2.0.0'
Next follows a solution version file, but before that, let's have a look at the project files. All of them contain this crap that doesn't work cross-platform:
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<PlatformTarget>x86</PlatformTarget>
@@ -83,12 +81,4 @@
<Folder Include="Properties\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
Remove anything nuget, and also <SolutionDir ...
and <RestorePackages...
, because we're going to do that in a better way with albacore:
desc 'restore all nugets as per the packages.config files'
nugets_restore :restore do |p|
p.out = 'src/packages'
p.exe = 'tools/NuGet.exe'
end
And then that's done for all projects.
Now, the solution version file:
desc "Update the common version information for the build. You can call this task without building."
assemblyinfo :global_version do |asm|
# Assembly file config
asm.product_name = PRODUCT
asm.description = "Topshelf is an open source project for hosting services without friction. By referencing Topshelf, your console application *becomes* a service installer with a comprehensive set of command-line options for installing, configuring, and running your application as a service."
asm.version = FORMAL_VERSION
asm.file_version = FORMAL_VERSION
asm.custom_attributes :AssemblyInformationalVersion => "#{BUILD_VERSION}",
:ComVisibleAttribute => false,
:CLSCompliantAttribute => true
asm.copyright = COPYRIGHT
asm.output_file = 'src/SolutionVersion.cs'
asm.namespaces "System", "System.Reflection", "System.Runtime.InteropServices"
end
We'll ignore the asmver_files
task type, and use the equivalent, asmver
task type:
asmver :asmver => :versioning do |a|
a.file_path = 'src/SolutionVersion.cs'
a.namespace = 'Topshelf'
a.attributes assembly_title: 'Topshelf',
assembly_version: ENV['LONG_VERSION'],
assembly_file_version: ENV['LONG_VERSION'],
assembly_informational_version: ENV['BUILD_VERSION'],
assembly_copyright: "Copyright #{Time.now.year} Chris Patterson, Dru Sellers, Travis Smith, All rights reserved.",
assembly_description: 'Topshelf is an open source project for hosting services without friction. By referencing Topshelf, your console application *becomes* a service installer with a comprehensive set of command-line options for installing, configuring, and running your application as a service.',
com_visible: false
end
As you can see I haven't written the CLSCompliant attribute as I have never in my 11 years of doing .Net seen a use-case for it and different languages routinely stamp all over its semantics - the virtual machine is the definition of what's compliant and what's not; there are two of them: .Net 4.5 (CLR 4.special) and Mono 3.x with now and they are the yardsticks.
TBD