# encoding: utf-8
require 'rake'
require 'nokogiri'
require 'fileutils'
require 'albacore'
require 'albacore/paths'
require 'albacore/cmd_config'
require 'albacore/config_dsl'
require 'albacore/cross_platform_cmd'
require 'albacore/project'
require 'albacore/logging'
require 'albacore/nuget_model'
module Albacore
module NugetsPack
# the nuget command
class Cmd
include CrossPlatformCmd
# executable => the nuget executable
def initialize executable, *args
opts = Map.options args
raise ArgumentError, 'out is nil' if opts.getopt(:out).nil?
@work_dir = opts.getopt :work_dir, default: nil
@executable = executable
@parameters = [%W{Pack -OutputDirectory #{opts.get(:out)}}].flatten
@opts = opts
mono_command
end
# run nuget on the nuspec to create a new package
# returns: a tuple-array of the package and the symbol package
# of which the symbol package is nil if it was not generated
def execute nuspec_file, nuspec_symbols_file = nil
debug "NugetsPack::Cmd#execute, opts: #{@opts} [nugets pack: cmd]"
original_pars = @parameters.dup
pars = original_pars.dup
pars << nuspec_file
pkg = get_nuget_path_of do
system @executable, pars, :work_dir => @work_dir
end
debug "package at '#{pkg}'"
# if the symbols flag is set and there's a symbols file specified
# then run NuGet.exe to generate the .symbols.nupkg file
if nuspec_symbols_file
pars = original_pars.dup
pars << '-Symbols'
pars << nuspec_symbols_file
spkg = with_subterfuge pkg do
get_nuget_path_of do
system @executable, pars, :work_dir => @work_dir
end
end
debug "symbol package at '#{spkg}'"
[pkg, spkg]
else
info "symbols not configured for generation, use Config#gen_symbols to do so [nugets pack: cmd]"
[pkg, nil]
end
end
private
# regexpes the package path from the output
def get_nuget_path_of
out = yield
out.match /Successfully created package '([:\s\p{Word}\\\/\d\.\-]+\.symbols\.nupkg)'./iu if out.respond_to? :match
trace "Got symbols return value: '#{out}', matched: '#{$1}'" if $1
return $1 if $1
out.match /Successfully created package '([:\s\p{Word}\\\/\d\.\-]+\.nupkg)'./iu if out.respond_to? :match
trace "Got NOT-symbols return value: '#{out}', matched: '#{$1}'"
unless $1
args = ARGV.inject("") { |state, arg| state + " " + '"' + arg + '"' }
warn do
%{Couldn't match package, please run
bundle exec rake DEBUG=true #{args} --trace
and report a bug to albacore with the full output. Here's the nuget process output:
--- START OUTPUT ---
#{out}
--- END OUTPUT ---
}
end
end
$1
end
# hide the original like a ninja while NuGet whimpers in a corner
def with_subterfuge pkg
FileUtils.mv pkg, "#{pkg}.tmp" if pkg && File.exists?(pkg)
res = yield
FileUtils.mv "#{pkg}.tmp", pkg if pkg && File.exists?("#{pkg}.tmp")
res
end
end
# This tasktype allows you to quickly package project files to nuget
# packages.
#
# Point files to the project files, that should be in MsBuild XML.
#
# Examples
#
# nugets_pack :pack => ['build/pkg', :versioning] do |p|
# p.files = FileList['src/**/*.csproj']
# p.out = 'build/pkg'
# p.exe = 'buildsupport/NuGet.exe'
# p.with_metadata do |m|
# m.version = ENV['NUGET_VERSION']
# end
# p.gen_symbols
# p.no_project_dependencies
# end
class Config
include CmdConfig
self.extend ConfigDSL
# the output directory to place the newfangled nugets in
attr_path :out
# the .net target (e.g. net40, mono20, mono3, etc)
attr_writer :target
# sets the files to search
attr_writer :files
# sets the MsBuild configuration that is used to produce the output into
# ...
attr_writer :configuration
def initialize
@package = Albacore::NugetModel::Package.new
@target = 'net40'
@symbols = false
@project_dependencies = true
@nuget_dependencies = true
@leave_nuspec = false
fill_required
end
def with_metadata &block
yield @package.metadata
end
# configure the package with a block
def with_package &block
yield @package
end
# generate symbols for the nugets: just call this method to
# enable generation
def gen_symbols
@symbols = true
end
# leave the nuspec behind, don't delete it after generating it
#
def leave_nuspec
@leave_nuspec = true
end
# call this if you want to cancel 'smart' scanning of the *proj
# file for its dependencies
def no_project_dependencies
@project_dependencies = false
end
# call this if you want to cancel inclusion of package dependencies
# when creating the nuspec file
def no_nuget_dependencies
@nuget_dependencies = false
end
# gets the options specified for the task, used from the task
def opts
files = @files.respond_to?(:each) ? @files : [@files]
[:authors, :description, :version].each do |required|
warn "metadata##{required} is missing from nugets_pack [nugets pack: config]" if @package.metadata.send(required) == 'MISSING'
end
Map.new({
:out => @out,
:exe => @exe,
:symbols => @symbols,
:package => @package,
:target => @target,
:files => @files,
:configuration => @configuration,
:project_dependencies => @project_dependencies,
:nuget_dependencies => @nuget_dependencies,
:original_path => FileUtils.pwd,
:leave_nuspec => @leave_nuspec
})
end
private
def fill_required
# see http://docs.nuget.org/docs/reference/nuspec-reference
with_metadata do |m|
m.authors = m.description = m.version = 'MISSING'
end
end
end
# a task that handles the generation of nugets from projects or nuspecs.
class ProjectTask
include Logging
def initialize opts, &before_execute
raise ArgumentError, 'opts is not a map' unless opts.is_a? Map
raise ArgumentError, 'no files given' unless opts.get(:files).length > 0
@opts = opts.apply :out => '.'
@files = opts.get :files
@before_execute = before_execute
end
def execute
knowns = compute_knowns
@files.each do |p|
proj, n, ns = generate_nuspec p, knowns
execute_inner! proj, n, ns
end
end
def path_to relative_file_path, cwd
File.expand_path( File.join(@opts.get(:original_path), relative_file_path), cwd )
end
# generate all nuspecs
def generate_nuspecs
nuspecs = {}
knowns = compute_knowns
@files.each do |p|
proj, n, ns = generate_nuspec p, knowns
nuspecs[proj.name] = OpenStruct.new({:proj => proj, :nuspec => n, :nuspec_symbols => ns })
end
nuspecs
end
private
# Computes the known *Ids* of the projects; as Name = Title when creating nugets.
def compute_knowns
Set.new(@files.map { |f| Albacore::Project.new f }.map { |p| p.id })
end
def generate_nuspec p, knowns
proj = Albacore::Project.new p
nuspec, nuspec_symbols = create_nuspec proj, knowns
[proj, nuspec, nuspec_symbols]
end
# execute, for each project file
def execute_inner! proj, nuspec, nuspec_symbols
nuspec_path = write_nuspec! proj, nuspec, false
nuspec_symbols_path = write_nuspec! proj, nuspec_symbols, true if nuspec_symbols
create_nuget! proj.proj_path_base, nuspec_path, nuspec_symbols_path
rescue => e
err (e.inspect)
raise $!
ensure
trace do
%{
PROJECT #{proj.name} nuspec:
#{nuspec.to_xml}
PROJECT #{proj.name} symbol nuspec:
#{if nuspec_symbols then nuspec_symbols.to_xml else 'NO SYMBOLS' end}}
end
# now remove them all
[nuspec_path, nuspec_symbols_path].each{|n| cleanup_nuspec n}
end
## Creating
def create_nuspec proj, knowns
version = @opts.get(:package).metadata.version
project_dependencies = @opts.get(:project_dependencies, true)
nuget_dependencies = @opts.get(:nuget_dependencies, true)
target = @opts.get :target
trace "creating NON-SYMBOL package for '#{proj.name}', targeting '#{target}' [nugets pack: task]"
nuspec = Albacore::NugetModel::Package.from_xxproj proj,
symbols: false,
verify_files: true,
dotnet_version: target,
known_projects: knowns,
version: version,
configuration: (@opts.get(:configuration)),
project_dependencies: project_dependencies,
nuget_dependencies: nuget_dependencies
# take data from package as configured in Rakefile, choosing what is in
# Rakefile over what is in projfile.
nuspec = nuspec.merge_with @opts.get(:package)
trace { "nuspec: #{nuspec.to_s} [nugets pack: task]" }
if @opts.get(:symbols)
trace { "creating SYMBOL package for '#{proj.name}' [nugets pack: task]" }
nuspec_symbols = Albacore::NugetModel::Package.from_xxproj proj,
symbols: true,
verify_files: true,
dotnet_version: target,
known_projects: knowns,
version: version,
configuration: (@opts.get(:configuration)),
project_dependencies: project_dependencies,
nuget_dependencies: nuget_dependencies
nuspec_symbols = nuspec_symbols.merge_with @opts.get(:package)
trace { "nuspec symbols: #{nuspec_symbols.to_s} [nugets pack: task]" }
[nuspec, nuspec_symbols]
else
trace { "skipping SYMBOL package for #{proj.name} [nugets pack: task]" }
[nuspec, nil]
end
end
def write_nuspec! proj, nuspec, symbols
raise ArgumentError, "no nuspect metadata id, project at path: #{proj.proj_path_base}, nuspec: #{nuspec.inspect}" unless nuspec.metadata.id
nuspec_path = File.join(proj.proj_path_base, nuspec.metadata.id + "#{ symbols ? '.symbols' : '' }.nuspec")
File.write(nuspec_path, nuspec.to_xml)
nuspec_path
end
def create_nuget! cwd, nuspec, nuspec_symbols = nil
# create the command
exe = path_to(@opts.get(:exe), cwd)
out = path_to(@opts.get(:out), cwd)
nuspec = path_to nuspec, cwd
nuspec_symbols = path_to nuspec_symbols, cwd if nuspec_symbols
cmd = Albacore::NugetsPack::Cmd.new exe,
work_dir: cwd,
out: out
# run any concerns that modify the command
@before_execute.call cmd if @before_execute
debug { "generating nuspec at #{nuspec}, and symbols (possibly) at '#{nuspec_symbols}' [nugets pack: task]" }
# run the command for the file
pkg, spkg = cmd.execute nuspec, nuspec_symbols
publish_artifact nuspec, pkg
publish_artifact nuspec_symbols, spkg if spkg && nuspec_symbols
end
## Cleaning up after generation
def cleanup_nuspec nuspec
return if nuspec.nil? or not File.exists? nuspec
return if @opts.get :leave_nuspec, false
File.delete nuspec
end
def publish_artifact nuspec, nuget
Albacore.publish :artifact, OpenStruct.new(
:nuspec => nuspec,
:nupkg => nuget,
:location => nuget
)
end
def self.accept? f
File.extname(f).downcase != '.nuspec'
end
end
# generate a nuget from a nuspec
class NuspecTask
include Logging
def initialize command_line, config, nuspec
@config = config
@nuspec = nuspec
# is a NuspecPack::Cmd
@command_line = command_line
end
def read_version_from_nuspec
begin
nuspec_file = File.open(@nuspec)
xml = Nokogiri::XML(nuspec_file)
nuspec_file.close
nodes = xml.xpath('.//metadata/version')
raise "No found" if nodes.empty?
nodes.first.text()
rescue => error
err "Error reading package version from file: #{error}"
raise
end
end
def execute
version = read_version_from_nuspec
filename = File.basename(@nuspec, File.extname(@nuspec))
@command_line.execute @nuspec
path = File.join(@config.opts.get(:out), "#{filename}.#{version}.nupkg")
Albacore.publish :artifact, OpenStruct.new(
:nuspec => @nuspec,
:nupkg => path,
:location => path
)
end
def self.accept? file
File.extname(file).downcase == '.nuspec'
end
end
end
end