2018-03-06

tachikomaコードリーディング

tachikoma (5.2.0)のコードリーディングをしました。

tachikomaはrakeタスクを起動して使います。bundle updateをしたい場合はbundle exec rake tachikoma:run_bundlerを実行します。今回はrun_bundlerのパターンを追っていきます。

Rakeタスクは以下のように定義されています。いずれもTachikoma::Application.runを呼び出します。

require 'tachikoma/application'

namespace :tachikoma do
  desc 'run tachikoma with bundler'
  task :run_bundler do
    Tachikoma::Application.run 'bundler'
  end

  desc 'run tachikoma with carton'
  task :run_carton do
    Tachikoma::Application.run 'carton'
  end
# ...

Tachikoma::Application.runは#runを呼び出します。strategyのメソッド以外は共通で#load, #fetch #pull_requestを呼び出します。

module Tachikoma
  # Main logic of Tachikoma
  class Application
    include FileUtils

    def self.run(strategy)
      new.run(strategy)
    end

    def run(strategy)
      load
      fetch
      send(strategy) if respond_to?(strategy)
      pull_request
    end

#loadでは設定ファイルのロードとインスタンス変数への設定を行います。base_config_pathはgem自体に存在するデフォルト設定ファイルのパス、user_config_pathはプロジェクト全体の設定ファイルのパス、each_config_pathはプロジェクトごとに固有の設定ファイルのパスです。@build_forがプロジェクト名を表しています。

    def load
      @build_for = ENV['BUILD_FOR']
      @github_token = ENV[github_token_key(@build_for)]

      base_config_path = File.join(Tachikoma.original_data_path, 'default.yaml')
      base_config = YAML.safe_load_file(base_config_path) || {}
      user_config_path = File.join(Tachikoma.data_path, '__user_config__.yaml')
      user_config = YAML.safe_load_file(user_config_path) if File.exist?(user_config_path)
      user_config ||= {}
      each_config_path = File.join(Tachikoma.data_path, "#{@build_for}.yaml")
      each_config = YAML.safe_load_file(each_config_path) if File.exist?(each_config_path)
      unless each_config
        fail %(Something wrong, BUILD_FOR: #{@build_for}, your config_path: #{each_config_path})
      end

      @configure = base_config.merge(user_config).merge(each_config)

      @commiter_name = @configure['commiter_name']
      @commiter_email = @configure['commiter_email']
# ...

#fetchはgit cloneを使ってリモートリポジトリからソースコードを取得します。

    def fetch
      clean
      sh(*([
        'git', 'clone',
        *@depth_option,
        @authorized_base_url,
        "#{Tachikoma.repos_path}/#{@build_for}"
      ].compact))
    end

#bundlerでは以下の処理を行います。

    def bundler
      Dir.chdir("#{Tachikoma.repos_path}/#{@build_for}") do
        Bundler.with_clean_env do
          sh(*['ruby', '-i', '-pe', '$_.gsub! /^ruby/, "#ruby"', 'Gemfile'])
          sh(*['git', 'config', 'user.name', @commiter_name])
          sh(*['git', 'config', 'user.email', @commiter_email])
          sh(*['git', 'checkout', '-b', "tachikoma/update-#{@readable_time}", @base_remote_branch])
          if File.exist?('Gemfile')
            @bundler_key_file = 'Gemfile'
            @bundler_lock_file = 'Gemfile.lock'
          elsif File.exist?('gems.rb')
            @bundler_key_file = 'gems.rb'
            @bundler_lock_file = 'gems.locked'
          else
            @bundler_key_file = 'Gemfile'
            @bundler_lock_file = 'Gemfile.lock'
          end
          sh(*([
            'bundle',
            '--gemfile', @bundler_key_file,
            '--no-deployment',
            '--without', 'nothing',
            '--path', 'vendor/bundle',
            @parallel_option
          ].compact))
          sh(*%w(bundle update))

          if @bundler_restore_bundled_with
            # restore_bundled_with
            lock_file_contents = File.read(@bundler_lock_file)
            lock_file = RestoreBundledWith::Lock.restore(
              lock_file_contents, @bundler_lock_file)
            File.write(@bundler_lock_file, lock_file.body)
          end

          sh(*['git', 'add', @bundler_lock_file])
          sh(*['git', 'commit', '-m', "Bundle update #{@readable_time}"]) do
            # ignore exitstatus
          end
          sh(*['git', 'push', @authorized_compare_url, "tachikoma/update-#{@readable_time}"])
        end
      end
    end

最後に#pull_requestでOctokit経由でプルリクエストを作成します。

    def pull_request
      @client = Octokit::Client.new access_token: @github_token
      @client.create_pull_request(
        @pull_request_url,
        @pull_request_base,
        @pull_request_head,
        @pull_request_title,
        @pull_request_body
      )
    rescue Octokit::UnprocessableEntity
    end
このエントリーをはてなブックマークに追加