├── .empty ├── .dockerignore ├── runner ├── docker-compose.yml ├── infer-version-path ├── Dockerfile ├── pipeline-generate └── initial.yml /.empty: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | doc 3 | node_modules 4 | pkg 5 | tmp 6 | -------------------------------------------------------------------------------- /runner: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | cmd="$1" 6 | 7 | echo 8 | echo "--- bundle env" 9 | bundle env 10 | echo "+++ " 11 | 12 | echo "+++ $cmd" 13 | 14 | exec bundle exec /bin/sh -e -c "$cmd" 15 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.6" 2 | 3 | services: 4 | gem: 5 | build: 6 | context: ../ 7 | dockerfile: .buildkite/Dockerfile 8 | args: 9 | - RUBY_IMAGE 10 | environment: 11 | - CI 12 | -------------------------------------------------------------------------------- /infer-version-path: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "fileutils" 4 | require "pathname" 5 | 6 | gemspecs = Dir.glob("*.gemspec") 7 | exit unless gemspecs.size == 1 8 | 9 | gemspec = Pathname.new(gemspecs.first) 10 | exit unless gemspec.exist? 11 | 12 | tempversion = Pathname.new("lib/.version/version.rb") 13 | exit unless tempversion.exist? 14 | 15 | requires = [] 16 | gemspec.each_line do |line| 17 | next unless line =~ /^\s*require\s+['"]([\w\/-]+)['"]\s*$/ 18 | 19 | requires << $1 20 | end 21 | 22 | version_requires = requires.grep(/\/version$/) 23 | exit unless version_requires.size == 1 24 | 25 | target = Pathname.new("lib/#{version_requires.first}.rb") 26 | exit if target.exist? 27 | 28 | FileUtils.mkdir_p target.dirname 29 | 30 | FileUtils.mv tempversion, target 31 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG RUBY_IMAGE 2 | FROM ${RUBY_IMAGE:-ruby:latest} 3 | 4 | WORKDIR /app 5 | ENV JRUBY_OPTS="--dev -J-Xmx1024M" 6 | 7 | # Wildcard ignores missing files; .empty ensures ADD always has at least 8 | # one valid source: https://stackoverflow.com/a/46801962 9 | ADD .buildkite/.empty .ci/pre-buil[d] .ci/ 10 | ADD .buildkite/runner .buildkite/infer-version-path /usr/local/bin/ 11 | 12 | ARG BUNDLER 13 | ARG RUBYGEMS 14 | RUN echo "--- :ruby: Updating RubyGems and Bundler" \ 15 | && (gem update --system ${RUBYGEMS:-} || gem update --system 2.7.8) \ 16 | && (gem install bundler -v "${BUNDLER:->= 0}" || gem install bundler -v "< 2") \ 17 | && ruby --version && gem --version && bundle --version \ 18 | && if [ -f ./.ci/pre-build ]; then \ 19 | echo "--- :package: Installing system deps" \ 20 | && chmod +x ./.ci/pre-build \ 21 | && ./.ci/pre-build; \ 22 | fi \ 23 | && chmod +x /usr/local/bin/runner /usr/local/bin/infer-version-path \ 24 | && apt-get clean \ 25 | && rm -rf /var/lib/apt/lists/* /tmp/* 26 | 27 | ADD .buildkite/.empty lib/*/version.rb lib/.version/ 28 | ADD Gemfile Gemfile.loc[k] *.gemspec ./ 29 | 30 | RUN infer-version-path \ 31 | && echo "--- :bundler: Installing Ruby deps" \ 32 | && rm -rf lib/.version/ Gemfile.lock \ 33 | && bundle install -j 8 \ 34 | && cp Gemfile.lock /tmp/Gemfile.lock.updated \ 35 | && rm -rf /usr/local/bundle/cache \ 36 | && echo "--- :floppy_disk: Copying repository contents" 37 | 38 | ADD . ./ 39 | 40 | RUN mv -f /tmp/Gemfile.lock.updated Gemfile.lock 41 | 42 | ENTRYPOINT ["runner"] 43 | CMD ["rake"] 44 | -------------------------------------------------------------------------------- /pipeline-generate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "json" 5 | require "net/http" 6 | require "pathname" 7 | require "yaml" 8 | 9 | QUEUE = ENV["BUILDKITE_AGENT_META_DATA_QUEUE"] || "default" 10 | 11 | ARTIFACTS_PLUGIN = "artifacts#v1.2.0" 12 | DOCKER_COMPOSE_PLUGIN = "docker-compose#v3.7.0" 13 | 14 | REPO_ROOT = Pathname.new(ARGV.shift || File.expand_path("../..", __FILE__)) 15 | 16 | 17 | def available_tags_for_image(image) 18 | uri = URI("https://registry.hub.docker.com/v2/repositories/library/#{image}/tags?page_size=1000") 19 | json = Net::HTTP.get(uri) 20 | JSON.parse(json).fetch("results").map { |x| x["name"] } 21 | end 22 | 23 | head_ruby_image = "rubylang/ruby:master-nightly-focal" 24 | 25 | available_ruby_versions = 26 | available_tags_for_image("ruby"). 27 | grep(/\A[0-9]+\.[0-9]+\z/). 28 | map { |s| Gem::Version.new(s) }. 29 | sort. 30 | reverse 31 | 32 | ruby_images = 33 | if REPO_ROOT.glob("*.gemspec").first&.read =~ /required_ruby_version[^0-9]+([0-9]+\.[0-9]+)/ 34 | min_ruby = Gem::Version.new($1 || "2.0") 35 | 36 | available_ruby_versions. 37 | select { |v| v >= min_ruby } 38 | else 39 | available_ruby_versions.first(4) 40 | end.map { |v| "ruby:#{v}" } + 41 | [head_ruby_image] 42 | 43 | soft_fail_images = [head_ruby_image] 44 | 45 | BUNDLER = "< 2.2.10" 46 | RUBYGEMS = "3.2.9" 47 | 48 | puts YAML.dump({ 49 | "steps" => ruby_images.map do |ruby| 50 | { 51 | "label" => ":ruby: #{ruby}", 52 | "env" => { 53 | "RUBY_IMAGE" => ruby, 54 | "BUNDLER" => BUNDLER, 55 | "RUBYGEMS" => RUBYGEMS, 56 | }, 57 | "plugins" => [ 58 | { 59 | ARTIFACTS_PLUGIN => { 60 | "download" => [".dockerignore", ".buildkite/*"], 61 | }, 62 | }, 63 | { 64 | DOCKER_COMPOSE_PLUGIN => { 65 | "run" => "gem", 66 | "config" => ".buildkite/docker-compose.yml", 67 | } 68 | }, 69 | ], 70 | "timeout_in_minutes" => 10, 71 | "soft_fail" => soft_fail_images.include?(ruby), 72 | "agents" => { "queue" => QUEUE }, 73 | "retry" => { "automatic" => { "exit_status" => -1, "limit" => 2 } }, 74 | } 75 | end 76 | }) 77 | -------------------------------------------------------------------------------- /initial.yml: -------------------------------------------------------------------------------- 1 | # This file is never read -- it's just a copy of the pipeline's 2 | # configuration in the Buildkite UI. 3 | 4 | steps: 5 | - name: ":pipeline:" 6 | command: | 7 | PATH=/bin:/usr/bin 8 | set -e 9 | treesha="$$(git ls-tree -d HEAD .buildkite | awk '{print $$3}')" 10 | if [ -z "$${treesha}" ]; then 11 | echo "+++ No .buildkite/; using fallback repository" 12 | rm -rf .buildkite 13 | git clone "https://github.com/rails/gem-buildkite-config" .buildkite 14 | rm -rf .buildkite/.git 15 | sh -c "$$PIPELINE_COMMAND" 16 | elif [ "$${#treesha}" -lt 40 ]; then 17 | echo "Short SHA for .buildkite/" 18 | exit 1 19 | elif curl -s -S "https://gist.githubusercontent.com/matthewd/49232efd48afe8faaed6e88f13205117/raw/c9962c4c2b73d1324156c47f2220f643e7097fb0/list" | grep -a -F -x "$${treesha}"; then 20 | echo "+++ Known tree; generating pipeline" 21 | echo ".buildkite/ tree is $${treesha}" 22 | sh -c "$$PIPELINE_COMMAND" 23 | else 24 | echo "+++ Unknown tree; requesting approval" 25 | echo ".buildkite/ tree is $${treesha}" 26 | buildkite-agent pipeline upload <<'NESTED' 27 | steps: 28 | - block: "Review Build Script" 29 | prompt: | 30 | This commit uses new build configuration. Please review the changes in .buildkite/ carefully before unblocking. 31 | - name: ":pipeline:" 32 | command: >- 33 | $$PIPELINE_COMMAND 34 | plugins: 35 | - artifacts#v1.2.0: 36 | download: 37 | - ".buildkite/*" 38 | - ".buildkite/**/*" 39 | timeout_in_minutes: 5 40 | NESTED 41 | fi 42 | [ -f .buildkite/.dockerignore ] && cp .buildkite/.dockerignore .dockerignore 43 | plugins: 44 | - artifacts#v1.2.0: 45 | upload: [".dockerignore", ".buildkite/**/*"] 46 | env: 47 | PIPELINE_COMMAND: >- 48 | docker run --rm 49 | -v "$$PWD":/app:ro -w /app 50 | -e CI 51 | -e BUILDKITE_BRANCH 52 | -e BUILDKITE_BUILD_ID 53 | -e BUILDKITE_PULL_REQUEST 54 | -e BUILDKITE_PULL_REQUEST_BASE_BRANCH 55 | -e BUILDKITE_REBUILT_FROM_BUILD_ID 56 | ruby:latest 57 | .buildkite/pipeline-generate | 58 | buildkite-agent pipeline upload 59 | timeout_in_minutes: 5 60 | depends_on: [] 61 | --------------------------------------------------------------------------------