require "enumerator"
require 'reap/task'
require "reap/vendor/http-access2"
# ___ _ _____ _
# | _ \___| |___ __ _ ___ ___ |_ _|_ _ __| |__
# | / -_) / -_) _` (_- -_) | |/ _` (_-< / /
# |_|_\___|_\___\__,_/__/\___| |_|\__,_/__/_\_\
#
# = Release Task
#
# This task releases files to RubyForge and other GForge instaces
# or SourceForge clones. In its most simple usage it looks like:
#
# project = MetaProject::Project::XForge::RubyForge.new('xforge')
# # Create a new release of the xforge project on Rubyforge.
# task :release => [:gem] do
# Rake::XForge::Release.new(project) {}
# end
#
# The previous example will use defaults where it can. It will prompt
# you for your xForge user name and password before it uploads all
# gems under the pkg folder and creates a RubyForge release.
#
# While defaults are nice, you may want a little more control. You can
# specify additional attributes:
#
# * #user_name
# * #password
# * #changes_file
# * #version
# * #files
# * #release_name
# * #release_notes
# * #release_changes
# * #package_name
#
# Example:
# project = MetaProject::Project::XForge::RubyForge.new('xforge')
# task :release => [:gem] do
# release_files = FileList[ 'pkg/*.gem', 'CHANGES' ]
#
# Rake::XForge::Release.new(project) do |xf|
# # Never hardcode user name and password in the Rakefile!
# xf.user_name = ENV['RUBYFORGE_USER']
# xf.password = ENV['RUBYFORGE_PASSWORD']
# xf.files = release_files.to_a
# xf.release_name = "XForge 0.1"
# end
#
# This can be invoked from the command line:
#
# rake release RUBYFORGE_USER=myuser \
# RUBYFORGE_PASSWORD=mypassword
#
# If you don't like blocks, you can do like this:
#
# project = MetaProject::Project::XForge::RubyForge.new('xforge')
# task :release => [:gem] do
# xf = Rake::XForge::Release.new(project)
# ... # Set additional attributes
# xf.execute
# end
class Reap::Release < Reap::Task
section_required true
task_desc "Release distribution files."
task_help %{
reap release
Release distribution to Rubyforge, or other
GForge based service.
host URL of host service
username Username of host service
project Project name at host
package Package name
groupid Group id number
architecture Architecture (Any, i386, PPC, etc.)
release Release name (default is "%s")
private Private release?
changelog Change log file
notelog Release notes file
dir Distribution directory
exclude Distribution types to exclude
The release option can be a template by using %s in the
string. The version number of your project will be sub'd
in for the %s. This saves you from having to update
the release name before every release.
}
alias_method :rel, :task
def run
# setup
rel.host ||= 'rubyforge.org'
rel.host.chomp!('/')
rel.username ||= master.rubyforge.username
#rel.password ||= master.rubyforge.password
rel.project ||= master.rubyforge.project || master.name
rel.package ||= master.rubyforge.package
#rel.packageid ||= master.rubyforge.packageid
rel.groupid ||= master.rubyforge.groupid
rel.private ||= false
rel.processor ||= 'Any'
rel.date ||= Time::now.strftime('%Y-%m-%d %H:%M')
rel.exclude ||= []
rel.exclude << 'tar.gz' if rel.exclude.include?( 'tgz' )
rel.exclude << 'tgz' if rel.exclude.include?( 'tar.gz' )
rel.exclude << 'tar.bz2' if rel.exclude.include?( 'tbz' )
rel.exclude << 'tbz' if rel.exclude.include?( 'tar.bz2' )
rel.cookie_jar ||= File::join(File::expand_path("~"), ".rubyforge.cookie_jar")
# do not inherit these
rel.dir = section.dir || master.project.dir
rel.release = (section.release || "%s") % (master.version || rel.date)
# verify
abort "missing field -- package" unless rel.package
#abort "missing field -- packageid" unless rel.packageid
abort "missing field -- groupid" unless rel.groupid
# add more...
# case rel.host
# when 'rubyforge', 'rubyforge.org'
# else
# puts %{Unrecognized release host '#{rel.host}'. Skipped.}
# skip = true
# end
# release
puts "Reap is preparing release ..."
rtypes = [ 'tgz', 'tbz', 'tar.gz', 'tar.bz2', 'deb', 'gem', 'ebuild' ]
rtypes -= rel.exclude
rtypes = rtypes.collect { |rt| Regexp.escape( rt ) }
re_rtypes = Regexp.new( '[.](' << rtypes.join('|') << ')$' )
dir = File.join( rel.dir, "#{rel.name}-#{rel.version}" )
files = Dir.entries(dir).select { |f|
f =~ re_rtypes or f == 'PKGBUILD'
}
files = files.collect { |f| File.join( dir, f ) }
if files.empty?
puts "No files to release at #{dir}."
exit -1
end
# ask for password
unless $PASSWORD
print "Password for #{rel.username}: "
until passwd = $stdin.gets.strip ; sleep 1 ; end
@password = passwd
else
@password = $PASSWORD
end
login {
unless package?
puts "Package (#{rel.package}) does not exist."
puts "Use -f option to force package creation."
exit 0 unless $FORCE
create_package
puts "Created package -- #{rel.package}"
end
if release?
unless $FORCE
puts "Release (#{rel.release}) already exists."
puts "Use -f option to force re-release."
exit 0
end
files.each do |f|
remove_file( f ) if file?( f )
puts "Removed old file -- #{ f }"
add_file( f )
puts "Added new file -- #{ f }"
end
else
add_release( files.first )
unless release?
puts "Release creation failed -- #{rel.release}"
exit -1
end
puts "Added release -- #{ rel.release }"
puts "Added file -- #{ files.first }"
files[1..-1].each do |f|
add_file( f )
puts "Added file -- #{ f }"
end
end
}
puts "Release complete!"
end
private
FILETYPES = {
".deb" => 1000,
".rpm" => 2000,
".zip" => 3000,
".bz2" => 3100,
".gz" => 3110,
".src.zip" => 5000,
".src.bz2" => 5010,
".src.gz" => 5020,
".src.rpm" => 5100,
".src" => 5900,
".jpg" => 8000,
".txt" => 8100,
".text" => 8100,
".htm" => 8200,
".html" => 8200,
".pdf" => 8300,
".oth" => 9999,
".ebuild" => 1300,
".exe" => 1100,
".dmg" => 1200,
".tar.gz" => 5000,
".tgz" => 5000,
".gem" => 1400,
".pgp" => 8150,
".sig" => 8150,
}
PROCESSORS = {
"i386" => 1000,
"IA64" => 6000,
"Alpha" => 7000,
"Any" => 8000,
"PPC" => 2000,
"MIPS" => 3000,
"Sparc" => 4000,
"UltraSparc" => 5000,
"Other" => 9999,
}
# login
def login( &block )
# login
page = "/account/login.php"
method = "post_content"
form = {
"return_to" => "",
"form_loginname" => rel.username,
"form_pw" => @password,
"login" => "Login"
}
http_transaction( page, method, form )
# do whatever
block.call
# logout
page = "/account/logout.php"
method = "post_content"
form = {}
http_transaction( page, method, form )
end
# Package exists?
def package?
page = "/frs/"
method = "post_content"
form = {
"group_id" => rel.groupid
}
scrape = http_transaction( page, method, form )
restr = ''
restr << Regexp.escape( rel.package )
restr << '\s*'
restr << Regexp.escape( %{ rel.groupid,
"package_name" => rel.package,
"func" => "add_package",
"is_public" => (rel.private ? 0 : 1),
"submit" => "Create This Package"
}
http_transaction( page, method, form )
end
# Release exits?
def release?
page = "/frs/admin/showreleases.php"
method = "post_content"
form = {
"package_id" => rel.packageid,
"group_id" => rel.groupid
}
scrape = http_transaction( page, method, form )
restr = ''
restr << Regexp.escape( %{"editrelease.php?group_id=#{rel.groupid}} )
restr << Regexp.escape( %{&package_id=#{rel.packageid}} )
restr << Regexp.escape( %{&release_id=} )
restr << '(\d+)'
restr << Regexp.escape( %{">#{rel.release}} )
re = Regexp.new( restr )
md = re.match( scrape )
if md
rel.releaseid = md[1]
end
end
# Add a new release.
def add_release( userfile )
page = "/frs/admin/qrs.php"
method = "post_content"
type_id = rel.type || userfile[%r|\.[^\./]+$|]
type_id = FILETYPES[type_id]
proc_id = PROCESSORS[rel.processor]
# how to use these?
notes = rel.notes ? rel.notes : ( rel.notelog ? open(rel.notelog) : nil )
changes = rel.changes ? rel.changes : ( rel.changelog ? open(rel.changelog) : nil )
userfile = open(userfile)
preformatted = '1'
form = {
"group_id" => rel.groupid,
"package_id" => rel.packageid,
"release_name" => rel.release,
"release_date" => rel.date,
"type_id" => type_id,
"processor_id" => proc_id,
"preformatted" => preformatted,
"userfile" => userfile,
"submit" => "Release File"
}
boundary = Array::new(8){ "%2.2d" % rand(42) }.join('__')
extheader = { 'content-type'=>"multipart/form-data; boundary=___#{ boundary }___" }
http_transaction( page, method, form, extheader )
end
# Does file exist?
# Note this is a little bit fragile.
# If two releases have the same exact file name in them
# there could be a problem --that's probably not likely,
# maybe even impossible, but as of yet, I can't yet rule it out.
def file?( file )
page = "/frs/"
method = "post_content"
form = {
"group_id" => rel.groupid
}
scrape = http_transaction( page, method, form )
restr = ''
restr << Regexp.escape( rel.package )
restr << '\s*'
restr << Regexp.escape( %{ rel.groupid,
"package_id" => rel.packageid,
"release_id" => rel.releaseid,
"file_id" => rel.fileid,
"step3" => "Delete File",
"im_sure" => '1',
"submit" => "Delete File "
}
http_transaction( page, method, form )
end
# Add file to release.
def add_file( userfile )
page = '/frs/admin/editrelease.php'
method = "post_content"
type_id = rel.type || userfile[%r|\.[^\./]+$|]
type_id = FILETYPES[type_id]
proc_id = PROCESSORS[rel.processor]
userfile = open( userfile )
form = {
"group_id" => rel.groupid,
"package_id" => rel.packageid,
"release_id" => rel.releaseid,
"step2" => '1',
"userfile" => userfile,
"type_id" => type_id,
"processor_id" => proc_id,
"submit" => "Add This File"
}
boundary = Array::new(8){ "%2.2d" % rand(42) }.join('__')
extheader = { 'content-type'=>"multipart/form-data; boundary=___#{ boundary }___" }
http_transaction( page, method, form, extheader )
end
# http transaction
def http_transaction( page, method, form, extheader={} )
client = HTTPAccess2::Client::new ENV['HTTP_PROXY']
client.debug_dev = STDERR if ENV['DEBUG']
client.set_cookie_store( rel.cookie_jar )
# fixes http-access2 bug
client.redirect_uri_callback = lambda do |res|
page = res.header['location'].first
page = page =~ %r/http/ ? page : "http://#{ rel.host }/#{ page }"
page
end
page.sub!(/^\//, '')
uri = "http://#{ rel.host }/#{ page }"
response = client.send method, uri, form, extheader
client.save_cookie_store
return response
end
end
# fixes http-access2 bug
BEGIN {
require "reap/vendor/http-access2"
module WebAgent::CookieUtils
def domain_match(host, domain)
case domain
when /\d+\.\d+\.\d+\.\d+/
return (host == domain)
when '.'
return true
when /^\./
#return tail_match?(domain, host)
return tail_match?(host, domain)
else
return (host == domain)
end
end
end
}