diff --git a/travis-matrix-preview/.gitignore b/matriarch/.gitignore similarity index 100% rename from travis-matrix-preview/.gitignore rename to matriarch/.gitignore diff --git a/travis-matrix-preview/Gemfile b/matriarch/Gemfile similarity index 84% rename from travis-matrix-preview/Gemfile rename to matriarch/Gemfile index 6ba5029..e6fa230 100644 --- a/travis-matrix-preview/Gemfile +++ b/matriarch/Gemfile @@ -1,5 +1,6 @@ source 'https://rubygems.org' +gem 'activesupport' gem 'puma' gem 'sinatra' gem 'sinatra-contrib', require: 'sinatra/contrib' diff --git a/matriarch/config.ru b/matriarch/config.ru new file mode 100644 index 0000000..1c3944c --- /dev/null +++ b/matriarch/config.ru @@ -0,0 +1,5 @@ +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) + +require 'matriarch' +run Matriarch::App diff --git a/matriarch/lib/matriarch.rb b/matriarch/lib/matriarch.rb new file mode 100644 index 0000000..f09eedb --- /dev/null +++ b/matriarch/lib/matriarch.rb @@ -0,0 +1,4 @@ +module Matriarch + autoload :App, 'matriarch/app' + autoload :Config, 'matriarch/config' +end diff --git a/matriarch/lib/matriarch/app.rb b/matriarch/lib/matriarch/app.rb new file mode 100644 index 0000000..786528d --- /dev/null +++ b/matriarch/lib/matriarch/app.rb @@ -0,0 +1,19 @@ +require 'matriarch' +require 'json' +require 'sinatra/base' +require 'sinatra/json' + +module Matriarch + class App < Sinatra::Base + get '/' do + "oh hai\n" + end + + post '/matrix' do + config = ::Matriarch::Config.new(JSON.parse(request.body.read)) + json data: config.expand + end + + run! if app_file == $PROGRAM_NAME + end +end diff --git a/matriarch/lib/matriarch/config.rb b/matriarch/lib/matriarch/config.rb new file mode 100644 index 0000000..dee99ca --- /dev/null +++ b/matriarch/lib/matriarch/config.rb @@ -0,0 +1,189 @@ +require 'active_support/core_ext/array/wrap' +require 'active_support/core_ext/hash/keys' +require 'active_support/core_ext/hash/slice' +require 'active_support/core_ext/object/try' + +module Matriarch + class Config + DEFAULT_LANG = 'ruby'.freeze + + ENV_KEYS = [ + :compiler, + :csharp, + :d, + :dart, + :elixir, + :env, + :fsharp, + :gemfile, + :ghc, + :go, + :haxe, + :jdk, + :julia, + :mono, + :node_js, + :otp_release, + :perl, + :perl6, + :php, + :python, + :ruby, + :rust, + :rvm, + :scala, + :visualbasic, + :xcode_scheme, + :xcode_sdk + ].freeze + + EXPANSION_KEYS_FEATURE = [:os].freeze + + EXPANSION_KEYS_LANGUAGE = { + 'c' => [:compiler], + 'c++' => [:compiler], + 'clojure' => [:lein, :jdk], + 'cpp' => [:compiler], + 'csharp' => [:csharp, :mono], + 'd' => [:d], + 'dart' => [:dart], + 'elixir' => [:elixir, :otp_release], + 'erlang' => [:otp_release], + 'fsharp' => [:fsharp, :mono], + 'go' => [:go], + 'groovy' => [:jdk], + 'haskell' => [:ghc], + 'haxe' => [:haxe], + 'java' => [:jdk], + 'julia' => [:julia], + 'node_js' => [:node_js], + 'objective-c' => [:rvm, :gemfile, :xcode_sdk, :xcode_scheme], + 'perl' => [:perl], + 'perl6' => [:perl6], + 'php' => [:php], + 'python' => [:python], + 'ruby' => [:rvm, :gemfile, :jdk, :ruby], + 'rust' => [:rust], + 'scala' => [:scala, :jdk], + 'visualbasic' => [:visualbasic, :mono] + }.freeze + + EXPANSION_KEYS_UNIVERSAL = [:env, :branch].freeze + + def self.matrix_keys_for(config, options = {}) + keys = matrix_keys(config, options) + keys & config.keys.map(&:to_sym) + end + + def self.matrix_keys(config, options = {}) + lang = Array(config.symbolize_keys[:language]).first + keys = ENV_KEYS + keys &= EXPANSION_KEYS_LANGUAGE.fetch(lang, EXPANSION_KEYS_LANGUAGE[DEFAULT_LANG]) + keys << :os if options[:multi_os] + keys += [:dist, :group] if options[:dist_group_expansion] + keys | EXPANSION_KEYS_UNIVERSAL + end + + def initialize(config, options = {}) + @config = config.symbolize_keys + @options = options + end + + def expand + configs = expand_matrix + configs = include_matrix_configs(exclude_matrix_configs(configs)) + configs = configs.map { |config| cleanup_config(merge_config(Hash[config])) } + configs + # configs.map { |config| Build::Config::OS.new(config, options).run } + end + + private + + attr_reader :config, :options + + def allow_failure_configs + (settings[:allow_failures] || []).select do |config| + config = config.to_hash.symbolize_keys if config.respond_to?(:to_hash) + end + end + + def fast_finish? + settings[:fast_finish] + end + + private + + def settings + @settings ||= config[:matrix] || {} + @settings = {} if @settings.is_a?(Array) + @settings + end + + def expand_matrix + rows = config.slice(*expand_keys).values.select { |value| value.is_a?(Array) } + max_size = rows.max_by(&:size).try(:size) || 1 + + array = expand_keys.inject([]) do |result, key| + values = Array.wrap(config[key]) + values += [values.last] * (max_size - values.size) + result << values.map { |value| [key, value] } + end + + permutations(array).uniq + end + + # recursively builds up permutations of values in the rows of a nested array + def permutations(base, result = []) + base = base.dup + base.empty? ? [result] : base.shift.map { |value| permutations(base, result + [value]) }.flatten(1) + end + + def expand_keys + @expand_keys ||= config.keys.map(&:to_sym) & self.class.matrix_keys_for(config, options) + end + + def exclude_matrix_configs(configs) + configs.reject { |config| exclude_config?(config) } + end + + def exclude_config?(config) + exclude_configs = normalize_matrix_filter_configs(settings[:exclude] || []) + config = config.map { |config| [config[0].to_s, *config[1..-1]] }.sort + exclude_configs.any? do |excluded| + excluded.all? { |matrix_key| config.include? matrix_key } + end + end + + def include_matrix_configs(configs) + include_configs = normalize_matrix_filter_configs(settings[:include] || []) + if configs.flatten.empty? && settings.has_key?(:include) + include_configs + else + configs + include_configs + end + end + + def normalize_matrix_filter_configs(configs) + configs = configs.select { |c| c.is_a?(Hash) } + configs = configs.compact.map(&:stringify_keys) + configs.map(&:to_a).map(&:sort) + end + + def merge_config(row) + config.select { |key, value| include_key?(key) }.merge(row) + end + + def cleanup_config(config) + config.delete(:matrix) + config + end + + def include_key?(key) + self.class.matrix_keys_for(config, options).include?(key.to_sym) || !known_env_key?(key.to_sym) + end + + def known_env_key?(key) + (ENV_KEYS | EXPANSION_KEYS_FEATURE).include?(key) + end + end +end diff --git a/travis-matrix-preview/app.rb b/travis-matrix-preview/app.rb deleted file mode 100644 index a5ca7ba..0000000 --- a/travis-matrix-preview/app.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'sinatra/base' - -class App < Sinatra::Base - run! if app_file == $PROGRAM_NAME -end diff --git a/travis-matrix-preview/config.ru b/travis-matrix-preview/config.ru deleted file mode 100644 index 5b106e2..0000000 --- a/travis-matrix-preview/config.ru +++ /dev/null @@ -1,2 +0,0 @@ -require './app' -run App