lib/polisher/rpm/spec.rb in polisher-0.10.2 vs lib/polisher/rpm/spec.rb in polisher-0.11.1

- old
+ new

@@ -1,445 +1,106 @@ # Polisher RPM Spec Represenation # # Licensed under the MIT license # Copyright (C) 2013-2014 Red Hat, Inc. -require 'polisher/core' -require 'polisher/gem' -require 'polisher/rpm/requirement' -require 'polisher/component' +require 'polisher/rpm/spec/requirements' +require 'polisher/rpm/spec/files' +require 'polisher/rpm/spec/subpackages' +require 'polisher/rpm/spec/check' -module Polisher - deps = ['gem2rpm', 'versionomy', 'active_support', 'active_support/core_ext'] - Component.verify("RPM::Spec", *deps) do - module RPM - class Spec - # RPM Spec Requirement Prefix - def self.requirement_prefix - Requirement.prefix - end +require 'polisher/rpm/spec/updater' +require 'polisher/rpm/spec/parser' +require 'polisher/rpm/spec/comparison' - def requirement_prefix - self.class.requirement_prefix - end +require 'polisher/rpm/spec/gem_files' +require 'polisher/rpm/spec/gem_reference' +require 'polisher/rpm/spec/gem_requirements' - def self.package_prefix - requirement_prefix - end +module Polisher + module RPM + class Spec + include SpecRequirements + include SpecFiles + include SpecSubpackages + include SpecCheck - AUTHOR = "#{ENV['USER']} <#{ENV['USER']}@localhost.localdomain>" + include SpecUpdater + include SpecParser + include SpecComparison - COMMENT_MATCHER = /^\s*#.*/ - GEM_NAME_MATCHER = /^%global\s*gem_name\s(.*)$/ - SPEC_NAME_MATCHER = /^Name:\s*#{package_prefix}-(.*)$/ - SPEC_VERSION_MATCHER = /^Version:\s*(.*)$/ - SPEC_RELEASE_MATCHER = /^Release:\s*(.*)$/ - SPEC_REQUIRES_MATCHER = /^Requires:\s*(.*)$/ - SPEC_BUILD_REQUIRES_MATCHER = /^BuildRequires:\s*(.*)$/ - SPEC_GEM_REQ_MATCHER = /^.*\s*#{requirement_prefix}\((.*)\)(\s*(.*))?$/ - SPEC_SUBPACKAGE_MATCHER = /^%package\s(.*)$/ - SPEC_CHANGELOG_MATCHER = /^%changelog$/ - SPEC_FILES_MATCHER = /^%files$/ - SPEC_SUBPKG_FILES_MATCHER = /^%files\s*(.*)$/ - SPEC_DOC_FILES_MATCHER = /^%files doc$/ - SPEC_CHECK_MATCHER = /^%check$/ + # TODO: make these mixins optional depending on if rpm corresponds to gem + include SpecGemFiles + include SpecGemReference + include SpecGemRequirements - FILE_MACRO_MATCHERS = - [/^%doc\s/, /^%config\s/, /^%attr\s/, - /^%verify\s/, /^%docdir.*/, /^%dir\s/, - /^%defattr.*/, /^%exclude\s/, /^%{gem_instdir}\/+/] + AUTHOR = "#{ENV['USER']} <#{ENV['USER']}@localhost.localdomain>" - FILE_MACRO_REPLACEMENTS = - {"%{_bindir}" => 'bin', - "%{gem_libdir}" => 'lib'} + # metadata keys parsed + # @see [Polisher::RPM::SpecParser::ClassMethods#parse] + METADATA_IDS = [:contents, :gem_name, :version, :release, + :requires, :build_requires, :has_check, :changelog, + :pkg_excludes, :pkg_files, :changelog_entries] - attr_accessor :metadata + COMMENT_MATCHER = /^\s*#.*/ + GEM_NAME_MATCHER = /^%global\s*gem_name\s(.*)$/ + SPEC_NAME_MATCHER = /^Name:\s*#{package_prefix}-(.*)$/ + SPEC_VERSION_MATCHER = /^Version:\s*(.*)$/ + SPEC_RELEASE_MATCHER = /^Release:\s*(.*)$/ + SPEC_REQUIRES_MATCHER = /^Requires:\s*(.*)$/ + SPEC_BUILD_REQUIRES_MATCHER = /^BuildRequires:\s*(.*)$/ + SPEC_GEM_REQ_MATCHER = /^.*\s*#{requirement_prefix}\((.*)\)(\s*(.*))?$/ + SPEC_SUBPACKAGE_MATCHER = /^%package\s(.*)$/ + SPEC_DOC_SUBPACKAGE_MATCHER = /^%package\sdoc$/ + SPEC_CHANGELOG_MATCHER = /^%changelog$/ + SPEC_FILES_MATCHER = /^%files$/ + SPEC_SUBPKG_FILES_MATCHER = /^%files\s*(.*)$/ + SPEC_EXCLUDED_FILE_MATCHER = /^%exclude\s+(.*)$/ + SPEC_PREP_MATCHER = /^%prep$/ + SPEC_CHECK_MATCHER = /^%check$/ + SPEC_DESCRIPTION_MATCHER = /^%description$/ - # Return the currently configured author - def self.current_author - ENV['POLISHER_AUTHOR'] || AUTHOR - end + FILE_MACRO_MATCHERS = + [/^%doc\s/, /^%config\s/, /^%attr\s/, + /^%verify\s/, /^%docdir.*/, /^%dir\s/, /^%defattr.*/, + /^%{gem_instdir}\/+/, /^%{gem_cache}/, /^%{gem_spec}/, /^%{gem_docdir}/] - def initialize(metadata={}) - @metadata = metadata - end + FILE_MACRO_REPLACEMENTS = + {"%{_bindir}" => 'bin', "%{gem_libdir}" => 'lib'} - # Dispatch all missing methods to lookup calls in rpm spec metadata - def method_missing(method, *args, &block) - # proxy to metadata - if @metadata.has_key?(method) - @metadata[method] + attr_accessor :metadata - else - super(method, *args, &block) - end - end + def initialize(metadata = nil) + @metadata = self.class.default_metadata.merge(metadata || {}) + end - # Return gem corresponding to spec name/version - def upstream_gem - @gem ||= Polisher::Gem.from_rubygems gem_name, version - end + # Dispatch all missing methods to lookup calls in rpm spec metadata + def method_missing(method, *args, &block) + # proxy to metadata + return @metadata[method] if @metadata.key?(method) - # Return boolean indicating if spec has a %check section - def has_check? - @metadata.has_key?(:has_check) && @metadata[:has_check] - end + # return nil if metadata value not set + return nil if METADATA_IDS.include?(method) - # Return all the Requires for the specified gem - def requirements_for_gem(gem_name) - @metadata[:requires].nil? ? [] : - @metadata[:requires].select { |r| r.gem_name == gem_name } - end + # set value if invoking metadata setter + id = method[0...-1].intern if method[-1] == '=' + metadata_setter = METADATA_IDS.include?(id) && args.length == 1 + return @metadata[id] = args.first if metadata_setter - # Return all the BuildRequires for the specified gem - def build_requirements_for_gem(gem_name) - @metadata[:build_requires].nil? ? [] : - @metadata[:build_requires].select { |r| r.gem_name == gem_name } - end + # dispatch to default behaviour + super(method, *args, &block) + end - # Return bool indicating if this spec specifies all the - # requirements in the specified gem dependency - # - # @param [Gem::Dependency] gem_dep dependency which to retreive / compare - # requirements - def has_all_requirements_for?(gem_dep) - reqs = self.requirements_for_gem gem_dep.name - # create a spec requirement dependency for each expanded subrequirement, - # verify we can find a match for that - gem_dep.requirement.to_s.split(',').all? { |greq| - Gem2Rpm::Helpers.expand_requirement([greq.split]).all? { |ereq| - tereq = Requirement.new :name => "#{requirement_prefix}(#{gem_dep.name})", - :condition => ereq.first, - :version => ereq.last.to_s - reqs.any? { |req| req.matches?(tereq)} - } - } - end + # Return contents of spec as string + # + # @return [String] string representation of rpm spec + def to_string + contents + end - # Return list of gem dependencies for which we have no - # corresponding requirements - def missing_deps_for(gem) - # Comparison by name here assuming if it is in existing spec, - # spec author will have ensured versions are correct for their purposes - gem.deps.select { |dep| requirements_for_gem(dep.name).empty? } - end - - # Return list of gem dev dependencies for which we have - # no corresponding requirements - def missing_dev_deps_for(gem) - # Same note as in #missing_deps_for above - gem.dev_deps.select { |dep| build_requirements_for_gem(dep.name).empty? } - end - - # Return list of dependencies of upstream gem which - # have not been included - def excluded_deps - missing_deps_for(upstream_gem) - end - - # Return boolean indicating if the specified gem is on excluded list - def excludes_dep?(gem_name) - excluded_deps.any? { |d| d.name == gem_name } - end - - # Return list of dev dependencies of upstream gem which - # have not been included - def excluded_dev_deps - missing_dev_deps_for(upstream_gem) - end - - # Return boolean indicating if the specified gem is on - # excluded dev dep list - def excludes_dev_dep?(gem_name) - excluded_dev_deps.any? { |d| d.name == gem_name } - end - - # Return all gem Requires - def gem_requirements - @metadata[:requires].nil? ? [] : - @metadata[:requires].select { |r| r.gem? } - end - - # Return all gem BuildRequires - def gem_build_requirements - @metadata[:build_requires].nil? ? [] : - @metadata[:build_requires].select { |r| r.gem? } - end - - # Return all non gem Requires - def non_gem_requirements - @metadata[:requires].nil? ? [] : - @metadata[:requires].select { |r| !r.gem? } - end - - # Return all non gem BuildRequires - def non_gem_build_requirements - @metadata[:build_requires].nil? ? [] : - @metadata[:build_requires].select { |r| !r.gem? } - end - - # Return all gem requirements _not_ in the specified gem - def extra_gem_requirements(gem) - gem_reqs = gem.deps.collect { |d| requirements_for_gem(d.name) }.flatten - gem_requirements - gem_reqs - end - - # Return all gem build requirements _not_ in the specified gem - def extra_gem_build_requirements(gem) - gem_reqs = gem.deps.collect { |d| requirements_for_gem(d.name) }.flatten - gem_build_requirements - gem_reqs - end - - # Parse the specified rpm spec and return new RPM::Spec instance from metadata - # - # @param [String] string contents of spec to parse - # @return [Polisher::RPM::Spec] spec instantiated from rpmspec metadata - def self.parse(spec) - in_subpackage = false - in_changelog = false - in_files = false - subpkg_name = nil - meta = {:contents => spec} - spec.each_line { |l| - if l =~ COMMENT_MATCHER - ; - - # TODO support optional gem prefix - elsif l =~ GEM_NAME_MATCHER - meta[:gem_name] = $1.strip - meta[:gem_name] = $1.strip - - elsif l =~ SPEC_NAME_MATCHER && - $1.strip != "%{gem_name}" - meta[:gem_name] = $1.strip - - elsif l =~ SPEC_VERSION_MATCHER - meta[:version] = $1.strip - - elsif l =~ SPEC_RELEASE_MATCHER - meta[:release] = $1.strip - - elsif l =~ SPEC_SUBPACKAGE_MATCHER - subpkg_name = $1.strip - in_subpackage = true - - elsif l =~ SPEC_REQUIRES_MATCHER && - !in_subpackage - meta[:requires] ||= [] - meta[:requires] << RPM::Requirement.parse($1.strip) - - elsif l =~ SPEC_BUILD_REQUIRES_MATCHER && - !in_subpackage - meta[:build_requires] ||= [] - meta[:build_requires] << RPM::Requirement.parse($1.strip) - - elsif l =~ SPEC_CHANGELOG_MATCHER - in_changelog = true - - elsif l =~ SPEC_FILES_MATCHER - subpkg_name = nil - in_files = true - - elsif l =~ SPEC_SUBPKG_FILES_MATCHER - subpkg_name = $1.strip - in_files = true - - elsif l =~ SPEC_CHECK_MATCHER - meta[:has_check] = true - - elsif in_changelog - meta[:changelog] ||= "" - meta[:changelog] << l - - elsif in_files - tgt = subpkg_name.nil? ? meta[:gem_name] : subpkg_name - meta[:files] ||= {} - meta[:files][tgt] ||= [] - - sl = l.strip.unrpmize - meta[:files][tgt] << sl unless sl.blank? - end - } - - meta[:changelog_entries] = meta[:changelog] ? - meta[:changelog].split("\n\n") : [] - meta[:changelog_entries].collect! { |c| c.strip }.compact! - - self.new meta - end - - # Update RPM::Spec metadata to new gem - # - # @param [Polisher::Gem] new_source new gem to update rpmspec to - def update_to(new_source) - update_deps_from(new_source) - update_files_from(new_source) - update_metadata_from(new_source) - end - - private - - # Update spec dependencies from new source - def update_deps_from(new_source) - @metadata[:requires] = - non_gem_requirements + - extra_gem_requirements(new_source) + - new_source.deps.select { |r| !excludes_dep?(r.name) } - .collect { |r| RPM::Requirement.from_gem_dep(r) }.flatten - - @metadata[:build_requires] = - non_gem_build_requirements + - extra_gem_build_requirements(new_source) + - new_source.dev_deps.select { |r| !excludes_dev_dep?(r.name) } - .collect { |r| RPM::Requirement.from_gem_dep(r, true) }.flatten - end - - # Internal helper to update spec files from new source - def update_files_from(new_source) - to_add = new_source.file_paths - @metadata[:files] ||= {} - @metadata[:files].each { |pkg,spec_files| - (new_source.file_paths & to_add).each { |gem_file| - # skip files already included in spec or in dir in spec - has_file = spec_files.any? { |sf| - gem_file.gsub(sf,'') != gem_file - } - - to_add.delete(gem_file) - to_add << gem_file.rpmize if !has_file && - !Gem.ignorable_file?(gem_file) - } - } - - @metadata[:new_files] = to_add.select { |f| !Gem.doc_file?(f) } - @metadata[:new_docs] = to_add - @metadata[:new_files] - end - - # Internal helper to update spec metadata from new source - def update_metadata_from(new_source) - # update to new version - @metadata[:version] = new_source.version - @metadata[:release] = "1%{?dist}" - - # add changelog entry - changelog_entry = <<EOS -* #{Time.now.strftime("%a %b %d %Y")} #{RPM::Spec.current_author} - #{@metadata[:version]}-1 -- Update to version #{new_source.version} -EOS - @metadata[:changelog_entries] ||= [] - @metadata[:changelog_entries].unshift changelog_entry.rstrip - end - - public - - # Return properly formatted rpmspec as string - # - # @return [String] string representation of rpm spec - def to_string - contents = @metadata[:contents] - - # replace version / release - contents.gsub!(SPEC_VERSION_MATCHER, "Version: #{@metadata[:version]}") - contents.gsub!(SPEC_RELEASE_MATCHER, "Release: #{@metadata[:release]}") - - # add changelog entry - cp = contents.index SPEC_CHANGELOG_MATCHER - cpn = contents.index "\n", cp - contents = contents[0...cpn+1] + - @metadata[:changelog_entries].join("\n\n") - - # update requires/build requires - rp = contents.index SPEC_REQUIRES_MATCHER - brp = contents.index SPEC_BUILD_REQUIRES_MATCHER - tp = rp < brp ? rp : brp - - pp = contents.index SPEC_SUBPACKAGE_MATCHER - pp = -1 if pp.nil? - - lrp = contents.rindex SPEC_REQUIRES_MATCHER, pp - lbrp = contents.rindex SPEC_BUILD_REQUIRES_MATCHER, pp - ltp = lrp > lbrp ? lrp : lbrp - - ltpn = contents.index "\n", ltp - - contents.slice!(tp...ltpn) - contents.insert tp, - (@metadata[:requires].collect { |r| "Requires: #{r.str}" } + - @metadata[:build_requires].collect { |r| "BuildRequires: #{r.str}" }).join("\n") - - # add new files - fp = contents.index SPEC_FILES_MATCHER - lfp = contents.index SPEC_SUBPKG_FILES_MATCHER, fp + 1 - lfp = contents.index SPEC_CHANGELOG_MATCHER if lfp.nil? - - contents.insert lfp - 1, @metadata[:new_files].join("\n") + "\n" - - # add new doc files - fp = contents.index SPEC_DOC_FILES_MATCHER - fp = contents.index SPEC_FILES_MATCHER if fp.nil? - lfp = contents.index SPEC_SUBPKG_FILES_MATCHER, fp + 1 - lfp = contents.index SPEC_CHANGELOG_MATCHER if lfp.nil? - - contents.insert lfp - 1, @metadata[:new_docs].join("\n") + "\n" - - # return new contents - contents - end - - # Compare this spec to a sepecified upstream gem source - # and return result. - # - # upstream_source should be an instance of Polisher::Gem, - # Polisher::Gemfile, or other class defining a 'deps' - # accessor that returns an array of Gem::Requirement dependencies - # - # Result will be a hash containing the shared dependencies as - # well as those that differ and their respective differences - def compare(upstream_source) - same = {} - diff = {} - upstream_source.deps.each do |d| - spec_reqs = self.requirements_for_gem(d.name) - spec_reqs_specifier = spec_reqs.empty? ? nil : - spec_reqs.collect { |req| req.specifier } - - if spec_reqs.nil? - diff[d.name] = {:spec => nil, - :upstream => d.requirement.to_s} - - elsif !spec_reqs.any? { |req| req.matches?(d) } || - !self.has_all_requirements_for?(d) - diff[d.name] = {:spec => spec_reqs_specifier, - :upstream => d.requirement.to_s} - - elsif !diff.has_key?(d.name) - same[d.name] = {:spec => spec_reqs_specifier, - :upstream => d.requirement.to_s } - end - end - - @metadata[:requires].each do |req| - next unless req.gem? - - upstream_dep = upstream_source.deps.find { |d| d.name == req.gem_name } - - if upstream_dep.nil? - diff[req.gem_name] = {:spec => req.specifier, - :upstream => nil} - - elsif !req.matches?(upstream_dep) - diff[req.gem_name] = {:spec => req.specifier, - :upstream => upstream_dep.requirement.to_s } - - elsif !diff.has_key?(req.gem_name) - same[req.gem_name] = {:spec => req.specifier, - :upstream => upstream_dep.requirement.to_s } - end - end unless @metadata[:requires].nil? - - {:same => same, :diff => diff} - end - - end # class Spec - end # module RPM - end # Component.verify("RPM::Spec") + # Return length of contents + def length + contents.length + end + end # class Spec + end # module RPM end # module Polisher