require 'ivy4r'
module Buildr
module Ivy
# TODO extend extension to download ivy stuff with dependencies automatically
# VERSION = '2.1.0-rc1'
class << self
def setting(*keys)
setting = Buildr.settings.build['ivy']
keys.each { |key| setting = setting[key] unless setting.nil? }
setting
end
# def version
# setting['version'] || VERSION
# end
# def dependencies
# @dependencies ||= [
# "org.apache.ivy:ivy:jar:#{version}",
# 'com.jcraft:jsch:jar:1.41',
# 'oro:oro:jar:2.08'
# ]
# end
end
class IvyConfig
attr_accessor :extension_dir, :resolved
# Store the current project and initialize ivy ant wrapper
def initialize(project)
@project = project
if project.parent.nil?
@extension_dir = @project.base_dir
else
@extension_dir = @project.parent.ivy.extension_dir
@base_ivy = @project.parent.ivy unless own_file?
end
end
def enabled?
@enabled ||= Ivy.setting('enabled') || true
end
def own_file?
@own_file ||= File.exists?(@project.path_to(file))
end
# Returns the correct ant instance to use, if project has its own ivy file uses the ivy file
# of project, if not uses the ivy file of parent project.
def ant
unless @ant
if own_file?
@ant = ::Ivy4r.new(@project.ant('ivy'))
@ant.lib_dir = lib_dir if lib_dir
@ant.project_dir = @extension_dir
else
@ant = @project.parent.ivy.ant
end
end
@ant
end
# Returns name of the project the ivy file belongs to.
def file_project
own_file? ? @project : @base_ivy.file_project
end
# Returns the artifacts for given configurations as array
def deps(*confs)
configure
pathid = "ivy.deps." + confs.join('.')
ant.cachepath :conf => confs.join(','), :pathid => pathid
end
# Returns ivy info for configured ivy file using a new ant instance.
def info
if @base_ivy
@base_ivy.info
else
ant.settings :id => 'ivy.info.settingsref'
result = ant.info :file => file, :settingsRef => 'ivy.info.settingsref'
@ant = nil
result
end
end
# Configures the ivy instance with additional properties and loading the settings file if it was provided
def configure
if @base_ivy
@base_ivy.configure
else
unless @configured
ant.property['ivy.status'] = status
ant.property['ivy.home'] = home
properties.each {|key, value| ant.property[key.to_s] = value }
@configured = ant.settings :file => settings if settings
end
end
end
# Resolves the configured file once.
def resolve
if @base_ivy
@base_ivy.resolve
else
unless @resolved
@resolved = ant.resolve :file => file
end
end
end
# Returns the additional infos for the manifest file.
def manifest
if @base_ivy
@base_ivy.manifest
else
{
'organisation' => @resolved['ivy.organisation'],
'module' => @resolved['ivy.organisation'],
'revision' => revision
}
end
end
# Creates the standard ivy dependency report
def report
ant.report :todir => report_dir
end
# Publishs the project as defined in ivy file if it has not been published already
def publish
if @base_ivy
@base_ivy.publish
else
unless @published
options = {:status => status, :pubrevision => revision, :artifactspattern => "#{publish_from}/[artifact].[ext]"}
options = publish_options * options
ant.publish options
@published = true
end
end
end
def home
@ivy_home_dir ||= Ivy.setting('home.dir') || "#{@extension_dir}/ivy-home"
end
def lib_dir
@lib_dir ||= Ivy.setting('lib.dir')
end
def settings
@settings ||= Ivy.setting('settings.file') || "#{@extension_dir}/ant-scripts/ivysettings.xml"
end
def file
@ivy_file ||= Ivy.setting('ivy.file') || 'ivy.xml'
end
# Sets the revision to use for the project, this is useful for development revisions that
# have an appended timestamp or any other dynamic revisioning.
#
# To set a different revision this method can be used in different ways.
# 1. project.ivy.revision(revision) to set the revision directly
# 2. project.ivy.revision { |ivy| [calculate revision] } use the block for dynamic
# calculation of the revision. You can access ivy4r via ivy.ant.[method]
def revision(*revision, &block)
raise "Invalid call with parameters and block!" if revision.size > 0 && block
if revision.empty? && block.nil?
if @revision_calc
@revision ||= @revision_calc.call(self)
else
@revision ||= @project.version
end
elsif block.nil?
raise "revision value invalid #{revision.join(', ')}" unless revision.size == 1
@revision = revision[0]
self
else
@revision_calc = block
self
end
end
# Sets the status to use for the project, this is useful for custom status handling
# like integration, alpha, gold.
#
# To set a different status this method can be used in different ways.
# 1. project.ivy.status(status) to set the status directly
# 2. project.ivy.status { |ivy| [calculate status] } use the block for dynamic
# calculation of the status. You can access ivy4r via ivy.ant.[method]
def status(*status, &block)
raise "Invalid call with parameters and block!" if status.size > 0 && block
if status.empty? && block.nil?
if @status_calc
@status ||= @status_calc.call(self)
else
@status ||= Ivy.setting('status') || 'integration'
end
elsif status.empty? && block.nil?
raise "status value invalid #{status.join(', ')}" unless status.size == 1
@status = status[0]
self
else
@status_calc = block
self
end
end
# Sets the publish options to use for the project. The options are merged with the default
# options including value set via #publish_from and overwrite all of them.
#
# To set the options this method can be used in different ways.
# 1. project.ivy.publish_options(options) to set the options directly
# 2. project.ivy.publish_options { |ivy| [calculate options] } use the block for dynamic
# calculation of options. You can access ivy4r via ivy.ant.[method]
def publish_options(*options, &block)
raise "Invalid call with parameters and block!" if options.size > 0 && block
if options.empty? && block.nil?
if @publish_options_calc
@publish_options ||= @publish_options_calc.call(self)
else
@publish_options ||= Ivy.setting('publish.options')
end
else
raise "Could not set 'publish_options' for '#{@project.name}' without own ivy file!" unless own_file?
if options.size > 0 && block.nil?
raise "publish options value invalid #{options.join(', ')}" unless options.size == 1
@publish_options = options[0]
self
else
@publish_options_calc = block
self
end
end
end
# Sets the additional properties for the ivy process use a Hash with the properties to set.
def properties(*properties)
if properties.empty?
@properties ||= {}
else
raise "properties value invalid #{properties.join(', ')}" unless properties.size == 1
@properties = properties[0]
self
end
end
# Sets the local repository for ivy files
def local_repository(*local_repository)
if local_repository.empty?
if own_file?
@local_repository ||= Ivy.setting('local.repository.dir') || "#{home}/repository"
else
@project.parent.ivy.local_repository
end
else
raise "Could not set 'local_repository' for '#{@project.name}' without own ivy file!" unless own_file?
raise "local_repository value invalid #{local_repository.join(', ')}" unless local_repository.size == 1
@local_repository = local_repository[0]
self
end
end
# Maps a package to a different name for publishing. This name is used instead of the default name
# for publishing use a hash with the +package+ as key and the newly mapped name as value. I.e.
# ivy.name(package(:jar) => 'new_name_without_version_number.jar')
# Note that this method is additive, a second call adds the names to the first.
def name(*name_mappings)
if name_mappings.empty?
@name_mappings ||= {}
else
raise "name_mappings value invalid #{name_mappings.join(', ')}" unless name_mappings.size == 1
@name_mappings = @name_mappings ? @name_mappings + name_mappings[0] : name_mappings[0].dup
self
end
end
# Sets the directory to publish artifacts from.
def publish_from(*publish_dir)
if publish_dir.empty?
if own_file?
@publish_from ||= Ivy.setting('publish.from') || @project.path_to(:target)
else
@project.parent.ivy.publish_from
end
else
raise "Could not set 'publish_from' for '#{@project.name}' without own ivy file!" unless own_file?
raise "publish_from value invalid #{publish_dir.join(', ')}" unless publish_dir.size == 1
@publish_from = publish_dir[0]
self
end
end
# Sets the directory to create dependency reports in.
def report_dir(*report_dir)
if report_dir.empty?
if own_file?
@report_dir ||= Ivy.setting('report.dir') || @project.path_to(:reports, 'ivy')
else
@project.parent.ivy.report_dir
end
else
raise "Could not set 'report_dir' for '#{@project.name}' without own ivy file!" unless own_file?
raise "publish_from value invalid #{report_dir.join(', ')}" unless report_dir.size == 1
@report_dir = report_dir[0]
self
end
end
# Set the configuration artifacts to use in package tasks like +:war+ or +:ear+.
# project.ivy.package_conf('server', 'client')
# or
# project.ivy.package_conf(['server', 'client'])
def package_conf(*package_conf)
if package_conf.empty?
@package_conf ||= [Ivy.setting('package.conf') || 'prod'].flatten.uniq
else
@package_conf = [package_conf].flatten.uniq
self
end
end
# Sets the includes pattern(s) to use for packages. I.e.
# project.ivy.package_include(/\.jar/, /\.gz/)
def package_include(*includes)
if includes.empty?
@package_include ||= [Ivy.setting('package.include') || /\.jar/].flatten.uniq
else
@package_include = [includes].flatten.uniq
self
end
end
# Sets the exclude pattern(s) to use for packages. I.e.
# project.ivy.package_exclude(/\.zip/, /\.tar/)
def package_exclude(*excludes)
if excludes.empty?
@package_exclude ||= [Ivy.setting('package.exlude') || ''].flatten.uniq
else
@package_exclude = [excludes].flatten.uniq
self
end
end
# Set the configuration artifacts to use for compile tasks, added to compile.with
# project.ivy.compile_conf('server', 'client')
def compile_conf(*compile_conf)
if compile_conf.empty?
@compile_conf ||= [Ivy.setting('compile.conf') || 'compile'].flatten.uniq
else
@compile_conf = [compile_conf].flatten.uniq
self
end
end
# Set the configuration artifacts to use for test tasks, added to test.compile.with
# and test.with. Note that all artifacts from #compile_conf are added automatically.
# project.ivy.test_conf('server', 'test')
def test_conf(*test_conf)
if test_conf.empty?
@test_conf ||= [Ivy.setting('test.conf') || 'test'].flatten.uniq
else
@test_conf = [test_conf].flatten.uniq
self
end
end
end
=begin rdoc
The Ivy Buildr extension adding the new tasks for ivy.
To use ivy in a +buildfile+ do something like:
ENV['BUILDR_EXT_DIR'] ||= '../Ivy'
require 'buildr/ivy_extension'
define 'ivy_project' do
[...]
ivy.compile_conf('compile').test_conf('test').package_conf('prod', 'server')
[...]
end
- This will add the +compile+ configuration to compile and test tasks
- Add the +test+ configuration to test compilation and execution
- include the artifacts from +prod+ and +server+ to any generated war or ear
- The ENV variable is needed to automatically configure the load path for ivy libs.
It assumes that you have the following dir structure [BUILDR_EXT_DIR]/ivy-home/jars
For more configuration options see IvyConfig.
=end
module IvyExtension
include Buildr::Extension
class << self
def add_ivy_deps_to_java_tasks(project)
resolve_target = project.ivy.file_project.task('ivy:resolve')
project.task :compiledeps => resolve_target do
compile_conf = [project.ivy.compile_conf].flatten
project.compile.with project.ivy.deps(compile_conf)
info "Ivy adding compile dependencies '#{compile_conf.join(', ')}' to project '#{project.name}'"
end
project.task :compile => "#{project.name}:compiledeps"
project.task :testdeps => resolve_target do
confs = [project.ivy.test_conf, project.ivy.compile_conf].flatten.uniq
project.test.with project.ivy.deps(confs)
info "Ivy adding test dependencies '#{confs.join(', ')}' to project '#{project.name}'"
end
project.task "test:compile" => "#{project.name}:testdeps"
project.task :javadocdeps => resolve_target do
confs = [project.ivy.test_conf, project.ivy.compile_conf].flatten.uniq
project.javadoc.with project.ivy.deps(confs)
info "Ivy adding javadoc dependencies '#{confs.join(', ')}' to project '#{project.name}'"
end
project.task :javadoc => "#{project.name}:javadocdeps"
end
def add_manifest_to_distributeables(project)
pkgs = project.packages.find_all { |pkg| [:jar, :war, :ear].member? pkg.type }
pkgs.each do |pkg|
name = "#{pkg.name}manifest"
task = project.task name => project.ivy.file_project.task('ivy:resolve') do
pkg.with :manifest => project.manifest.merge(project.ivy.manifest)
info "Adding manifest entries to package '#{pkg.name}'"
end
project.task :build => task
end
end
def add_prod_libs_to_distributeables(project)
includes = project.ivy.package_include
excludes = project.ivy.package_exclude
pkgs = project.packages.find_all { |pkg| [:war, :ear].member? pkg.type }
pkgs.each do |pkg|
name = "#{pkg.name}deps"
task = project.task name => project.ivy.file_project.task('ivy:resolve') do
confs = project.ivy.package_conf
libs = project.ivy.deps(confs).find_all do |lib|
lib = File.basename(lib)
use = includes.any? {|include| include === lib } && !excludes.any? {|exclude| exclude === lib}
verbose "Including '#{lib}' from package '#{pkg}' in project '#{project.name}'" if use
info "Excluding '#{lib}' from package '#{pkg}' in project '#{project.name}'" unless use
use
end
pkg.with :libs => libs
info "Adding production libs from conf '#{confs.join(', ')}' to package '#{pkg.name}' in project '#{project.name}'"
end
project.task :build => task
end
end
def add_copy_tasks_for_publish(project)
if project.ivy.own_file?
Buildr.projects.each do |current|
current.packages.each do |pkg|
target_file = current.ivy.name[pkg] || File.basename(pkg.name).gsub(/-#{project.version}/, '')
taskname = current.path_to(project.ivy.publish_from, target_file)
if taskname != pkg.name
project.file taskname => pkg.name do
verbose "Ivy copying '#{pkg.name}' to '#{taskname}' for publishing"
FileUtils.mkdir File.dirname(taskname) unless File.directory?(File.dirname(taskname))
FileUtils.cp pkg.name, taskname
end
end
project.task 'ivy:publish' => taskname
end
end
end
end
end
# Returns the +ivy+ configuration for the project. Use this to configure Ivy.
# see IvyConfig for more details about configuration options.
def ivy
@ivy_config ||= IvyConfig.new(self)
end
first_time do
namespace 'ivy' do
desc 'Resolves the ivy dependencies'
task :resolve
desc 'Publish the artifacts to ivy repository as defined by environment'
task :publish
desc 'Creates a dependency report for the project'
task :report
desc 'Clean the local Ivy cache and the local ivy repository'
task :clean
end
end
before_define do |project|
if project.parent.nil? && project.ivy.enabled?
info = project.ivy.info
project.version = info['ivy.revision']
project.group = "#{info['ivy.organisation']}.#{info['ivy.module']}"
end
end
after_define do |project|
if project.ivy.enabled?
IvyExtension.add_ivy_deps_to_java_tasks(project)
IvyExtension.add_manifest_to_distributeables(project)
IvyExtension.add_prod_libs_to_distributeables(project)
IvyExtension.add_copy_tasks_for_publish(project)
task :clean do
# TODO This is redundant, refactor ivy_ant_wrap and this to use a single config object
info "Cleaning ivy reports"
rm_rf project.path_to(:reports, 'ivy')
end
namespace 'ivy' do
task :configure do
project.ivy.configure
end
task :resolve => "#{project.name}:ivy:configure" do
project.ivy.resolve
end
task :report => "#{project.name}:ivy:resolve" do
project.ivy.report
end
task :publish => "#{project.name}:ivy:resolve" do
project.ivy.publish
end
end
end
end
end
# Global targets that are not bound to a project
namespace 'ivy' do
task :clean do
info "Cleaning local ivy cache"
Buildr.projects.find_all{ |p| p.ivy.own_file? }.each do |project|
project.ivy.ant.clean
end
end
task :resolve do
info "Resolving all distinct ivy files"
Buildr.projects.find_all{ |p| p.ivy.own_file? }.each do |project|
project.task('ivy:resolve').invoke
end
end
task :publish => :package do
info "Publishing all distinct ivy files"
Buildr.projects.find_all{ |p| p.ivy.own_file? }.each do |project|
project.task('ivy:publish').invoke
end
end
end
class Buildr::Project # :nodoc:
include IvyExtension
end
end
end