Cross package maker. DEB/RPM generation or conversion. Derived from jordansissel/fpm.

⌈⌋ ⎇ branch:  cross package maker


Artifact [730a4f0d30]

Artifact 730a4f0d302afc57016205c3c4cbfe84756036c9:

  • File lib/fpm/package/cpan.rb — part of check-in [338a1e5234] at 2013-05-07 06:51:12 on branch trunk — - improve cpan support (now with tests) (user: jls@semicomplete.com size: 6696)

require "fpm/namespace"
require "fpm/package"
require "fpm/util"
require "fileutils"
require "find"

class FPM::Package::CPAN < FPM::Package
  # Flags '--foo' will be accessable  as attributes[:npm_foo]
  option "--perl-bin", "PERL_EXECUTABLE",
    "The path to the perl executable you wish to run.", :default => "perl"
  option "--cpanm-bin", "CPANM_EXECUTABLE",
    "The path to the cpanm executable you wish to run.", :default => "cpanm"
  option "--package-name-prefix", "NAME_PREFIX", "Name to prefix the package " \
    "name with.", :default => "perl"
  option "--test", :flag, "Run the tests before packaging?", :default => true

  private
  def input(package)
    require "ftw" # for http access
    require "json"

    agent = FTW::Agent.new
    result = search(package, agent)
    tarball = download(result, agent)
    moduledir = unpack(tarball)

    # Read package metadata (name, version, etc)
    if File.exists?(File.join(moduledir, "META.json"))
      metadata = JSON.parse(File.read(File.join(moduledir, ("META.json"))))
    elsif File.exists?(File.join(moduledir, ("META.yml")))
      require "yaml"
      metadata = YAML.load_file(File.join(moduledir, ("META.yml")))
    else
      raise FPM::InvalidPackageConfiguration, 
        "Could not find package metadata. Checked for META.json and META.yml"
    end
    self.version = metadata["version"]
    self.description = metadata["abstract"]

    self.license = case metadata["license"]
      when Array; metadata["license"].first
      else; metadata["license"]
    end

    if metadata.include?("distribution")
      @logger.info("Setting package name from 'distribution'",
                   :distribution => metadata["distribution"])
      self.name = fix_name(metadata["distribution"])
    else
      @logger.info("Setting package name from 'name'",
                   :name => metadata["name"])
      self.name = fix_name(metadata["name"])
    end

    # Not all things have 'author' listed.
    self.vendor = metadata["author"].join(", ") if metadata.include?("author")
    self.url = metadata["resources"]["homepage"] rescue "unknown"

    # TODO(sissel): figure out if this perl module compiles anything
    # and set the architecture appropriately.
    self.architecture = "all"

    # Install any build/configure dependencies with cpanm.
    # We'll install to a temporary directory.
    @logger.info("Installing any build or configure dependencies")
    safesystem(attributes[:cpan_cpanm_bin], "-L", build_path("cpan"), moduledir)

    if !attributes[:no_auto_depends?] 
      if metadata.include?("requires")
        metadata["requires"].each do |dep_name, version|
          # Special case for representing perl core as a version.
          if dep_name == "perl"
            self.dependencies << "#{dep_name} >= #{version}"
            next
          end
          dep = search(dep_name, agent)
          
          if dep.include?("distribution")
            name = fix_name(dep["distribution"])
          else
            name = fix_name(dep_name)
          end

          if version.to_s == "0"
            # Assume 'Foo = 0' means any version?
            self.dependencies << "#{name}"
          else
            self.dependencies << "#{name} = #{version}"
          end
        end
      end
    end #no_auto_depends

    ::Dir.chdir(moduledir) do
      # TODO(sissel): install build and config dependencies to resolve
      # build/configure requirements.
      # META.yml calls it 'configure_requires' and 'build_requires'
      # META.json calls it prereqs/build and prereqs/configure
 
      prefix = attributes[:prefix] || "/usr/local"
      # TODO(sissel): Set default INSTALL path?
      # perl -e 'use Config; print "$Config{sitelib}"'
      safesystem(attributes[:cpan_perl_bin],
                 "-Mlocal::lib=#{build_path("cpan")}",
                 "Makefile.PL",
                 "PREFIX=#{prefix}",
                 # Have to specify INSTALL_BASE as empty otherwise
                 # Makefile.PL lies and claims we've set both PREFIX and
                 # INSTALL_BASE.
                 "INSTALL_BASE="
                )

      make = [ "make" ]

      safesystem(*make)
      safesystem(*(make + ["test"])) if attributes[:cpan_test?]
      safesystem(*(make + ["DESTDIR=#{staging_path}", "install"]))
    end

    # TODO(sissel): figure out if this perl module compiles anything
    # and set the architecture appropriately.
    self.architecture = "all"

    # Find any shared objects in the staging directory to set architecture as
    # native if found; otherwise keep the 'all' default.
    Find.find(staging_path) do |path|
      if path =~ /\.so$/  
        @logger.info("Found shared library, setting architecture=native",
                     :path => path)
        self.architecture = "native" 
      end
    end
  end

  def unpack(tarball)
    directory = build_path("module")
    ::Dir.mkdir(directory)
    args = [ "-C", directory, "-zxf", tarball,
      "--strip-components", "1" ]
    safesystem("tar", *args)
    return directory
  end

  def download(metadata, agent)
    distribution = metadata["distribution"]
    author = metadata["author"]
    @logger.info("Downloading perl module",
                 :distribution => distribution,
                 :version => version)

    # default to latest versionunless we specify one
    version = metadata["version"] if version.nil?

    tarball = "#{distribution}-#{version}.tar.gz"
    url = "http://search.cpan.org/CPAN/authors/id/#{author[0,1]}/#{author[0,2]}/#{author}/#{tarball}"
    response = agent.get!(url)
    if response.error?
      @logger.error("Download failed", :error => response.status_line,
                    :url => url)
      raise FPM::InvalidPackageConfiguration, "metacpan query failed"
    end

    File.open(build_path(tarball), "w") do |fd|
      response.read_body { |c| fd.write(c) }
    end
    return build_path(tarball)
  end # def download

  def search(package, agent)
    @logger.info("Asking metacpan about a module", :module => package)
    metacpan_url = "http://api.metacpan.org/v0/module/" + package
    response = agent.get!(metacpan_url)
    if response.error?
      @logger.error("metacpan query failed.", :error => response.status_line,
                    :module => package, :url => metacpan_url)
      raise FPM::InvalidPackageConfiguration, "metacpan query failed"
    end

    data = ""
    response.read_body { |c| data << c }
    metadata = JSON.parse(data)
    return metadata
  end # def metadata

  def fix_name(name)
    return [attributes[:cpan_package_name_prefix], name].join("-").gsub("::", "-")
  end # def fix_name

  public(:input)
end # class FPM::Package::NPM