require 'rugged' require 'oj' require 'time' module TripAdvisor class BuildInfo attr_accessor :version, :branch, :sha1, :author attr_reader :date def initialize(repo) @date = Time.now @branch = repo.branches.detect { |b| b.canonical_name == repo.head.name }.name name = repo.config['user.name'] email = repo.config['user.email'] @author = "#{name} <#{email}>" @sha1 = repo.head.target end def timestamp date.utc.strftime("%Y%m%d%H%M%S") end def to_hash Hash.new.tap do |info| info['version'] = self.version info['branch'] = self.branch info['sha1'] = self.sha1 info['author'] = self.author info['date'] = self.date.utc.iso8601 end end end class NullReporter def say(message) nil end end class ConsoleReporter def say(message) puts message end end class AbstractBuild attr_reader :path, :repo, :info attr_accessor :pod_repo, :reporter, :version_prefix, :remotes def initialize(path) @path = path @repo = Rugged::Repository.new(path) @info = BuildInfo.new(repo) @pod_repo = 'tripadvisor-mobile-ta-specs' @reporter = ConsoleReporter.new @version_prefix = 'v' end def build! raise "Cannot build a TripAdvisor::AbstractBuild: Only concrete subclasses are buildable." end def podspec_file @podspec_file ||= Dir.entries(path).detect { |f| f =~ /.podspec$/ } end def remotes if @remotes.nil? @remotes = `git remote`.split("\n") raise "Build failed: Non-zero exit status returned while executing `git remote`" unless $?.success? end @remotes end def run(command) unless system(command) raise "Build failed: Non-zero exit status returned while executing `#{command}`" end end def pod_update! run("bundle exec pod update") end def release_version File.read(File.join(path, 'VERSION')).chomp end def author @author ||= { name: repo.config['user.name'], email: repo.config['user.email'], time: Time.now } end def update_build_assets info.version = version File.open('BUILD.json', 'w') { |f| f << Oj.dump(info.to_hash) } podspec_content = File.read(podspec_file) unless podspec_content.gsub!(/(\.version\s+=\s+)['"](.+)['"]$/, "\\1'#{version}'") raise "Unable to update version of Podspec: version attribute not matched." end unless podspec_content.gsub!(/(\:tag\s+=>\s+)['"](.+)['"]/, "\\1'#{tag_name}'") raise "Unable to update version of Podspec: version attribute not matched." end File.open(podspec_file, 'w') { |f| f << podspec_content } end def commit!(files) say "Committing to #{path}" index = repo.index files.each { |f| index.add f } options = {} options[:tree] = index.write_tree(repo) options[:author] = author options[:committer] = author options[:message] = "Creating build #{version}" options[:parents] = [ repo.head.target ] options[:update_ref] = 'HEAD' Rugged::Commit.create(repo, options) end def tag!(target) Rugged::Tag.create(repo, :name => tag_name, :message => "Tagging build #{version}", :target => target, :tagger => author) end def push! branch = info.branch remotes.each do |remote_name| say "Pushing to remote '#{remote_name}'" # NOTE: Rugged apparently cannot push to the repositories we need # remote = Rugged::Remote.lookup(@repo, remote_name) # remote.push ["refs/heads/#{info.branch}", "refs/tags/#{tag_name}"] run("git push #{remote_name} #{branch}:#{branch} --tags") end end def push_podspec! run("bundle exec pod repo push --use-libraries --allow-warnings #{pod_repo} #{podspec_file}") end def update_staging_area(files) run("git add #{files.join(' ')}") end protected def say(message) reporter.say(message) end end class AdHocBuild < AbstractBuild def build! say "Creating AdHoc build at path: #{path}" Dir.chdir(path) do update_build_assets pod_update! files = [podspec_file, 'BUILD.json', 'Podfile.lock'] target = commit!(files) say "Committed updates to #{files.join(', ')} (ref: #{target})" tag!(target) say "Tagged build as #{tag_name}" push! say "Pushed to #{remotes.join(', ')}" push_podspec! say "Pushed podspec to spec repository '#{pod_repo}'" update_staging_area(files) end say "Build complete." end def version @version ||= "#{release_version}-b#{info.timestamp}" end def tag_name "builds/#{version_prefix}#{version}" end end class ReleaseBuild < AbstractBuild attr_accessor :release_version def initialize(path, release_version) super(path) @release_version = release_version File.write(File.join(path, 'VERSION'), "#{release_version}") end def build! say "Creating Release build at path: #{path}" Dir.chdir(path) do update_build_assets pod_update! files = [podspec_file, 'BUILD.json', 'Podfile.lock', 'VERSION'] target = commit!(files) say "Committed updates to #{files.join(', ')} (ref: #{target})" tag!(target) say "Tagged build as #{tag_name}" push! say "Pushed to #{remotes.join(', ')}" push_podspec! say "Pushed podspec to spec repository '#{pod_repo}'" update_staging_area(files) end say "Build complete." end def version @version ||= "#{release_version}" end def tag_name "#{version_prefix}#{version}" end end end