#-- # ___ _ # | _ \__ _ __| |____ _ __ _ ___ # | _/ _` / _| / / _` / _` / -_) # |_| \__,_\__|_\_\__,_\__, \___| # |___/ #++ require 'facet/fileutils/safe_ln.rb' require 'facet/filelist.rb' unless defined?( FileList ) # prevent overlap with Rake's module Reap # = Package # # This class creates standard .zip, .tgz, or .tbz # packages, plus .gem or .deb distributions. # # Builds distribution packages. The package task supports # tar.gz, tar.bz2, zip source packages and gem, pacman and # debian ditribution packages. # # Task specific settings: # # dir Directory in which to store distributions. # include Files to include in distribution. # exclude Files to exclude from those. # distribute List of distribution types desired. # (tgz, tar.gz, tbz, tar.bz2, zip, gem, deb, pac) # project Project name. # category Software category. # architecture Can be any, i368, i686, ppc, etc. # dependencies List of packages this program depends. # recommends List of packages that can be used with this package. # replaces List of packages this one replaces. # executables Executable files in this distribution. # rules (see below) # # RubyGems specific settings: # # autorequire # platform # require_paths # # The package task also has subsection for each type of distribution. # These can be used to override settings in the package information # if they in some way differ. Possible subsections are: # # gems # pacman # debian # # Finally there is one last parameter that you can use for creating packages # called 'rules'. The rules setting allows you to define how files are # copied into the distribution package, so instead of a one to one copy # of the included files, you can actually have a file placed in a different # location within the distribution. This can be very handy if you wish to # develop you project with one layout, but need to distribute it with another. # # The rules parameter is a literal string that consists of one rule per line. # A line consists three space separated entries. # # from_path, file_glob, to_path # # If no 'to_path' is given then it is considered the same as the 'from_path'. # It also supports two variables $name and $version which will be substitued # any of these entries. Here is a possible example: # # rules: | # lib **/* lib/$name/$version # # This will move any file under lib/ to the equivalent location under lib/$name/$version/. # The default set of rules is a mirror image transfer, spelled out it would be: # # rules: | # bin * # ext **/* # lib **/* # data **/* # conf **/* # # If your using Rolls against a normal project folder the alterntive is to create # versioned paths, probably as follows: # # rules: | # ext/$name **/* $name/$version # lib/$name **/* $name/$version # data/$name **/* $name/$version # conf/$name **/* $name/$version ? # # Please note that the rules parameter is a new feature and is still considered beta. class Package include TaskUtils KINDS = [ 'bin', 'lib', 'ext', 'data', 'conf', 'doc' ] LOCATIONS = [ 'dist', 'pkg', 'release', 'package', 'distribution' ] MUST_EXCLUDE = [ 'InstalledFiles', '**/CVS/**/*', '**/*~', 'dist', 'pkg', 'release' ] attr :pkg # Setup package task settings. def initialize( pkg ) @pkg = pkg pkg.status ||= 'development' pkg.series ||= '1' pkg.author ||= "Anonymous" pkg.maintainer ||= pkg.author pkg.email ||= '' pkg.summary ||= '' pkg.architecture ||= 'any' pkg.license ||= 'Ruby/GPL' pkg.dir ||= LOCATIONS.find { |f| File.directory?(f) } || 'dist' pkg.date ||= Time.now.strftime("%Y-%m-%d") d = pkg.date.split('-').collect{ |e| e.to_i } #d[0] = d[0] - 2000 # No need to keep the 2000 pkg.version ||= d.join('.') if $BUILD_VERSION pkg.buildno = Time.now.strftime("%H*60+%M") pkg.version += ".#{pkg.buildno}" end pkg.package_name ||= "#{pkg.name}-#{pkg.version}" pkg.exclude ||= [] pkg.exclude |= MUST_EXCLUDE pkg.include ||= ['**/*'] # distribute types include 'tgz', 'tbz', 'zip', 'tar.gz' 'tar.bz2', 'gem', 'pac' and 'deb'. pkg.distribute ||= [ 'gem', 'tar.bz2', 'zip' ] pkg.distribute = [pkg.distribute].flatten.collect { |t| t.to_s.strip.downcase } pkg.dependencies ||= [] pkg.executables ||= [] pkg.requirements ||= [] pkg.recommends ||= [] pkg.conflicts ||= [] pkg.replaces ||= [] @rules = parse_rules( pkg.rules ) end private #-- # TODO use shellwords #++ def parse_rules( spec ) return [] unless spec rules = [] #Hash.new { |h,k| h[k] = [] } lines = spec.strip.split("\n") lines.each { |line| words = line.strip.split(/\s+/) from, glob, to = *words rules << [ vsub(from), vsub(glob), vsub(to) ] } rules end def vsub( str ) return nil if str.nil? str = str.gsub( '$version', pkg.version.to_s ) #@version.to_s ) str = str.gsub( '$name', pkg.name.to_s ) str end public # Generate packages. def generate_packages release_folder = File.join( pkg.dir, pkg.package_name ) mirror_folder = File.join( pkg.dir, pkg.package_name, pkg.package_name ) @release_path = File.expand_path( release_folder ) @mirror_path = File.expand_path( mirror_folder ) if FileTest.directory?(release_folder) if $FORCE puts "Removing old directory '#{File.expand_path(release_folder)}'..." FileUtils.rm_r(release_folder) unless $PRETEND else puts "Package directory '#{pkg.package_name}' already exists. Use -f option to overwrite." return nil end end #puts "Creating #{pkg.distribute.join(',')} packages..." # First we make a copy of the files to be distributed. if $PRETEND puts "mkdir_p #{mirror_folder}" else FileUtils.mkdir_p( mirror_folder ) end trans = transfer_rules() list = ::FileList.new list.include(*pkg.include) list.exclude(*pkg.exclude) if pkg.exclude list.resolve # build transfer table folders, files = [], {} list.each do |from| to = trans.key?(from) ? trans[from] : from #to = File.join( mirror_folder, to ) if File.directory?( from ) folders << to else files[ from ] = to folders << File.dirname( to ) # ensure creation of files' folder end end folders.delete('') folders.uniq! # create folders folders.each do |to| to = File.join( mirror_folder, to ) if $PRETEND puts "mkdir_p #{to}" else FileUtils.mkdir_p( to ) end end # safe link files files.each do |from, to| to = File.join( mirror_folder, to ) if $PRETEND puts "safe_ln #{from}, #{to}" else FileUtils.safe_ln( from, to ) end end # store package folders and files relative to mirror_folder (gems build uses this) @package_files = folders + files.values #@package_files = package_files.collect{ |f| f.sub("#{mirror_folder}/", '') } # Now we create standard packages from the copy. FileUtils.chdir( @release_path ) do pkg.distribute.each do |t| sh_cmd = nil prefix = 'ERROR' # in case of unforseen bug case t when 'tbz', 'bz2', 'bzip2', 'tar.bz2' prefix = ( t == 'tbz' ? 'tbz' : 'tar.bz2' ) sh_cmd = 'tar --bzip2 -cvf' puts "\n[SHELL #{prefix.upcase}]" when 'tgz', 'tar.gz' prefix = ( t == 'tgz' ? 'tgz' : 'tar.gz' ) sh_cmd = 'tar --gzip -cvf' puts "\n[SHELL #{prefix.upcase}]" when 'zip' prefix = 'zip' sh_cmd = 'zip -r' puts "\n[SHELL #{prefix.upcase}]" when 'gem', 'deb', 'pac' sh_cmd = nil else puts "WARNING: Unknown package type '#{t}' skipped" sh_cmd = nil end sh %{#{sh_cmd} #{pkg.package_name}.#{prefix} #{pkg.package_name}} if sh_cmd end puts end # TODO Finish pretend mode for the distribution types. return if $PRETEND # create gem package if pkg.distribute.include?('gem') # load rubygems if available begin require 'rubygems' run_gem rescue LoadError # no rubygems tell "WARNING: Package .gem requested, but rubygems not found (skipped)." end # if defined?(::Gem) # run_gem # else # tell "WARNING: Package .gem requested, but rubygems not found (skipped)." # end end # create debian package if pkg.distribute.include?('deb') if true # TODO ensure required debian tools here run_deb else tell "WARNING: Package .deb requested, but debian tools not found (skipped)." end end # create PKGBUILD (pacman, archlinux) if pkg.distribute.include?('pac') if true # TODO ensure required tools here run_pacman else tell "WARNING: Pacman package requested, but required tools not found (skipped)." end end # we can remove mirror folder now FileUtils.rm_r(mirror_folder) unless $PRETEND #or $OPTIONS[:retain] return true end private # Determine transfer rules. # # Rules should proceed from the most general to the most specific. def transfer_rules trans = {} @rules.each do |rule| from, glob, to = *rule to = from unless to #? files = Dir.glob( File.join( from, glob ) ) files.each do |file| trans[file] = File.join( to, file.sub(from,'') ) end end trans end # Build a gem package. def run_gem # use subsection if given if pkg.gem pkg = pkg().__merge__( pkg.gem ) else pkg = pkg() end spec = Gem::Specification.new { |s| s.name = pkg.name s.version = pkg.version pkg.dependencies.each { |d,v| if v s.add_dependency(d, v) else s.add_dependency(d) end } if pkg.platform begin s.platform = ::Gem::Platform.const_get(pkg.platform.upcase) rescue NameError s.platform = ::Gem::Platform::RUBY end else s.platform = ::Gem::Platform::RUBY end s.summary = pkg.summary s.requirements = pkg.requirements # s.files = Dir.glob("lib/**/*").delete_if {|item| item.include?("CVS")} # s.files.concat Dir.glob("bin/**/*").delete_if {|item| item.include?("CVS")} #package_files = FileList.new #package_files.include(*pkg.include) #package_files.exclude(*pkg.exclude) if pkg.exclude and not pkg.exclude.empty? s.files = @package_files.to_a s.author = pkg.author s.email = pkg.email s.rubyforge_project = pkg.project s.homepage = pkg.homepage s.require_path = 'lib' s.require_paths = pkg.require_paths if pkg.require_paths s.autorequire = pkg.autorequire if pkg.autorequire s.executables = pkg.executables s.bindir = "bin" s.has_rdoc = true } tell "[RUBYGEMS]" Dir.chdir( @mirror_path ) { Gem.manage_gems Gem::Builder.new(spec).build gems = Dir.glob( './*.gem' ) gems.each{ |f| FileUtils.mv( f, @release_path ) } } puts end # This build the Debiam package. def run_deb # use subsection if given if pkg.debian pkg = pkg().__merge__( pkg.debian ) else pkg = pkg() end # build the debian control file dep = pkg.dependencies.collect{ |d, v| if v "#{d} (#{v})" else d end }.join(', ') # Some debian fields not yet used: # Installed-Size: 1024 # Replaces: sam-sheepdog # Pre-Depends: perl, ... # Suggests: docbook arch = pkg.architecture.downcase arch = (arch == 'any' ? 'all' : arch) ctrl = %{ Package: #{pkg.name} Version: #{pkg.version} Priority: optional Architecture: #{arch} Essential: no }.tabto(0) ctrl << "Section: #{pkg.category}\n" if pkg.category ctrl << "Depends: #{dep}\n" unless dep.empty? ctrl << "Recommends: #{pkg.recommends.join(' | ')}\n" unless pkg.recommends.empty? ctrl << "Conflicts: #{pkg.conflicts.join(', ')}\n" unless pkg.conflicts.empty? ctrl << "Maintainer: #{pkg.maintainer} <#{pkg.email}>\n" ctrl << "Provides: #{pkg.name}\n" ctrl << "Description: #{pkg.summary}\n" ctrl << " #{pkg.description}\n" if arch == 'all' debname = "#{pkg.name}-ruby_#{pkg.version}.deb" else debname = "#{pkg.name}-ruby_#{pkg.version}_#{pkg.architecture}.deb" end pkgdir = @release_path #File.join( pkg.dir, pkg.package_name ) debdir = File.join( pkgdir, 'debian' ) #debdebdir = File.join( debdir, 'DEBIAN' ) debfile = File.join( pkgdir, debname ) puts "[SHELL DEB]" FileUtils.mkdir_p(debdir) Dir.chdir( @mirror_path ) do unless provide_setup_rb puts "Setup.rb is missing. Forced to skip debian package creation." return nil end cmd = %{ruby setup.rb } cmd << '-q ' unless $VERBOSE cmd << %{all --installdirs=std --root=#{debdir}} sh cmd FileUtils.mkdir_p( File.join(debdir, 'DEBIAN') ) File.open( File.join(debdir, 'DEBIAN', 'control'), 'w') { |f| f << ctrl } sh %{dpkg-deb -b #{debdir} #{debfile}} end puts end # This builds a pacman (archlinux) PKGBUILD script. def run_pacman # use subsection if it exists if pkg.pacman pkg = pkg().__merge__( pkg.pacman ) else pkg = pkg() end unless pkg.source tell "SOURCE, a URL to the source package, is a required field" return nil end _dep = pkg.dependencies.collect{ |d| "'#{d}'" }.join(' ') _rep = pkg.replaces.collect{ |d| "'#{d}'" }.join(' ') _con = pkg.conflicts.collect{ |d| "'#{d}'" }.join(' ') proto = %{ pkgname=#{pkg.name} pkgver=#{pkg.version} pkgrel=#{pkg.series} pkgdesc="#{pkg.summary}" url="#{pkg.homepage}" license="#{pkg.license}" depends=(#{_dep}) conflicts=(#{_con}) replaces=(#{_rep}) source=(#{pkg.source}) md5sums=(#{pkg.md5}) }.tabto(0) # What are these for? # install= # backup=() if pkg.usemake proto << %{ makedepends=()" build() { cd $startdir/src/$pkgname-$pkgver ./ruby setup.rb --prefix=/usr make || return 1 make DESTDIR=$startdir/pkg install } }.tabto(0) else proto << %{ build() { cd $startdir/src/$pkgname-$pkgver ./ruby setup.rb --prefix=/usr } }.tabto(0) end pacdir = File.join( @release_path, 'pacman' ) #pacpacdir = File.join( pacdir, 'PACMAN' ) pacfile = File.join( pacdir, 'PKGBUILD' ) tell "[PACMAN PKGBUILD]" FileUtils.mkdir_p( pacdir ) File.open( pacfile, 'w') { |f| f << proto } tell "Created #{pacfile}" puts end end #class Package end #module Reap