Class: FPM::Package::Deb
- Inherits:
-
FPM::Package
- Object
- FPM::Package
- FPM::Package::Deb
- Defined in:
- lib/fpm/package/deb.rb
Overview
Support for debian packages (.deb files)
This class supports both input and output of packages.
Constant Summary collapse
- SCRIPT_MAP =
Map of what scripts are named.
{ :before_install => "preinst", :after_install => "postinst", :before_remove => "prerm", :after_remove => "postrm", :after_purge => "postrm", }
- COMPRESSION_TYPES =
The list of supported compression types. Default is gz (gzip)
[ "gz", "bzip2", "xz", "zst", "none" ]
- VERSION_FIELD_PATTERN =
Version string docs here: www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-version The format is: [epoch:]upstream_version. epoch - This is a single (generally small) unsigned integer upstream_version - must contain only alphanumerics 6 and the characters . + - ~ debian_revision - only alphanumerics and the characters + . ~
/ (?:(?:[0-9]+):)? # The epoch, an unsigned int (?:[A-Za-z0-9+~.-]+) # upstream version, probably should not contain dashes? (?:-[A-Za-z0-9+~.]+)? # debian_revision /x
- RELATIONSHIP_FIELD_PATTERN =
Version field pattern
/^ (?<name>[A-z0-9][A-z0-9_.-]+) (?:\s*\((?<relation>[<>=]+)\s(?<version>#{VERSION_FIELD_PATTERN})\))? $/x
Instance Attribute Summary
Attributes inherited from FPM::Package
#attributes, #attrs, #category, #config_files, #conflicts, #dependencies, #description, #directories, #epoch, #iteration, #license, #maintainer, #provides, #replaces, #scripts, #url, #vendor
Instance Method Summary collapse
-
#add_path(path, allconfigs) ⇒ Object
expand recursively a given path to be put in allconfigs.
-
#architecture ⇒ Object
Return the architecture.
-
#converted_from(origin) ⇒ Object
def output.
-
#data_tar_flags ⇒ Object
def to_s.
-
#initialize(*args) ⇒ Deb
constructor
A new instance of Deb.
- #input(input_path) ⇒ Object
-
#name ⇒ Object
Get the name of this package.
-
#output(output_path) ⇒ Object
def extract_files.
-
#prefix ⇒ Object
def name.
- #to_s(format = nil) ⇒ Object
-
#version ⇒ Object
def prefix.
Methods inherited from FPM::Package
apply_options, #build_path, #cleanup, #cleanup_build, #cleanup_staging, #convert, default_attributes, #edit_file, #files, inherited, option, #script, #staging_path, type, #type, types
Methods included from Util
#ar_cmd, #ar_cmd_deterministic?, #copied_entries, #copy_entry, #copy_metadata, #default_shell, #erbnew, #execmd, #expand_pessimistic_constraints, #logger, #program_exists?, #program_in_path?, #safesystem, #safesystemout, #tar_cmd, #tar_cmd_supports_sort_names_and_set_mtime?
Constructor Details
#initialize(*args) ⇒ Deb
Returns a new instance of Deb.
248 249 250 251 |
# File 'lib/fpm/package/deb.rb', line 248 def initialize(*args) super(*args) attributes[:deb_priority] = "optional" end |
Instance Method Details
#add_path(path, allconfigs) ⇒ Object
expand recursively a given path to be put in allconfigs
1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 |
# File 'lib/fpm/package/deb.rb', line 1106 def add_path(path, allconfigs) # Strip leading / path = path[1..-1] if path[0,1] == "/" cfg_path = File.(path, staging_path) Find.find(cfg_path) do |p| if File.file?(p) allconfigs << p.gsub("#{staging_path}/", '') end end end |
#architecture ⇒ Object
Return the architecture. This will default to native if not yet set. It will also try to use dpkg and ‘uname -m’ to figure out what the native ‘architecture’ value should be.
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/fpm/package/deb.rb', line 258 def architecture if @architecture.nil? or @architecture == "native" # Default architecture should be 'native' which we'll need to ask the # system about. if program_in_path?("dpkg") @architecture = %x{dpkg --print-architecture 2> /dev/null}.chomp if $?.exitstatus != 0 or @architecture.empty? # if dpkg fails or emits nothing, revert back to uname -m @architecture = %x{uname -m}.chomp end else @architecture = %x{uname -m}.chomp end end case @architecture when "x86_64" # Debian calls x86_64 "amd64" @architecture = "amd64" when "aarch64" # Debian calls aarch64 "arm64" @architecture = "arm64" when "noarch" # Debian calls noarch "all" @architecture = "all" when "ppc64le" # Debian calls ppc64le "ppc64el" @architecture = "ppc64el" end return @architecture end |
#converted_from(origin) ⇒ Object
def output
753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 |
# File 'lib/fpm/package/deb.rb', line 753 def converted_from(origin) self.dependencies = self.dependencies.collect do |dep| fix_dependency(dep) end.flatten # If an invalid depends field was found i.e. /bin.sh then fix_depends will blank it # Make sure we remove this blank here self.dependencies = self.dependencies.reject { |p| p.empty? } self.provides = self.provides.collect do |provides| fix_provides(provides) end.flatten # If an invalid provides field was found i.e. mypackage(arch) then fix_provides will blank it # Make sure we remove this blank here self.provides = self.provides.reject { |p| p.empty? } if origin == FPM::Package::CPAN # The fpm cpan code presents dependencies and provides fields as perl(ModuleName) # so we'll need to convert them to something debian supports. # Replace perl(ModuleName) > 1.0 with Debian-style perl-ModuleName (> 1.0) perldepfix = lambda do |dep| m = dep.match(/perl\((?<name>[A-Za-z0-9_:]+)\)\s*(?<op>.*$)/) if m.nil? # 'dep' syntax didn't look like 'perl(Name) > 1.0' dep else # Also replace '::' in the perl module name with '-' modulename = m["name"].gsub("::", "-") # Fix any upper-casing or other naming concerns Debian has about packages name = "#{attributes[:cpan_package_name_prefix]}-#{modulename}" if m["op"].empty? name else # 'dep' syntax was like this (version constraint): perl(Module) > 1.0 "#{name} (#{m["op"]})" end end end rejects = [ "perl(vars)", "perl(warnings)", "perl(strict)", "perl(Config)" ] self.dependencies = self.dependencies.reject do |dep| # Reject non-module Perl dependencies like 'vars' and 'warnings' rejects.include?(dep) end.collect(&perldepfix).collect(&method(:fix_dependency)) # Also fix the Provides field 'perl(ModuleName) = version' to be 'perl-modulename (= version)' self.provides = self.provides.collect(&perldepfix).collect(&method(:fix_provides)) end # if origin == FPM::Packagin::CPAN if origin == FPM::Package::Deb changelog_path = staging_path("usr/share/doc/#{name}/changelog.Debian.gz") if File.exist?(changelog_path) logger.debug("Found a deb changelog file, using it.", :path => changelog_path) attributes[:deb_changelog] = build_path("deb_changelog") File.open(attributes[:deb_changelog], "w") do |deb_changelog| Zlib::GzipReader.open(changelog_path) do |gz| IO::copy_stream(gz, deb_changelog) end end File.unlink(changelog_path) end end if origin == FPM::Package::Deb changelog_path = staging_path("usr/share/doc/#{name}/changelog.gz") if File.exist?(changelog_path) logger.debug("Found an upstream changelog file, using it.", :path => changelog_path) attributes[:deb_upstream_changelog] = build_path("deb_upstream_changelog") File.open(attributes[:deb_upstream_changelog], "w") do |deb_upstream_changelog| Zlib::GzipReader.open(changelog_path) do |gz| IO::copy_stream(gz, deb_upstream_changelog) end end File.unlink(changelog_path) end end if origin == FPM::Package::Gem # fpm's gem input will have provides as "rubygem-name = version" # and we need to convert this to Debian-style "rubygem-name (= version)" self.provides = self.provides.collect do |provides| m = /^(#{attributes[:gem_package_name_prefix]})-([^\s]+)\s*=\s*(.*)$/.match(provides) if m "#{m[1]}-#{m[2]} (= #{m[3]})" else provides end end end end |
#data_tar_flags ⇒ Object
def to_s
1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 |
# File 'lib/fpm/package/deb.rb', line 1276 def data_tar_flags data_tar_flags = [] if attributes[:deb_use_file_permissions?].nil? if !attributes[:deb_user].nil? if attributes[:deb_user] == 'root' data_tar_flags += [ "--numeric-owner", "--owner", "0" ] else data_tar_flags += [ "--owner", attributes[:deb_user] ] end end if !attributes[:deb_group].nil? if attributes[:deb_group] == 'root' data_tar_flags += [ "--numeric-owner", "--group", "0" ] else data_tar_flags += [ "--group", attributes[:deb_group] ] end end end return data_tar_flags end |
#input(input_path) ⇒ Object
339 340 341 342 |
# File 'lib/fpm/package/deb.rb', line 339 def input(input_path) extract_info(input_path) extract_files(input_path) end |
#name ⇒ Object
Get the name of this package. See also FPM::Package#name
This accessor actually modifies the name if it has some invalid or unwise characters.
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 |
# File 'lib/fpm/package/deb.rb', line 294 def name if @name =~ /[A-Z]/ logger.warn("Debian tools (dpkg/apt) don't do well with packages " \ "that use capital letters in the name. In some cases it will " \ "automatically downcase them, in others it will not. It is confusing." \ " Best to not use any capital letters at all. I have downcased the " \ "package name for you just to be safe.", :oldname => @name, :fixedname => @name.downcase) @name = @name.downcase end if @name.include?("_") logger.info("Debian package names cannot include underscores; " \ "automatically converting to dashes", :name => @name) @name = @name.gsub(/[_]/, "-") end if @name.include?(" ") logger.info("Debian package names cannot include spaces; " \ "automatically converting to dashes", :name => @name) @name = @name.gsub(/[ ]/, "-") end return @name end |
#output(output_path) ⇒ Object
def extract_files
498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 |
# File 'lib/fpm/package/deb.rb', line 498 def output(output_path) self.provides = self.provides.collect { |p| fix_provides(p) } self.provides.each do |provide| if !valid_provides_field?(provide) raise FPM::InvalidPackageConfiguration, "Found invalid Provides field values (#{provide.inspect}). This is not valid in a Debian package." end end output_check(output_path) # Abort if the target path already exists. # create 'debian-binary' file, required to make a valid debian package File.write(build_path("debian-binary"), "2.0\n") # If we are given --deb-shlibs but no --after-install script, we # should implicitly create a before/after scripts that run ldconfig if attributes[:deb_shlibs] if !script?(:after_install) logger.info("You gave --deb-shlibs but no --after-install, so " \ "I am adding an after-install script that runs " \ "ldconfig to update the system library cache") scripts[:after_install] = template("deb/ldconfig.sh.erb").result(binding) end if !script?(:after_remove) logger.info("You gave --deb-shlibs but no --after-remove, so " \ "I am adding an after-remove script that runs " \ "ldconfig to update the system library cache") scripts[:after_remove] = template("deb/ldconfig.sh.erb").result(binding) end end if attributes[:source_date_epoch].nil? and not attributes[:source_date_epoch_default].nil? attributes[:source_date_epoch] = attributes[:source_date_epoch_default] end if attributes[:source_date_epoch] == "0" logger.error("Alas, ruby's Zlib::GzipWriter does not support setting an mtime of zero. Aborting.") raise FPM::InvalidPackageConfiguration, "#{name}: source_date_epoch of 0 not supported." end if not attributes[:source_date_epoch].nil? and not ar_cmd_deterministic? logger.error("Alas, could not find an ar that can handle -D option. Try installing recent gnu binutils. Aborting.") raise FPM::InvalidPackageConfiguration, "#{name}: ar is insufficient to support source_date_epoch." end if not attributes[:source_date_epoch].nil? and not tar_cmd_supports_sort_names_and_set_mtime? logger.error("Alas, could not find a tar that can set mtime and sort. Try installing recent gnu tar. Aborting.") raise FPM::InvalidPackageConfiguration, "#{name}: tar is insufficient to support source_date_epoch." end attributes[:deb_systemd] = [] attributes.fetch(:deb_systemd_list, []).each do |systemd| name = File.basename(systemd) extname = File.extname(name) name_with_extension = if extname.empty? "#{name}.service" elsif [".service", ".timer"].include?(extname) name else raise FPM::InvalidPackageConfiguration, "Invalid systemd unit file extension: #{extname}. Expected .service or .timer, or no extension." end dest_systemd = staging_path(File.join(attributes[:deb_systemd_path], "#{name_with_extension}")) mkdir_p(File.dirname(dest_systemd)) FileUtils.cp(systemd, dest_systemd) File.chmod(0644, dest_systemd) attributes[:deb_systemd] << name_with_extension end if script?(:before_upgrade) or script?(:after_upgrade) or attributes[:deb_systemd].any? puts "Adding action files" if script?(:before_install) or script?(:before_upgrade) scripts[:before_install] = template("deb/preinst_upgrade.sh.erb").result(binding) end if script?(:before_remove) or not attributes[:deb_systemd].empty? scripts[:before_remove] = template("deb/prerm_upgrade.sh.erb").result(binding) end if script?(:after_install) or script?(:after_upgrade) or attributes[:deb_systemd].any? scripts[:after_install] = template("deb/postinst_upgrade.sh.erb").result(binding) end if script?(:after_remove) scripts[:after_remove] = template("deb/postrm_upgrade.sh.erb").result(binding) end if script?(:after_purge) scripts[:after_purge] = template("deb/postrm_upgrade.sh.erb").result(binding) end end # There are two changelogs that may appear: # - debian-specific changelog, which should be archived as changelog.Debian.gz # - upstream changelog, which should be archived as changelog.gz # see https://www.debian.org/doc/debian-policy/ch-docs.html#s-changelogs # Write the changelog.Debian.gz file dest_changelog = File.join(staging_path, "usr/share/doc/#{name}/changelog.Debian.gz") mkdir_p(File.dirname(dest_changelog)) File.new(dest_changelog, "wb", 0644).tap do |changelog| Zlib::GzipWriter.new(changelog, Zlib::BEST_COMPRESSION).tap do |changelog_gz| if not attributes[:source_date_epoch].nil? changelog_gz.mtime = attributes[:source_date_epoch].to_i end if attributes[:deb_changelog] logger.info("Writing user-specified changelog", :source => attributes[:deb_changelog]) File.new(attributes[:deb_changelog]).tap do |fd| chunk = nil # Ruby 1.8.7 doesn't have IO#copy_stream changelog_gz.write(chunk) while chunk = fd.read(16384) end.close else logger.info("Creating boilerplate changelog file") changelog_gz.write(template("deb/changelog.erb").result(binding)) end end.close end # No need to close, GzipWriter#close will close it. # Write the changelog.gz file (upstream changelog) dest_upstream_changelog = File.join(staging_path, "usr/share/doc/#{name}/changelog.gz") if attributes[:deb_upstream_changelog] File.new(dest_upstream_changelog, "wb", 0644).tap do |changelog| Zlib::GzipWriter.new(changelog, Zlib::BEST_COMPRESSION).tap do |changelog_gz| if not attributes[:source_date_epoch].nil? changelog_gz.mtime = attributes[:source_date_epoch].to_i end logger.info("Writing user-specified upstream changelog", :source => attributes[:deb_upstream_changelog]) File.new(attributes[:deb_upstream_changelog]).tap do |fd| chunk = nil # Ruby 1.8.7 doesn't have IO#copy_stream changelog_gz.write(chunk) while chunk = fd.read(16384) end.close end.close end # No need to close, GzipWriter#close will close it. end if File.exist?(dest_changelog) and not File.exist?(dest_upstream_changelog) # see https://www.debian.org/doc/debian-policy/ch-docs.html#s-changelogs File.rename(dest_changelog, dest_upstream_changelog) end attributes.fetch(:deb_init_list, []).each do |init| name = File.basename(init, ".init") dest_init = File.join(staging_path, "etc/init.d/#{name}") mkdir_p(File.dirname(dest_init)) FileUtils.cp init, dest_init File.chmod(0755, dest_init) end attributes.fetch(:deb_default_list, []).each do |default| name = File.basename(default, ".default") dest_default = File.join(staging_path, "etc/default/#{name}") mkdir_p(File.dirname(dest_default)) FileUtils.cp default, dest_default File.chmod(0644, dest_default) end attributes.fetch(:deb_upstart_list, []).each do |upstart| name = File.basename(upstart, ".upstart") dest_init = staging_path("etc/init.d/#{name}") name = "#{name}.conf" if !(name =~ /\.conf$/) dest_upstart = staging_path("etc/init/#{name}") mkdir_p(File.dirname(dest_upstart)) FileUtils.cp(upstart, dest_upstart) File.chmod(0644, dest_upstart) # Install an init.d shim that calls upstart mkdir_p(File.dirname(dest_init)) FileUtils.ln_s("/lib/init/upstart-job", dest_init) end attributes.fetch(:deb_systemd_list, []).each do |systemd| name = File.basename(systemd) extname = File.extname(systemd) name_with_extension = extname.empty? ? "#{name}.service" : name dest_systemd = staging_path(File.join(attributes[:deb_systemd_path], "#{name_with_extension}")) mkdir_p(File.dirname(dest_systemd)) FileUtils.cp(systemd, dest_systemd) File.chmod(0644, dest_systemd) end write_control_tarball # Tar up the staging_path into data.tar.{compression type} case self.attributes[:deb_compression] when "gz", nil datatar = build_path("data.tar.gz") controltar = build_path("control.tar.gz") compression_flags = ["-z"] # gnu tar obeys GZIP environment variable with options for gzip; -n = forget original filename and date = {"GZIP" => "-#{self.attributes[:deb_compression_level] || 9}" + "#{'n' if tar_cmd_supports_sort_names_and_set_mtime? and not attributes[:source_date_epoch].nil?}"} when "bzip2" datatar = build_path("data.tar.bz2") controltar = build_path("control.tar.gz") compression_flags = ["-j"] = {"BZIP" => "-#{self.attributes[:deb_compression_level] || 9}"} when "xz" datatar = build_path("data.tar.xz") controltar = build_path("control.tar.xz") compression_flags = ["-J"] = {"XZ_OPT" => "-#{self.attributes[:deb_compression_level] || 3}"} when "zst" datatar = build_path("data.tar.zst") controltar = build_path("control.tar.zst") compression_flags = ["--use-compress-program", "zstd"] = {"ZSTD_CLEVEL" => "-#{self.attributes[:deb_compression_level] || 3}"} when "none" datatar = build_path("data.tar") controltar = build_path("control.tar") compression_flags = [] = {} else raise FPM::InvalidPackageConfiguration, "Unknown compression type '#{self.attributes[:deb_compression]}'" end args = [ tar_cmd, "-C", staging_path ] + compression_flags + data_tar_flags + [ "-cf", datatar, "." ] if tar_cmd_supports_sort_names_and_set_mtime? and not attributes[:source_date_epoch].nil? # Use gnu tar options to force deterministic file order and timestamp args += ["--sort=name", ("--mtime=@%s" % attributes[:source_date_epoch])] end args.unshift() safesystem(*args) # pack up the .deb, which is just an 'ar' archive with 3 files # the 'debian-binary' file has to be first File.(output_path).tap do |output_path| ::Dir.chdir(build_path) do safesystem(*ar_cmd, output_path, "debian-binary", controltar, datatar) end end # if a PACKAGENAME.changes file is to be created if self.attributes[:deb_generate_changes?] distribution = self.attributes[:deb_dist] # gather information about the files to distribute files = [ output_path ] changes_files = [] files.each do |path| changes_files.push({ :name => path, :size => File.size?(path), :md5sum => Digest::MD5.file(path).hexdigest, :sha1sum => Digest::SHA1.file(path).hexdigest, :sha256sum => Digest::SHA2.file(path).hexdigest, }) end # write change infos to .changes file changes_path = File.basename(output_path, '.deb') + '.changes' changes_data = template("deb/deb.changes.erb").result(binding) File.write(changes_path, changes_data) logger.log("Created changes", :path => changes_path) end # if deb_generate_changes end |
#prefix ⇒ Object
def name
320 321 322 |
# File 'lib/fpm/package/deb.rb', line 320 def prefix return (attributes[:prefix] or "/") end |
#to_s(format = nil) ⇒ Object
1270 1271 1272 1273 1274 |
# File 'lib/fpm/package/deb.rb', line 1270 def to_s(format=nil) # Default format if nil # git_1.7.9.3-1_amd64.deb return super(format.nil? ? "NAME_FULLVERSION_ARCH.EXTENSION" : format) end |
#version ⇒ Object
def prefix
324 325 326 327 328 329 330 331 332 333 334 335 336 337 |
# File 'lib/fpm/package/deb.rb', line 324 def version if @version.kind_of?(String) if @version.start_with?("v") && @version.gsub(/^v/, "") =~ /^#{VERSION_FIELD_PATTERN}$/ logger.warn("Debian 'Version' field needs to start with a digit. I was provided '#{@version}' which seems like it just has a 'v' prefix to an otherwise-valid Debian version, I'll remove the 'v' for you.") @version = @version.gsub(/^v/, "") end if @version !~ /^#{VERSION_FIELD_PATTERN}$/ raise FPM::InvalidPackageConfiguration, "The version looks invalid for Debian packages. Debian version field must contain only alphanumerics and . (period), + (plus), - (hyphen) or ~ (tilde). I have '#{@version}' which which isn't valid." end end return @version end |