diff --git a/lib/fpm/command.rb b/lib/fpm/command.rb index 115ea5b31..d799bae42 100644 --- a/lib/fpm/command.rb +++ b/lib/fpm/command.rb @@ -51,6 +51,9 @@ def help(*args) option ["-s", "--input-type"], "INPUT_TYPE", "the package type to use as input (gem, rpm, python, etc)", :attribute_name => :input_type + option ["-r", "--recipe"], "RECIPE_FILE", + "path to recipe file used for building a source based package", + :attribute_name => :recipe_file option ["-C", "--chdir"], "CHDIR", "Change directory to here before searching for files", :attribute_name => :chdir @@ -311,7 +314,7 @@ def execute return 1 end input_class = FPM::Package.types[input_type] - output_class = FPM::Package.types[output_type] + output_class = FPM::Package.types[output_type] || FPM::SourcePackage.types[output_type] input = input_class.new @@ -470,7 +473,7 @@ def execute end # Convert to the output type - output = input.convert(output_class) + output = input.convert(output_class, recipe_file) # Provide any template values as methods on the package. if template_scripts? @@ -685,21 +688,29 @@ def validate "Missing required -t flag. What package output did you want?") # Verify the types requested are valid - types = FPM::Package.types.keys.sort + types = FPM::Package.types.keys.concat(FPM::SourcePackage.types.keys).sort @command.input_type.tap do |val| next if val.nil? - mandatory(FPM::Package.types.include?(val), + mandatory(types.include?(val), "Invalid input package -s flag) type #{val.inspect}. " \ "Expected one of: #{types.join(", ")}") end @command.output_type.tap do |val| next if val.nil? - mandatory(FPM::Package.types.include?(val), + mandatory(types.include?(val), "Invalid output package (-t flag) type #{val.inspect}. " \ "Expected one of: #{types.join(", ")}") end + # we cannot output a source based package without a recipe + @command.recipe_file.tap do |val| + is_source_package = FPM::SourcePackage.types.keys.include?(@command.output_type) + mandatory(is_source_package ? val : true, + "source based package output type #{@command.output_type} " \ + "requires a recipe file (via -r flag).") + end + @command.dependencies.tap do |dependencies| # Verify dependencies don't include commas (#257) dependencies.each do |dep| diff --git a/lib/fpm/package.rb b/lib/fpm/package.rb index 1bbbd5d7a..3bfd9bd1c 100644 --- a/lib/fpm/package.rb +++ b/lib/fpm/package.rb @@ -191,12 +191,12 @@ def type end # def type # Convert this package to a new package type - def convert(klass) + def convert(klass, recipe_file) logger.info("Converting #{self.type} to #{klass.type}") exclude - pkg = klass.new + pkg = recipe_file ? klass.new(recipe_file) : klass.new pkg.cleanup_staging # purge any directories that may have been created by klass.new # copy other bits @@ -414,7 +414,9 @@ class << self # Lets us track all known FPM::Package subclasses def inherited(klass) @subclasses ||= {} - @subclasses[klass.name.gsub(/.*:/, "").downcase] = klass + unless klass.name == 'FPM::SourcePackage' + @subclasses[klass.name.gsub(/.*:/, "").downcase] = klass + end end # def self.inherited # Get a list of all known package subclasses diff --git a/lib/fpm/package/srpm.rb b/lib/fpm/package/srpm.rb new file mode 100644 index 000000000..916acb1e3 --- /dev/null +++ b/lib/fpm/package/srpm.rb @@ -0,0 +1,64 @@ +require "fpm/source_package" +require "fileutils" + +class FPM::Package::SRPM < FPM::SourcePackage + + def output(output_path) + output_check(output_path) + + %w(BUILD RPMS SRPMS SOURCES SPECS).each { |d| FileUtils.mkdir_p(build_path(d)) } + + spec_file = staging_path("#{name}.spec") + # puts "spec_file = #{spec_file}" + + File.open(spec_file, "w+") do |file| + + if name = name() + file.write("Name: #{name}\n") + end + + if version = version() + file.write("Version: #{version}\n") + end + + if license = license() + file.write("License: #{license}\n") + end + + if release = iteration() + file.write("Release: #{release}\n") + else + file.write("Release: 1\n") + end + + if download_url = recipe.download + download_source(download_url.chomp, build_path("SOURCES")) + file.write("Url: #{download_url}\n") + file.write("Source0: #{download_url}\n") + end + + if summary = description() + file.write("Summary: #{summary}\n") + file.write("%description\n#{summary}\n\n") + end + + if prebuild_instructions = recipe.prebuild + file.write("%prep\n#{prebuild_instructions}\n\n") + end + + if build_instructions = recipe.build + file.write("%build\n#{build_instructions}\n\n") + end + + if install_instructions = recipe.install + file.write("%install\n#{install_instructions}\n\n") + end + end + + safesystem("rpmbuild", "-bs", "--define", "_topdir #{build_path}", spec_file) + + ::Dir["#{build_path}/SRPMS/*.src.rpm"].each do |srpm| + FileUtils.cp(srpm, output_path) + end + end +end diff --git a/lib/fpm/recipe.rb b/lib/fpm/recipe.rb new file mode 100644 index 000000000..64896ffd4 --- /dev/null +++ b/lib/fpm/recipe.rb @@ -0,0 +1,50 @@ +require "fpm/util" + +class FPM::Recipe + + RECIPE_SECTIONS = [ :download, + :prebuild, + :build, + :install, + ] + + attr_accessor(*RECIPE_SECTIONS) + + def initialize(recipe_file) + recipe_file = File.expand_path(recipe_file) + + unless File.file?(recipe_file) + STDERR.puts "recipe file #{recipe_file} does not exist" + exit 1 + end + + # parse recipe_file + cur_section = nil + cur_val = nil + File.foreach(recipe_file) do |line| + line.gsub!(/ *#.*| +$/, '') + next if line =~ /^$/ + if section_name = section_tag?(line) + if !cur_section # this is the first section tag we have seen. + cur_section = section_name + cur_val = "" + else + instance_variable_set("@#{cur_section}", cur_val) + cur_section = section_name + cur_val = "" + end + else # this line is not a section + cur_val += line + end + end + if cur_section and cur_val + self.instance_variable_set("@#{cur_section}", cur_val) + end + end + + private + def section_tag?(line) + # Note that no trimming of +line+ happens here + line[/^\[(#{RECIPE_SECTIONS.join("|")})\]$/, 1] + end +end diff --git a/lib/fpm/source_package.rb b/lib/fpm/source_package.rb new file mode 100644 index 000000000..9d6f7a0f0 --- /dev/null +++ b/lib/fpm/source_package.rb @@ -0,0 +1,29 @@ +require "fpm/package" +require "fpm/recipe" + +# This class is the parent of all source based packages. +# If you want to implement a source based FPM package type, you'll inherit from +# this. +class FPM::SourcePackage < FPM::Package + attr_reader :recipe + + def initialize(recipe_file) + super() + @recipe = FPM::Recipe.new(recipe_file) + end + + def download_source(url, dir) + safesystem("wget", "-P", dir, url) + end + + class << self + def inherited(klass) + @subclasses ||= {} + @subclasses[klass.name.gsub(/.*:/, "").downcase] = klass + end + + def types + return @subclasses + end + end +end