blob: 4b8894c53dbdad1e981f18927d9cffe34219c466 [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with this
# work for additional information regarding copyright ownership. The ASF
# licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
module Buildr #:nodoc:
desc 'Download all artifacts'
task 'artifacts'
desc "Download all artifacts' sources"
task 'artifacts:sources'
desc "Download all artifacts' external annotations"
task 'artifacts:annotations'
desc "Download all artifacts' javadoc"
task 'artifacts:javadoc'
# Mixin with a task to make it behave like an artifact. Implemented by the packaging tasks.
#
# An artifact has an identifier, group identifier, type, version number and
# optional classifier. All can be used to locate it in the local repository,
# download from or upload to a remote repository.
#
# The #to_spec and #to_hash methods allow it to be used everywhere an artifact is
# accepted.
module ActsAsArtifact
ARTIFACT_ATTRIBUTES = [:group, :id, :type, :classifier, :version]
MAVEN_METADATA = 'maven-metadata.xml'
class << self
private
# :stopdoc:
def included(mod)
mod.extend self
end
def extend_object(base)
base.instance_eval { alias :install_old :install } if base.respond_to? :install
base.instance_eval { alias :uninstall_old :uninstall } if base.respond_to? :uninstall
base.instance_eval { alias :upload_old :upload } if base.respond_to? :upload
super
end
def extended(base)
#We try to keep the previous instance methods defined on the base instance if there were ones.
base.instance_eval { alias :install :install_old } if base.respond_to? :install_old
base.instance_eval { alias :uninstall :uninstall_old } if base.respond_to? :uninstall_old
base.instance_eval { alias :upload :upload_old } if base.respond_to? :upload_old
end
# :startdoc:
end
# The artifact identifier.
attr_reader :id
# The group identifier.
attr_reader :group
# The file type. (Symbol)
attr_reader :type
# The version number.
attr_reader :version
# Optional artifact classifier.
attr_reader :classifier
attr_accessor :buildr_project
def snapshot?
version =~ /-SNAPSHOT$/
end
def final_version
return version unless snapshot?
Time.now.strftime("%Y%m%d.%H%M%S")
end
# :call-seq:
# to_spec_hash => Hash
#
# Returns the artifact specification as a hash. For example:
# com.example:app:jar:1.2
# becomes:
# { :group=>'com.example',
# :id=>'app',
# :type=>:jar,
# :version=>'1.2' }
def to_spec_hash
base = { :group=>group, :id=>id, :type=>type, :version=>version }
classifier ? base.merge(:classifier=>classifier) : base
end
alias_method :to_hash, :to_spec_hash
# :call-seq:
# to_spec => String
#
# Returns the artifact specification, in the structure:
# <group>:<artifact>:<type>:<version>
# or
# <group>:<artifact>:<type>:<classifier>:<version>
def to_spec
classifier ? "#{group}:#{id}:#{type}:#{classifier}:#{version}" : "#{group}:#{id}:#{type}:#{version}"
end
# :call-seq:
# pom => Artifact
#
# Convenience method that returns a POM artifact.
def pom
return self if type == :pom
Buildr.artifact(:group=>group, :id=>id, :version=>version, :type=>:pom)
end
# :call-seq:
# sources_artifact => Artifact
#
# Convenience method that returns a sources artifact.
def sources_artifact
sources_spec = to_spec_hash.merge(:classifier=>'sources')
sources_task = OptionalArtifact.define_task(Buildr.repositories.locate(sources_spec))
sources_task.send :apply_spec, sources_spec
sources_task
end
# :call-seq:
# javadoc_artifact => Artifact
#
# Convenience method that returns the associated javadoc artifact
def javadoc_artifact
javadoc_spec = to_spec_hash.merge(:classifier=>'javadoc')
javadoc_task = OptionalArtifact.define_task(Buildr.repositories.locate(javadoc_spec))
javadoc_task.send :apply_spec, javadoc_spec
javadoc_task
end
# :call-seq:
# annotations_artifact => Artifact
#
# Convenience method that returns an annotations artifact. The annotations artifact is used by
# Intellij IDEA as a source of external annotations.
def annotations_artifact
annotations_spec = to_spec_hash.merge(:classifier=>'annotations')
annotations_task = OptionalArtifact.define_task(Buildr.repositories.locate(annotations_spec))
annotations_task.send :apply_spec, annotations_spec
annotations_task
end
# :call-seq:
# pom_xml => string
#
# Creates POM XML for this artifact.
def pom_xml
if self.buildr_project
Buildr::CustomPom.pom_xml(self.buildr_project, self)
else
Proc.new do
xml = Builder::XmlMarkup.new(:indent => 2)
xml.instruct!
xml.project do
xml.modelVersion '4.0.0'
xml.groupId group
xml.artifactId id
xml.version version
xml.classifier classifier if classifier
end
end
end
end
# :call-seq:
# maven_metadata_xml => string
#
# Creates Maven Metadata XML content for this artifact.
def maven_metadata_xml
xml = Builder::XmlMarkup.new(:indent=>2)
xml.instruct!
xml.metadata do
xml.groupId group
xml.artifactId id
xml.version version
xml.versioning do
xml.snapshot do
xml.timestamp final_version
xml.buildNumber 1
end
xml.lastupdated Time.now.strftime("%Y%m%d%H%M%S")
end
end
end
def install
invoke
in_local_repository = Buildr.repositories.locate(self)
if pom && pom != self && classifier.nil?
pom.invoke
pom.install
end
if name != in_local_repository
mkpath File.dirname(in_local_repository)
cp name, in_local_repository, :preserve => false
info "Installed #{name} to #{in_local_repository}"
end
end
def uninstall
installed = Buildr.repositories.locate(self)
rm installed if File.exist?(installed)
pom.uninstall if pom && pom != self && classifier.nil?
end
# :call-seq:
# upload
# upload(url)
# upload(options)
#
# Uploads the artifact, its POM and digital signatures to remote server.
#
# In the first form, uses the upload options specified by repositories.release_to
# or repositories.snapshot_to if the subject is a snapshot artifact.
# In the second form, uses a URL that includes all the relevant information.
# In the third form, uses a hash with the options :url, :username, :password,
# and :permissions. All but :url are optional.
def upload(upload_to = nil)
upload_task(upload_to).invoke
end
def upload_task(upload_to = nil)
upload_to ||= Buildr.repositories.snapshot_to if snapshot? && Buildr.repositories.snapshot_to != nil && Buildr.repositories.snapshot_to[:url] != nil
upload_to ||= Buildr.repositories.release_to
upload_to = { :url=>upload_to } unless Hash === upload_to
raise ArgumentError, 'Don\'t know where to upload, perhaps you forgot to set repositories.release_to' unless upload_to[:url]
# Set the upload URI, including mandatory slash (we expect it to be the base directory).
# Username/password may be part of URI, or separate entities.
uri = URI.parse(upload_to[:url].clone)
uri.path = uri.path + '/' unless uri.path[-1] == '/'
to_escape = "!\"\#$%&'()*+,-./:;<=>?@{}|~`'"
uri.user = URI.encode(upload_to[:username], to_escape) if upload_to[:username]
uri.password = URI.encode(upload_to[:password], to_escape) if upload_to[:password]
path = group.gsub('.', '/') + "/#{id}/#{version}/#{upload_name}"
unless task = Buildr.application.lookup(uri+path)
deps = [self]
deps << pom.upload_task( upload_to ) if pom && pom != self && classifier.nil?
task = Rake::Task.define_task uri + path => deps do
# Upload artifact relative to base URL, need to create path before uploading.
options = upload_to[:options] || {:permissions => upload_to[:permissions]}
info "Deploying #{to_spec}"
URI.upload uri + path, name, options
if snapshot? && pom != self
maven_metadata = group.gsub('.', '/') + "/#{id}/#{version}/#{MAVEN_METADATA}"
URI.write uri + maven_metadata, maven_metadata_xml, :permissions => upload_to[:permissions]
end
end
end
task
end
protected
# Apply specification to this artifact.
def apply_spec(spec)
spec = Artifact.to_hash(spec)
ARTIFACT_ATTRIBUTES.each { |key| instance_variable_set("@#{key}", spec[key]) }
self
end
def group_path
group.gsub('.', '/')
end
def upload_name
return File.basename(name) unless snapshot?
return File.basename(name).gsub(/SNAPSHOT/, "#{final_version}-1")
end
def extract_type(type)
return :jar if type == :bundle
type
end
end
# A file task referencing an artifact in the local repository.
#
# This task includes all the artifact attributes (group, id, version, etc). It points
# to the artifact's path in the local repository. When invoked, it will download the
# artifact into the local repository if the artifact does not already exist.
#
# Note: You can enhance this task to create the artifact yourself, e.g. download it from
# a site that doesn't have a remote repository structure, copy it from a different disk, etc.
class Artifact < Rake::FileTask
# The default artifact type.
DEFAULT_TYPE = :jar
include ActsAsArtifact, Buildr
class << self
# :call-seq:
# lookup(spec) => Artifact
#
# Lookup a previously registered artifact task based on its specification (String or Hash).
def lookup(spec)
@artifacts ||= {}
@artifacts[to_spec(spec)]
end
# :call-seq:
# list => specs
#
# Returns an array of specs for all the registered artifacts. (Anything created from artifact, or package).
def list
@artifacts ||= {}
@artifacts.keys
end
# :call-seq:
# register(artifacts) => artifacts
#
# Register an artifact task(s) for later lookup (see #lookup).
def register(*tasks)
@artifacts ||= {}
fail 'You can only register an artifact task, one of the arguments is not a Task that responds to to_spec' unless
tasks.all? { |task| task.respond_to?(:to_spec) && task.respond_to?(:invoke) }
tasks.each { |task| @artifacts[task.to_spec] = task }
tasks
end
# :call-seq:
# to_hash(spec_hash) => spec_hash
# to_hash(spec_string) => spec_hash
# to_hash(artifact) => spec_hash
#
# Turn a spec into a hash. This method accepts a String, Hash or any object that responds to
# the method to_spec. There are several reasons to use this method:
# * You can pass anything that could possibly be a spec, and get a hash.
# * It will check that the spec includes the group identifier, artifact
# identifier and version number and set the file type, if missing.
# * It will always return a new specs hash.
def to_hash(spec)
if spec.respond_to?(:to_spec)
to_hash spec.to_spec
elsif Hash === spec
rake_check_options spec, :id, :group, :type, :classifier, :version, :scope
# Sanitize the hash and check it's valid.
spec = ARTIFACT_ATTRIBUTES.inject({}) { |h, k| h[k] = spec[k].to_s if spec[k] ; h }
fail "Missing group identifier for #{spec.inspect}" unless spec[:group]
fail "Missing artifact identifier for #{spec.inspect}" unless spec[:id]
fail "Missing version for #{spec.inspect}" unless spec[:version]
spec[:type] = (spec[:type] || DEFAULT_TYPE).to_sym
spec
elsif String === spec
group, id, type, version, *rest = spec.split(':').map { |part| part.empty? ? nil : part }
unless rest.empty?
# Optional classifier comes before version.
classifier, version = version, rest.shift
fail "Expecting <group:id:type:version> or <group:id:type:classifier:version>, found <#{spec}>" unless rest.empty?
end
to_hash :group=>group, :id=>id, :type=>type, :version=>version, :classifier=>classifier
else
fail 'Expecting a String, Hash or object that responds to to_spec'
end
end
# :call-seq:
# to_spec(spec_hash) => spec_string
#
# Convert a hash back to a spec string. This method accepts
# a string, hash or any object that responds to to_spec.
def to_spec(hash)
hash = to_hash(hash) unless Hash === hash
version = ":#{hash[:version]}" if hash[:version]
classifier = ":#{hash[:classifier]}" if hash[:classifier]
"#{hash[:group]}:#{hash[:id]}:#{hash[:type] || DEFAULT_TYPE}#{classifier}#{version}"
end
# :call-seq:
# hash_to_file_name(spec_hash) => file_name
#
# Convert a hash spec to a file name.
def hash_to_file_name(hash)
version = "-#{hash[:version]}" if hash[:version]
classifier = "-#{hash[:classifier]}" if hash[:classifier]
"#{hash[:id]}#{version}#{classifier}.#{extract_type(hash[:type]) || DEFAULT_TYPE}"
end
end
def initialize(*args) #:nodoc:
super
enhance do |task|
# Default behavior: download the artifact from one of the remote repositories
# if the file does not exist. But this default behavior is counter productive
# if the artifact knows how to build itself (e.g. download from a different location),
# so don't perform it if the task found a different way to create the artifact.
task.enhance do
if download_needed? task
info "Downloading #{to_spec}"
download
pom.invoke rescue nil if pom && pom != self && classifier.nil?
end
end
end
end
# :call-seq:
# from(path) => self
#
# Use this when you want to install or upload an artifact from a given file, for example:
# test = artifact('group:id:jar:1.0').from('test.jar')
# install test
# See also Buildr#install and Buildr#upload.
def from(path)
@from = path.is_a?(Rake::Task) ? path : File.expand_path(path.to_s)
enhance [@from] do
mkpath File.dirname(name)
cp @from.to_s, name
end
pom.content pom_xml unless pom == self || pom.has_content?
self
end
# :call-seq:
# content(string) => self
# content(Proc) => self
#
# Use this when you want to install or upload an artifact from a given content, for example:
# readme = artifact('com.example:readme:txt:1.0').content(<<-EOF
# Please visit our website at http://example.com/readme
# <<EOF
# install readme
#
# If the argument is a Proc the it will be called when the artifact is written out. If the result is not a proc
# and not a string, it will be converted to a string using to_s
def content(string = nil)
unless string
@content = @content.call if @content.is_a?(Proc)
return @content
end
unless @content
enhance do
write name, self.content
end
class << self
# Force overwriting target since we don't have source file
# to check for timestamp modification
def needed?
true
end
end
end
@content = string
pom.content pom_xml unless pom == self || pom.has_content?
self
end
protected
def has_content?
@from || @content
end
# :call-seq:
# download
#
# Downloads an artifact from one of the remote repositories, and stores it in the local
# repository. Raises an exception if the artifact is not found.
#
# This method attempts to download the artifact from each repository in the order in
# which they are returned from #remote, until successful.
def download
trace "Downloading #{to_spec}"
remote = Buildr.repositories.remote_uri
remote = remote.each { |repo_url| repo_url.path += '/' unless repo_url.path[-1] == '/' }
fail "Unable to download #{to_spec}. No remote repositories defined." if remote.empty?
exact_success = remote.find do |repo_url|
begin
path = "#{group_path}/#{id}/#{version}/#{File.basename(name)}"
download_artifact(repo_url + path)
true
rescue URI::NotFoundError
false
rescue Exception=>error
info error
trace error.backtrace.join("\n")
false
end
end
if exact_success
return
elsif snapshot?
download_m2_snapshot(remote)
else
fail_download(remote)
end
end
def download_m2_snapshot(remote_uris)
remote_uris.find do |repo_url|
snapshot_url = current_snapshot_repo_url(repo_url)
if snapshot_url
begin
download_artifact snapshot_url
true
rescue URI::NotFoundError
false
end
else
false
end
end or fail_download(remote_uris)
end
def current_snapshot_repo_url(repo_url)
begin
metadata_path = "#{group_path}/#{id}/#{version}/maven-metadata.xml"
metadata_xml = StringIO.new
URI.download repo_url + metadata_path, metadata_xml
metadata = REXML::Document.new(metadata_xml.string).root
timestamp = REXML::XPath.first(metadata, '//timestamp')
build_number = REXML::XPath.first(metadata, '//buildNumber')
error "No timestamp provided for the snapshot #{to_spec}" if timestamp.nil?
error "No build number provided for the snapshot #{to_spec}" if build_number.nil?
return nil if timestamp.nil? || build_number.nil?
snapshot_of = version[0, version.size - 9]
classifier_snippet = (classifier != nil) ? "-#{classifier}" : ""
repo_url + "#{group_path}/#{id}/#{version}/#{id}-#{snapshot_of}-#{timestamp.text}-#{build_number.text}#{classifier_snippet}.#{type}"
rescue URI::NotFoundError
nil
end
end
def fail_download(remote_uris)
fail "Failed to download #{to_spec}, tried the following repositories:\n#{remote_uris.join("\n")}"
end
protected
# :call-seq:
# needed?
#
# Validates whether artifact is required to be downloaded from repository
def needed?
return true if snapshot? && File.exist?(name) && (update_snapshot? || old?)
super
end
private
# :call-seq:
# download_artifact
#
# Downloads artifact from given repository,
# supports downloading snapshot artifact with relocation on succeed to local repository
def download_artifact(path)
download_file = "#{name}.#{Time.new.to_i}"
begin
URI.download path, download_file
if File.exist?(download_file)
FileUtils.mkdir_p(File.dirname(name))
FileUtils.mv(download_file, name)
end
ensure
File.delete(download_file) if File.exist?(download_file)
end
end
# :call-seq:
# :download_needed?
#
# Validates whether artifact is required to be downloaded from repository
def download_needed?(task)
return true if !File.exist?(name)
if snapshot?
return false if offline? && File.exist?(name)
return true if update_snapshot? || old?
end
return false
end
def update_snapshot?
Buildr.application.options.update_snapshots
end
def offline?
Buildr.application.options.work_offline
end
# :call-seq:
# old?
#
# Checks whether existing artifact is older than period from build settings or one day
def old?
settings = Buildr.application.settings
time_to_be_old = settings.user[:expire_time] || settings.build[:expire_time] || 60 * 60 * 24
File.mtime(name).to_i < (Time.new.to_i - time_to_be_old)
end
end
# An artifact that is optional.
# If downloading fails, the user will be informed but it will not raise an exception.
class OptionalArtifact < Artifact
protected
# If downloading fails, the user will be informed but it will not raise an exception.
def download
super
rescue
info "Failed to download #{to_spec}. Skipping it."
end
end
# Holds the path to the local repository, URLs for remote repositories, and settings for release server.
#
# You can access this object from the #repositories method. For example:
# puts repositories.local
# repositories.remote << 'http://example.com/repo'
# repositories.release_to = 'sftp://example.com/var/www/public/repo'
class Repositories
include Singleton
# :call-seq:
# local => path
#
# Returns the path to the local repository.
#
# The default path is .m2/repository relative to the home directory.
# You can set this using the M2_REPO environment variable or the repositories/local
# value in your settings.yaml file.
def local
@local ||= File.expand_path(ENV['M2_REPO'] || ENV['local_repo'] ||
(Buildr.settings.user['repositories'] && Buildr.settings.user['repositories']['local']) ||
File.join(ENV['HOME'], '.m2/repository'))
end
# :call-seq:
# local = path
#
# Sets the path to the local repository.
#
# The best place to set the local repository path is from a buildr.rb file
# located in the .buildr directory under your home directory. That way all
# your projects will share the same path, without affecting other developers
# collaborating on these projects.
def local=(dir)
@local = dir ? File.expand_path(dir) : nil
end
# :call-seq:
# locate(spec) => path
#
# Locates an artifact in the local repository based on its specification, and returns
# a file path.
#
# For example:
# locate :group=>'log4j', :id=>'log4j', :version=>'1.1'
# => ~/.m2/repository/log4j/log4j/1.1/log4j-1.1.jar
def locate(spec)
spec = Artifact.to_hash(spec)
File.join(local, spec[:group].split('.'), spec[:id], spec[:version], Artifact.hash_to_file_name(spec))
end
# :call-seq:
# mirrors => Array
#
# Returns an array of all the mirror repository URLs.
#
# Mirrors override remote repositories defined in the project.
# The best way is to add repositories to the user settings file under '$HOME/.buildr/settings.yaml'.
# For example:
# repositories:
# mirrors:
# - http://example.com/repository
def mirrors
unless @mirrors
@mirrors = [Buildr.settings.user, Buildr.settings.build].inject([]) { |repos, hash|
repos | Array(hash['repositories'] && hash['repositories']['mirrors'])
}
end
@mirrors
end
# :call-seq:
# remote = Array
# remote = url
# remote = nil
#
# With a String argument, clears the array and set it to that single URL.
#
# With an Array argument, clears the array and set it to these specific URLs.
#
# With nil, clears the array.
def mirrors=(urls)
case urls
when nil then @mirrors = nil
when Array then @mirrors = urls.dup
else @mirrors = [urls.to_s]
end
end
# :call-seq:
# remote => Array
#
# Returns an array of all the remote repository URLs.
#
# When downloading artifacts, repositories are accessed in the order in which they appear here.
# The best way is to add repositories individually, for example:
# repositories.remote << 'http://example.com/repo'
#
# You can also specify remote repositories in the settings.yaml (per user) and build.yaml (per build)
# files. Both sets of URLs are loaded by default into this array, URLs from the personal setting
# showing first.
#
# For example:
# repositories:
# remote:
# - http://example.com/repo
# - http://elsewhere.com/repo
def remote
unless mirrors.empty?
info "Remote repositories overridden by mirrors #{mirrors.map(&:to_s).join(", ")}"
mirrors
end
unless @remote
@remote = [Buildr.settings.user, Buildr.settings.build].inject([]) { |repos, hash|
repos | Array(hash['repositories'] && hash['repositories']['remote'])
}
end
@remote
end
# :call-seq:
# remote_uri => Array
#
# Returns an array of all the remote repositories as instances of URI
#
# Supports
# * String urls: "http://example.com/repo"
# * URI: URI.parse( "http://example.com/repo" )
# * Hash: { :url => "http://example.com/repo", :user => "user", :pass => "pass" }
#
def remote_uri
remote
uris = []
@remote.each do |repo|
case repo
when nil then
# ignore nil
when URI then
uris << repo
when Hash then
url = (repo[:url] || repo['url'] )
if url
uri = URI.parse(url)
if ( username = (repo[:username] || repo['username'] || repo[:user] || repo['user']) )
uri.user = username
end
if ( password = (repo[:password] || repo['password'] || repo[:pass] || repo['pass']) )
uri.password = password
end
uris << uri
else
fail( "Repository Hash format missing url: #{repo}" )
end
when String then
uris << URI.parse(repo)
else
fail( "Unsupported Repository format: #{repo}" )
end
end
uris
end
# :call-seq:
# remote = Array
# remote = url
# remote = nil
#
# With a String argument, clears the array and set it to that single URL.
#
# With an Array argument, clears the array and set it to these specific URLs.
#
# With nil, clears the array.
def remote=(urls)
case urls
when nil then @remote = nil
when Array then @remote = urls.dup
else @remote = [urls.to_s]
end
end
# :call-seq:
# release_to = url
# release_to = hash
#
# Specifies the release server. Accepts a Hash with different repository settings
# (e.g. url, username, password), or a String to only set the repository URL.
#
# Besides the URL, all other settings depend on the transport protocol in use.
#
# For example:
# repositories.release_to = 'sftp://john:secret@example.com/var/www/repo/'
#
# repositories.release_to = { :url=>'sftp://example.com/var/www/repo/',
# :username='john', :password=>'secret' }
# Or in the settings.yaml file:
# repositories:
# release_to: sftp://john:secret@example.com/var/www/repo/
#
# repositories:
# release_to:
# url: sftp://example.com/var/www/repo/
# username: john
# password: secret
def release_to=(options)
options = { :url=>options } unless Hash === options
@release_to = options
end
# :call-seq:
# release_to => hash
#
# Returns the current release server setting as a Hash. This is a more convenient way to
# configure the settings, as it allows you to specify the settings progressively.
#
# For example, the Buildfile will contain the repository URL used by all developers:
# repositories.release_to[:url] ||= 'sftp://example.com/var/www/repo'
# Your private buildr.rb will contain your credentials:
# repositories.release_to[:username] = 'john'
# repositories.release_to[:password] = 'secret'
def release_to
unless @release_to
value = (Buildr.settings.user['repositories'] && Buildr.settings.user['repositories']['release_to']) \
|| (Buildr.settings.build['repositories'] && Buildr.settings.build['repositories']['release_to'])
@release_to = Hash === value ? value.inject({}) { |hash, (key, value)| hash.update(key.to_sym=>value) } : { :url=>Array(value).first }
end
@release_to
end
# :call-seq:
# snapshot_to = url
# snapshot_to = hash
#
# Specifies the release server. Accepts a Hash with different repository settings
# (e.g. url, username, password), or a String to only set the repository URL.
#
# Besides the URL, all other settings depend on the transport protocol in use.
#
# For example:
# repositories.snapshot_to = 'sftp://john:secret@example.com/var/www/repo/'
#
# repositories.snapshot_to = { :url=>'sftp://example.com/var/www/repo/',
# :username='john', :password=>'secret' }
# Or in the settings.yaml file:
# repositories:
# snapshot_to: sftp://john:secret@example.com/var/www/repo/
#
# repositories:
# snapshot_to:
# url: sftp://example.com/var/www/repo/
# username: john
# password: secret
def snapshot_to=(options)
options = { :url=>options } unless Hash === options
@snapshot_to = options
end
# :call-seq:
# snapshot_to => hash
#
# Returns the current snapshot release server setting as a Hash. This is a more convenient way to
# configure the settings, as it allows you to specify the settings progressively.
#
# For example, the Buildfile will contain the repository URL used by all developers:
# repositories.snapshot_to[:url] ||= 'sftp://example.com/var/www/repo'
# Your private buildr.rb will contain your credentials:
# repositories.snapshot_to[:username] = 'john'
# repositories.snapshot_to[:password] = 'secret'
def snapshot_to
unless @snapshot_to
value = (Buildr.settings.user['repositories'] && Buildr.settings.user['repositories']['snapshot_to']) \
|| (Buildr.settings.build['repositories'] && Buildr.settings.build['repositories']['snapshot_to'])
@snapshot_to = Hash === value ? value.inject({}) { |hash, (key, value)| hash.update(key.to_sym=>value) } : { :url=>Array(value).first }
end
@snapshot_to
end
end
# :call-seq:
# repositories => Repositories
#
# Returns an object you can use for setting the local repository path, remote repositories
# URL and release server settings.
#
# See Repositories.
def repositories
Repositories.instance
end
# :call-seq:
# artifact(spec) => Artifact
# artifact(spec) { |task| ... } => Artifact
#
# Creates a file task to download and install the specified artifact in the local repository.
#
# You can use a String or a Hash for the artifact specification. The file task will point at
# the artifact's path inside the local repository. You can then use this tasks as a prerequisite
# for other tasks.
#
# This task will download and install the artifact only once. In fact, it will download and
# install the artifact if the artifact does not already exist. You can enhance it if you have
# a different way of creating the artifact in the local repository. See Artifact for more details.
#
# For example, to specify an artifact:
# artifact('log4j:log4j:jar:1.1')
#
# To use the artifact in a task:
# compile.with artifact('log4j:log4j:jar:1.1')
#
# To specify an artifact and the means for creating it:
# download(artifact('dojo:dojo-widget:zip:2.0')=>
# 'http://download.dojotoolkit.org/release-2.0/dojo-2.0-widget.zip')
def artifact(spec, path = nil, &block) #:yields:task
spec = artifact_ns.fetch(spec) if spec.kind_of?(Symbol)
spec = Artifact.to_hash(spec)
unless task = Artifact.lookup(spec)
task = Artifact.define_task(path || repositories.locate(spec))
task.send :apply_spec, spec
Rake::Task['rake:artifacts'].enhance [task]
Artifact.register(task)
unless spec[:type] == :pom
Rake::Task['artifacts:sources'].enhance [task.sources_artifact]
Rake::Task['artifacts:javadoc'].enhance [task.javadoc_artifact]
Rake::Task['artifacts:annotations'].enhance [task.annotations_artifact]
end
end
task.enhance &block
end
# :call-seq:
# artifacts(*spec) => artifacts
#
# Handles multiple artifacts at a time. This method is the plural equivalent of
# #artifact, but can do more things.
#
# Returns an array of artifacts built using the supplied
# specifications, each of which can be:
# * An artifact specification (String or Hash). Returns the appropriate Artifact task.
# * An artifact of any other task. Returns the task as is.
# * A project. Returns all artifacts created (packaged) by that project.
# * A string. Returns that string, assumed to be a file name.
# * An array of artifacts or a Struct.
# * A symbol. Returns the named artifact from the current ArtifactNamespace
#
# For example, handling a collection of artifacts:
# xml = [ xerces, xalan, jaxp ]
# ws = [ axis, jax-ws, jaxb ]
# db = [ jpa, mysql, sqltools ]
# artifacts(xml, ws, db)
#
# Using artifacts created by a project:
# artifacts project('my-app') # All packages
# artifacts project('my-app').package(:war) # Only the WAR
def artifacts(*specs, &block)
specs.flatten.inject([]) do |set, spec|
case spec
when ArtifactNamespace
set |= spec.artifacts
when Symbol, Hash
set |= [artifact(spec)]
when /([^:]+:){2,4}/ # A spec as opposed to a file name.
set |= [artifact(spec)]
when String # Must always expand path.
set |= [File.expand_path(spec)]
when Project
set |= artifacts(spec.packages)
when Rake::Task
set |= [spec]
when Struct
set |= artifacts(spec.values)
else
if spec.respond_to? :to_spec
set |= artifacts(spec.to_spec)
else
fail "Invalid artifact specification in #{specs.inspect}"
end
end
end
end
def transitive(*args)
options = Hash === args.last ? args.pop : {}
dep_opts = {
:scopes => options[:scopes] || [nil, "compile", "runtime"],
:optional => options[:optional]
}
specs = args.flatten
specs.inject([]) do |set, spec|
case spec
when /([^:]+:){2,4}/ # A spec as opposed to a file name.
artifact = artifact(spec)
set |= [artifact] unless artifact.type == :pom
set |= POM.load(artifact.pom).dependencies(dep_opts).map { |spec| artifact(spec) }
when Hash
set |= [transitive(spec, options)]
when String # Must always expand path.
set |= transitive(file(File.expand_path(spec)), options)
when Project
set |= transitive(spec.packages, options)
when Rake::Task
set |= spec.respond_to?(:to_spec) ? transitive(spec.to_spec, options) : [spec]
when Struct
set |= transitive(spec.values, options)
else
fail "Invalid artifact specification in: #{specs.to_s}"
end
end
end
# :call-seq:
# group(ids, :under=>group_name, :version=>number) => artifacts
#
# Convenience method for defining multiple artifacts that belong to the same group, type and version.
# Accepts multiple artifact identifiers followed by two or three hash values:
# * :under -- The group identifier
# * :version -- The version number
# * :type -- The artifact type (optional)
# * :classifier -- The artifact classifier (optional)
#
# For example:
# group 'xbean', 'xbean_xpath', 'xmlpublic', :under=>'xmlbeans', :version=>'2.1.0'
# Or:
# group %w{xbean xbean_xpath xmlpublic}, :under=>'xmlbeans', :version=>'2.1.0'
def group(*args)
hash = args.pop
args.flatten.map do |id|
artifact :group => hash[:under],
:type => hash[:type],
:version => hash[:version],
:classifier => hash[:classifier],
:id => id
end
end
# :call-seq:
# install(artifacts) => install_task
#
# Installs the specified artifacts in the local repository as part of the install task.
#
# You can use this to install various files in the local repository, for example:
# install artifact('group:id:jar:1.0').from('some_jar.jar')
# $ buildr install
def install(*args, &block)
artifacts = artifacts(args).uniq
raise ArgumentError, 'This method can only install artifacts' unless artifacts.all? { |f| f.respond_to?(:to_spec) }
task('install').tap do |install|
install.enhance(artifacts) do
artifacts.each(&:install)
end
install.enhance &block if block
task('uninstall') do
artifacts.map(&:to_s ).each { |file| rm file if File.exist?(file) }
end
end
end
# :call-seq:
# upload(artifacts)
#
# Uploads the specified artifacts to the release server as part of the upload task.
#
# You can use this to upload various files to the release server, for example:
# upload artifact('group:id:jar:1.0').from('some_jar.jar')
# $ buildr upload
def upload(*args, &block)
artifacts = artifacts(args)
raise ArgumentError, 'This method can only upload artifacts' unless artifacts.all? { |f| f.respond_to?(:to_spec) }
upload_artifacts_tasks = artifacts.map { |artifact| artifact.upload_task }
task('upload').tap do |task|
task.enhance &block if block
task.enhance upload_artifacts_tasks
end
end
end