class Revision::Releasable

Constants

BUILD_CONFIGURATION_DEFAULT
BUILD_DIR_BASE_NAME
BUILD_TARGET_DEFAULT
CONFIG_FILE_NAME
RELATIVE_PATH_TO_BOOTLOADER

RELATIVE_PATH_TO_BOOTLOADER = File.join(“..”,“bootloader”)

REVISION_PLACEHOLDER

Attributes

artefacts[R]
build_steps[R]
git_tag_prefix[R]
id[R]
revision[R]
root[R]
secondary_revisions[R]

Public Class Methods

from_folder(root = Dir.getwd) click to toggle source

Instantiate the Releasables defined in releasables.yaml

# File lib/revision/releasable.rb, line 34
def self.from_folder(root = Dir.getwd)
  config = load_definitions(:root=>root)

  releasables = {}
  config[:releasables].each do |config_entry|
    if config_entry[:folder]
      #Load entries from a nested releasable definition file
      releasables = releasables.merge(from_folder(File.join(root, config_entry[:folder])))
    else
      r = new(root: root, config: config_entry)
      releasables[r.id] = r
    end
  end
  releasables
end
load_definitions(root: nil) click to toggle source

Load a file in yaml format containing one or more releasable definitions @param root [String] An optional root directory argument @return [Hash] Contents of the yaml file

# File lib/revision/releasable.rb, line 25
def self.load_definitions(root: nil)
  root ||= Dir.getwd
  config_file = File.join(root, CONFIG_FILE_NAME)
  raise Errors::NoDefinition.new(root) unless File.exist?(config_file)
  puts "Loading releasable definitions from #{config_file} ..."
  YAML.load_file(config_file)
end
new(root: nil, config: {}) click to toggle source
# File lib/revision/releasable.rb, line 60
def initialize(root: nil, config: {})

  root ||= Dir.getwd
  @root = Pathname.new(root).realpath
  @id = config[:id] || File.basename(@root)
  @revision = _build_revision_info(config[:revision], embed_changelog: true)
  @secondary_revisions = config[:secondary_revisions].nil? ? [] : config[:secondary_revisions].map { |r| _build_revision_info(r, embed_changelog:false)}
  @git_tag_prefix = config[:revision][:git_tag_prefix].nil? ? 'v' : "#{config[:revision][:git_tag_prefix]}_v"
  # Legacy definition syntax compatibility
  @build_def = config[:build] ? config[:build] : { environment: { variables: {}}, steps: config[:build_steps]}
  @artefacts = config[:artefacts] || []
  @artefacts.each { |a| a[:dest] ||= a[:src] } unless @artefacts.nil? || @artefacts.empty?
  @config = config
end

Public Instance Methods

_build_revision_info(definition, embed_changelog: true) click to toggle source

def git_repo

@git_repo ||= Rugged::Repository.discover('.')
puts 'WARNING: No git repo found in this directory or its ancestors' if @git_repo.nil?
@git_repo

end

# File lib/revision/releasable.rb, line 56
def _build_revision_info(definition, embed_changelog: true)
  Info.new(File.join(@root,definition[:src]), regex: definition[:regex], comment_prefix: definition[:comment_prefix], embed_changelog: embed_changelog)
end
archive() click to toggle source
# File lib/revision/releasable.rb, line 208
def archive
  puts "Archiving #{@artefacts.length} build artefacts as #{archive_name}..."
  amap = artefact_map
  if File.exist?(archive_name)
    puts "... deleting existing archive"
    File.delete(archive_name)
  end
  Zip::File.open(archive_name, Zip::File::CREATE) do |zipfile|
    amap.each.with_index(1) do |entry, idx|
      src, dest = entry
      #TODO: Add directory processing....
      puts "... (#{idx}/#{amap.length}) #{src} => #{dest}"
      zipfile.add(dest, src)
    end
    puts "... embedding revision history as #{changelog_name} "
    zipfile.get_output_stream(changelog_name) { |os| output_changelog(os)}
  end

  if @config.dig(:archive)
    archive_root = File.expand_path(@config[:archive])
    puts "... moving #{archive_name} to #{archive_root}"
    FileUtils.mkdir_p(archive_root)
    FileUtils.mv(archive_name, archive_root)
  end
end
archive_name() click to toggle source
# File lib/revision/releasable.rb, line 183
def archive_name
  "#{@id}_v#{@revision}.zip"
end
artefact_map(dest_prefix = '') click to toggle source
# File lib/revision/releasable.rb, line 191
def artefact_map(dest_prefix = '')
  amap = {}
  @artefacts.each_with_index do |a, index|
    src = a[:src].gsub(REVISION_PLACEHOLDER, @revision.to_s)
    dest = a[:dest].gsub(REVISION_PLACEHOLDER, @revision.to_s)
    if Gem.win_platform? && !src.end_with?('.exe') && File.exist?(File.join(@root, src + '.exe'))
      puts "... windows platform -- appending '.exe' (#{src})"
      src += '.exe'
      dest += '.exe' unless dest.end_with?('.exe')
    end
    src = File.join(@root,src)
    dest = dest_prefix.empty? ? dest : File.join(dest_prefix, dest)
    amap[src] = dest
  end
  amap
end
build(skip_steps = 0) click to toggle source
# File lib/revision/releasable.rb, line 110
def build(skip_steps = 0)
  if @build_def.dig(:environment, :variables)
    @build_def[:environment][:variables].each do |key, value|
      if(key.match?('PATH'))
        if Gem.win_platform?
          value.gsub!(':', ';')
          value.gsub!('/', '\\')
        else
          value.gsub!(';', ':')
          value.gsub!('\\', '/')
        end
        value.gsub!('~', Dir.home)
      end
      puts "Setting environment variable '#{key}' to '#{value}'"
      ENV[key] = value
    end
  end
  exec_pipeline('build', @build_def[:steps], skip_steps)
  # steps = @build_def[:steps][skip_steps..-1]
  # puts "Executing #{steps.length} of #{@build_def[:steps].length} build steps..."
  # Dir.chdir(@root) do
  #   steps.each_with_index do |step, index|
  #     step_index = index+1+skip_steps
  #     puts "... (#{step_index}/#{@build_def[:steps].length}) #{step}"
  #     system(step)
  #     puts "WARNING: build step #{step_index}: #{step} exit status #{$?.exitstatus}" unless $?.exitstatus.zero?
  #   end
  # end
end
changelog_name() click to toggle source
# File lib/revision/releasable.rb, line 187
def changelog_name
  "#{@id}_revision_history_v#{@revision}.txt"
end
commit_message() click to toggle source
# File lib/revision/releasable.rb, line 152
def commit_message
  changelog_entry = @revision.last_changelog_entry
  #Insert a blank line between the revision header and release notes, as per git commit best practice
  commit_lines = ["#{tag_id} #{changelog_entry[1]}", '']
  if changelog_entry.length > 2
    commit_lines << "Also..."
    commit_lines += changelog_entry[2..-1]
  end
  escape(commit_lines.join("\n"))
end
deploy(destination='') click to toggle source
# File lib/revision/releasable.rb, line 234
def deploy(destination='')
  destinations = []
  if not destination.empty?
    destinations.append({dest: destination})
  elsif @config.dig(:deploy)
    if @config[:deploy].kind_of?(Array)
      destinations.append(*@config[:deploy])
    else
      destinations.append(@config[:deploy])
    end
  end

  raise Errors::NotSpecified.new(':deploy') if destinations.empty?

  if @config.dig(:deploy, :pre)
    exec_pipeline('deploy (pre)', @config[:deploy][:pre])
  end

  destinations.each do |d|
    destination = File.expand_path(d[:dest])

    if d.dig(:pre)
      exec_pipeline('deploy (pre / #{d[:dest]})', d[:pre])
    end

    puts "Deploying #{@artefacts.length} build artefacts to #{destination}..."
    if not File.exist?(destination)
      puts "... folder not found -> creating ... '#{destination}'"
      FileUtils.mkdir_p(destination)
    end
    amap = artefact_map(destination)
    amap.each.with_index(1) do |entry, idx|
      src, dest = entry
      puts "... (#{idx}/#{amap.length}) #{src} => #{dest}"
      if File.exist?(dest)
        puts "... deleting existing '#{dest}' ..."
        FileUtils.rm_rf(dest)
      end
      puts "... deploying '#{src}' -> '#{dest}"
      FileUtils.cp_r(src,dest)
    end
    File.open(File.join(destination,changelog_name),'w') { |f| output_changelog(f)}

    if d.dig(:post)
      exec_pipeline('deploy (post / #{d[:dest]})', d[:post])
    end
  end



  if @config.dig(:deploy, :post)
    exec_pipeline('deploy (post)', @config[:deploy][:post])
  end
end
escape(a_string) click to toggle source
# File lib/revision/releasable.rb, line 144
def escape(a_string)
  a_string.gsub('"',"\\\"")
end
exec_pipeline(type, steps, skip_steps=0) click to toggle source
# File lib/revision/releasable.rb, line 96
def exec_pipeline(type, steps, skip_steps=0)
  exec_steps = steps[skip_steps..-1]
  puts "#{type} :: Executing steps #{skip_steps+1} to #{steps.length}..."
  Dir.chdir(@root) do
    exec_steps.each_with_index do |step, index|
      step_index = index+1+skip_steps
      puts "... (#{step_index}/#{steps.length}) #{step}"
      system(step)
      puts "{type} :: WARNING: step #{step_index}: #{step} exit status #{$?.exitstatus}" unless $?.exitstatus.zero?
    end
  end

end
output_changelog(output_stream) click to toggle source
# File lib/revision/releasable.rb, line 294
def output_changelog(output_stream)
  output_stream.puts "Revision history for #{@id} version #{@revision}"
  output_stream.puts ""
  @revision.changelog {|line| output_stream.puts(line)}
end
package() click to toggle source
# File lib/revision/releasable.rb, line 289
def package
  build
  archive
end
push() click to toggle source
# File lib/revision/releasable.rb, line 174
def push
  pushed = false
  Dir.chdir(@root) do
    pushed = system("git push") && system("git push --tags")
    puts "ERROR :: Failed to push to remote" unless pushed
  end
  pushed
end
tag() click to toggle source
# File lib/revision/releasable.rb, line 163
def tag
  Dir.chdir(@root) do
    puts "Committing..."
    puts commit_message
    system("git commit -a -m \"#{commit_message}\"")
    puts "Tagging as #{tag_id}"
    puts "git tag -a #{tag_id} -m \"#{tag_annotation}\""
    system("git tag -a #{tag_id} -m \"#{tag_annotation}\"")
  end
end
tag_annotation() click to toggle source
# File lib/revision/releasable.rb, line 148
def tag_annotation
  escape(@revision.last_changelog_entry.join("\n"))
end
tag_id() click to toggle source
# File lib/revision/releasable.rb, line 140
def tag_id
  "#{@git_tag_prefix}#{revision}"
end
to_s() click to toggle source
# File lib/revision/releasable.rb, line 75
    def to_s
      <<~EOT
      #{@id} v#{@revision} @ #{@root}

        Build environment:
        #{@build_def[:environment]}

        Build pipeline:
        - #{@build_def[:steps].nil? ? 'empty' : @build_def[:steps].join("\n  - ")}

        Build artefacts:
        #{@artefacts.empty? ? '- None defined' : @artefacts.map{ |a| "- #{a[:src]}\n    => #{a[:dest]}" }.join("\n") }

        Git commit details:
        - log entry: #{commit_message}
        Git tag id #{tag_id} / annotation:
          #{tag_annotation.gsub("\n","\n    ")}

      EOT
    end