rakelib/support.rb
author Jan Vrany <jan.vrany@labware.com>
Wed, 04 Nov 2020 11:10:18 +0000
changeset 310 2cf08578aa5f
parent 289 c10f5a5b2e92
child 322 d31ea885c8fa
permissions -rw-r--r--
Do not ship VDB as part of default build / toy archive The reason is that few people used it and those who did (or will) are those who may want to hack on it. To make this easier when using toy archive, we won't ship VDB in toy archive anymore and instead we'll document on how to install it in VDB's `README.md`

require 'net/http'
require 'net/https'
require 'json'
require 'rakelib/extensions.rb'
require 'rakelib/rbspec.rb'
require 'rakelib/scm.rb'

# Following hack is required to allow passing variable
# values in `make` style, i.e., to allow for
#
#  rake PROJECT=stx:jv-branch compile
#
ARGV.each do |arg|
  name_and_value = /^([A-Za-z_]+)\w*=(.*)/.match(arg)
  self.class.const_set(name_and_value[1], name_and_value[2]) if name_and_value
end

# Update PATH for build so build scripts may access  scripts and programs 
# in `bin` directory. This is required especially on Windows as Windows do 
# not ship with some basic tools (e.g. zip) by default. As a courtesy to the
# user, provide our own. 
# NOTE that this MUST be the first entry in the PATH to be able to override
# MINGW/MSYS make.exe with Borland's ancient make.exe (sigh, St/X still deals
# with dinosaurs)
ENV['PATH'] = "#{File.expand_path('bin')};#{File::PATH_SEPARATOR}#{ENV['PATH']}"

# Return true if running under Jenkins, false otherwise
def jenkins?
  (ENV.has_key? 'WORKSPACE' and
      ENV.has_key? 'JOB_NAME' and
      ENV.has_key? 'BUILD_ID')
end

# Returns true if and only if this is machine of one of the core developers
# (currently only JV).
# Indeed this is a feeble check and can be easily bypassed by changing the
# code or by setting a proper environment variable. But this should not hurt
# much as in that case, unauthorized person wouldn't be able to connect to
# stc and librun repository so the build will fail.
def core_developer?
  # JV's box: jv@..., vranyj1@...
  user = ENV['USER'] || ENV['USERNAME']
  (user == 'jv') or (user == 'vranyj1') ? true : false
end

# ArtifactRepository module provides API for downloading (binary) files from
# some sort of - well - artifact repository. A repository contains multiple
# builds (currently the API provides only access to `latestBuild()`), build
# contains multipe `Artifact`s. An `Artifact` is essentially a downloadable
# (binary) file.
#
# Currently only Jenkins-based repository is provided (see class `Jenkins`
# within this module)
module ArtifactRepository

  private

  # A private method to GET data over HTTP(S)
  def self.get(uri, &block)
    # http parameters
    http_host = Net::HTTP.new(uri.host, uri.port)
    http_host.use_ssl = (uri.scheme == 'https') # simple true enough

    if false # host verification not used (yet)
      http_host.verify_mode = OpenSSL::SSL::VERIFY_PEER
      http_host.cert_store = OpenSSL::X509::Store.new
      http_host.cert_store.set_default_paths
      http_host.cert_store.add_file('cacert.pem') # file downloaded from curl.haxx.se/ca/cacert.pem
    else
      http_host.verify_mode = OpenSSL::SSL::VERIFY_NONE
    end

    # actual download
    body = nil
    http_host.start do |http|
      http.request_get(uri) {|response| block ? (yield response) : (body = response.body)}
    end
    body
  end

  public

  class Artifact
    attr_reader :name
    attr_reader :uri

    def initialize(name, uri)
      @name = name
      @uri = uri
    end

    def download_to(destination)
      if !File.exist? destination
        raise Exception.new("Invalid destination for download: #{destination}") unless File.directory? File.dirname(destination)
      else
        if !File.directory? destination
          raise Exception.new("Invalid destination for download: #{destination}")
        else
          destination = File.join(destination, @name)
        end
      end
      ArtifactRepository::get(@uri) do |response|
        File.open(destination, 'wb') do |file|
          response.read_body {|part| file.write part}
        end
      end
    end
  end

  # Jenkins-based artifact repository.
  class Jenkins

    def initialize(uri)
      @uri = uri
    end

    def latestBuild()
      return Build.new(@uri + '/lastStableBuild')
    end

    class Build
      attr_reader :data
      attr_reader :uri

      def initialize(uri)
        @uri = uri
        @data = JSON.parse(ArtifactRepository::get(URI(uri.to_s + '/api/json')))
      end

      # Return a list of artifacts (as instances of `ArtifactRepository::Artifact`) 
      # associated with this build. 
      def artifacts
        unless @artifacts
          @artifacts = @data['artifacts'].collect {|each| Artifact.new(each['fileName'], URI(@uri.to_s + '/artifact/' + each['relativePath']))}
        end
        @artifacts
      end
    end # class ArtifactRepository::Jenkins::Build
  end # class ArtifactRepository::Jenkins

  class PlainHTTPListing

    def initialize(uri)
      @uri = uri
      @html_data = Net::HTTP.get(URI(uri))
    end

    def latestBuild()
      return Build.new(@uri + latest_build + '/') # dont forget trailing '/'!
    end

    class Build
      attr_reader :uri, :html_data

      def initialize(uri)
        @uri = uri
        @html_data = Net::HTTP.get(URI(uri))
      end

      def artifacts
        unless @artifacts
          # get latest build file list
          @artifacts = lastest_build_files(Array.new, String.new).collect {|each_file| Artifact.new(each_file, URI(@uri + each_file))} 
        end
        @artifacts
      end

      private

      # parse HTML via regexp (yes, bad idea but Apache can't serve natively JSON)
      # retuns Array of String(s) - filenames and their suffixes
      def parse_html_directories(html_build_directory_list)
        files_and_suffixes = html_build_directory_list.scan(/(smalltalkx-jv-branch-\d+.\d+.\d+_build\d+[a-zA-Z0-9_\-]+\.|tar\.bz2|zip|7z|7zip|\.sha256|\.sha512)/)
        files_and_suffixes.flatten
      end

      # create file array where the file part and its suffix will be merged together
      # returns Array of String filenames without any duplicities (needed due to HTML nature (a href))
      def lastest_build_files(stx_latest_build_files, stx_file)  
        parse_html_directories(@html_data).each do |file_part|
          if file_part.match(/\.$/) # only a file_part has a . at the end
            stx_latest_build_files.push(stx_file) unless stx_file.empty? 
            stx_file = String.new  # empty current String content
            stx_file = file_part
          else 
            stx_file = stx_file + file_part
          end
        end
        # prune (duplicities)
        stx_latest_build_files.uniq!
      end

    end # class ArtifactRepository::PlainHTTPListing::Build

    private

    # find newest build contains date: yyyy-mm-dd_buildId
    def latest_build
      directory_list.max
    end

    # get Array of directories
    def directory_list
      directories=@html_data.scan(/\d{4}-\d{2}-\d{2}_\d+/)
      directories.uniq! # remove duplicates
    end

  end # class ArtifactRepository::PlainHTTPListing

end # module ArtifactRepository