diff --git a/lib/fpm/package.rb b/lib/fpm/package.rb
index 1bbbd5d7af..38f7239c13 100644
--- a/lib/fpm/package.rb
+++ b/lib/fpm/package.rb
@@ -109,6 +109,11 @@ def to_s
 
   attr_accessor :directories
 
+  # Strings that contain the shell code for building/installing a package. These
+  # are needed by source-based package types.
+  attr_accessor :build_procedure
+  attr_accessor :install_procedure
+
   # Any other attributes specific to this package.
   # This is where you'd put rpm, deb, or other specific attributes.
   attr_accessor :attributes
@@ -190,6 +195,13 @@ def type
     self.class.type
   end # def type
 
+  # Source-based package types (such as SRPM and SDEB) should override this method to return true
+  public
+  def source_pkg?
+    return nil
+  end # def source_pkg?
+  private
+
   # Convert this package to a new package type
   def convert(klass)
     logger.info("Converting #{self.type} to #{klass.type}")
@@ -206,6 +218,18 @@ def convert(klass)
       :@name, :@provides, :@replaces, :@scripts, :@url, :@vendor, :@version,
       :@directories, :@staging_path, :@attrs
     ]
+
+    # source packages dont need to actually build the source, instead they need
+    # the shell code for the build and install procedures.
+    if pkg.source_pkg?()
+      h = shell_code() # h is a two elem hash ...
+      @build_procedure = h[:build_procedure]
+      @install_procedure = h[:install_procedure]
+      ivars += [:@build_procedure, :@install_procedure]
+    else # binary packages need to be built and installed to the staging_path
+      build_and_install
+    end
+
     ivars.each do |ivar|
       #logger.debug("Copying ivar", :ivar => ivar, :value => instance_variable_get(ivar),
                     #:from => self.type, :to => pkg.type)
@@ -240,9 +264,12 @@ def converted_from(origin)
   # The idea is that you can keep pumping in new things to a package
   # for later conversion or output.
   #
-  # Implementations are expected to put files relevant to the 'input' in the
-  # staging_path
-  def input(thing_to_input)
+  # Implementations are expected to leave their prepared files in the
+  # staging_path.
+  #
+  # It is important to note that the build/installation of the
+  # package now happens in the convert() method, not input().
+  def input(thing_to_prepare)
     raise NotImplementedError.new("#{self.class.name} does not yet support " \
                                   "reading #{self.type} packages")
   end # def input
@@ -253,6 +280,15 @@ def output(path)
                                   "creating #{self.type} packages")
   end # def output
 
+  def shell_code
+    raise NotImplementedError.new("#{self.class.name} does not yet know how to produce " \
+                                  "shell code for its build/install procedures")
+  end # def shell_code
+
+  def build_and_install
+    raise NotImplementedError.new("TODO")
+  end # def build_and_install
+
   def staging_path(path=nil)
     @staging_path ||= Stud::Temporary.directory("package-#{type}-staging")
 
diff --git a/lib/fpm/package/cpan.rb b/lib/fpm/package/cpan.rb
index a541efc18f..1eb13080c1 100644
--- a/lib/fpm/package/cpan.rb
+++ b/lib/fpm/package/cpan.rb
@@ -51,25 +51,25 @@ def input(package)
     require "json"
 
     if File.exist?(package)
-      moduledir = package
+      @moduledir = package
       result = {}
     else
       result = search(package)
       tarball = download(result, version)
-      moduledir = unpack(tarball)
+      @moduledir = unpack(tarball)
     end
 
     # Read package metadata (name, version, etc)
-    if File.exist?(File.join(moduledir, "META.json"))
-      local_metadata = JSON.parse(File.read(File.join(moduledir, ("META.json"))))
-    elsif File.exist?(File.join(moduledir, ("META.yml")))
+    if File.exist?(File.join(@moduledir, "META.json"))
+      local_metadata = JSON.parse(File.read(File.join(@moduledir, ("META.json"))))
+    elsif File.exist?(File.join(@moduledir, ("META.yml")))
       require "yaml"
-      local_metadata = YAML.load_file(File.join(moduledir, ("META.yml")))
-    elsif File.exist?(File.join(moduledir, "MYMETA.json"))
-      local_metadata = JSON.parse(File.read(File.join(moduledir, ("MYMETA.json"))))
-    elsif File.exist?(File.join(moduledir, ("MYMETA.yml")))
+      local_metadata = YAML.load_file(File.join(@moduledir, ("META.yml")))
+    elsif File.exist?(File.join(@moduledir, "MYMETA.json"))
+      local_metadata = JSON.parse(File.read(File.join(@moduledir, ("MYMETA.json"))))
+    elsif File.exist?(File.join(@moduledir, ("MYMETA.yml")))
       require "yaml"
-      local_metadata = YAML.load_file(File.join(moduledir, ("MYMETA.yml")))
+      local_metadata = YAML.load_file(File.join(@moduledir, ("MYMETA.yml")))
     end
 
     # Merge the MetaCPAN query result and the metadata pulled from the local
@@ -128,9 +128,9 @@ def input(package)
     logger.info("Installing any build or configure dependencies")
 
     if attributes[:cpan_sandbox_non_core?]
-      cpanm_flags = ["-L", build_path("cpan"), moduledir]
+      cpanm_flags = ["-L", build_path("cpan"), @moduledir]
     else
-      cpanm_flags = ["-l", build_path("cpan"), moduledir]
+      cpanm_flags = ["-l", build_path("cpan"), @moduledir]
     end
 
     # This flag causes cpanm to ONLY download dependencies, skipping the target
@@ -195,8 +195,10 @@ def input(package)
         end
       end
     end #no_auto_depends
+  end # def input
 
-    ::Dir.chdir(moduledir) do
+  def build_and_install
+    ::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'
@@ -279,7 +281,7 @@ def input(package)
           if ::Dir.entries(parent).sort == ['.', '..'].sort
             FileUtils.rmdir parent
           else
-            break
+
           end
         end
       end
@@ -299,7 +301,20 @@ def input(package)
         self.architecture = "native"
       end
     end
-  end
+  end # def build_and_install
+
+  def shell_code
+    build_procedure = ""
+    install_procedure = ""
+    if File.exist?("Build.PL") # Module::Build
+      build_procedure = "perl Build.PL\n./Build\n#{attributes[:cpan_test?] ? "./Build test\n" : "" }"
+      install_procedure = "./Build install\n"
+    elsif File.exist?("Makefile.PL") # ExtUtils::MakeMaker
+      build_procedure = "perl Makefile.PL\nmake\n#{attributes[:cpan_test?] ? "make test\n" : "" }"
+      install_procedure = "make install\n"
+    end
+    return { :build_procedure => build_procedure, :install_procedure => install_procedure }
+  end # def shell_code
 
   def unpack(tarball)
     directory = build_path("module")
diff --git a/lib/fpm/package/gem.rb b/lib/fpm/package/gem.rb
index 26a45ad419..fa763f616c 100644
--- a/lib/fpm/package/gem.rb
+++ b/lib/fpm/package/gem.rb
@@ -76,16 +76,25 @@ def staging_path(path=nil)
 
   def input(gem)
     # 'arg'  is the name of the rubygem we should unpack.
-    path_to_gem = download_if_necessary(gem, version)
+    @path_to_gem = download_if_necessary(gem, version)
 
     # Got a good gem now (downloaded or otherwise)
     #
     # 1. unpack it into staging_path
     # 2. take the metadata from it and update our wonderful package with it.
-    load_package_info(path_to_gem)
-    install_to_staging(path_to_gem)
+    load_package_info(@path_to_gem)
   end # def input
 
+  def build_and_install
+    install_to_staging(@path_to_gem)
+  end # def build_and_install
+
+  def shell_code
+    build_procedure = ""
+    install_procedure = "gem install #{File.basename(@path_to_gem)}"
+    return { :build_procedure => build_procedure, :install_procedure => install_procedure }
+  end # def shell_code
+
   def download_if_necessary(gem, gem_version)
     path = gem
     if !File.exist?(path)