diff --git a/utilities/releaseScripts/1_setup.rb b/utilities/releaseScripts/1_setup.rb new file mode 100644 index 00000000..90194d85 --- /dev/null +++ b/utilities/releaseScripts/1_setup.rb @@ -0,0 +1,141 @@ +=begin +-------------------------------------------------------------------------------- + +Check for the required parameters of base directory, username, email address, +revision label and candidate label. + +Offer to run the licenser to be sure that we are ready to go. + +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +=end + +$: << File.dirname(File.expand_path(__FILE__)) +require '_common' + +# +# ------------------------------------------------------------------------------------ +# Runs the licenser against both VIVO and Vitro, and shows the results. +# ------------------------------------------------------------------------------------ +# + +class LicenserCaller + def call_licenser(property_file) + properties = PropertyFileReader.read(property_file) + l = Licenser.new(properties) + l.process + l.report(properties) + return l.success? + end + + def initialize() + require "#{Settings.vitro_path}/utilities/licenser/licenser" + require "#{Settings.vitro_path}/utilities/licenser/property_file_reader" + + puts "Scanning VIVO..." + vivo_success = call_licenser("#{Settings.vivo_path}/config/licenser/licenser.properties") + puts "Scanning Vitro..." + vitro_success = call_licenser("#{Settings.vitro_path}/webapp/config/licenser/licenser.properties") + if vivo_success && vitro_success + puts "Licenser was successful" + else + puts "Licenser found problems" + end + end +end + +# +# Show the current value of the setting, ask for a replacement value, validate it, and set it. +# +def do_setting(getter, validator, setter, label, format="a string") + v1 = getter.call + currently = v1.empty? ? "Currently not set" : "Currently '#{v1}'" + v2 = prompt "#{label}?\n(#{format})\n(#{currently})" + + v2 = v1 if v2.empty? + v2 = validator.call(v2) + + if v2.empty? + raise InputException.new("Can't run without #{label}") + elsif v1 == v2 + puts "Keeping #{label} as '#{v2}'" + puts + else + puts "Setting #{label} to '#{v2}'" + setter.call(v2) + puts + end + v2 +end + +def get_base_directory() + do_setting( + Settings.method(:base_directory), + Settings.method(:confirm_base_directory), + Settings.method(:base_directory=), + "Git base directory", + "holds Vitro and VIVO repositories") +end + +def get_user_name() + do_setting( + Settings.method(:username), + Settings.method(:confirm_username), + Settings.method(:username=), + "Git user.name") +end + +def get_email() + do_setting( + Settings.method(:email), + Settings.method(:confirm_email), + Settings.method(:email=), + "Git user.email") +end + +def get_release_label() + do_setting( + Settings.method(:release_label), + Settings.method(:confirm_release_label), + Settings.method(:release_label=), + "Release label", + "like '3.2' or '3.2.1'") +end + +def get_candidate_label() + do_setting( + Settings.method(:candidate_label), + Settings.method(:confirm_candidate_label), + Settings.method(:candidate_label=), + "Release candidate label", + "like 'rc1' or 'tc3' or 'final'") +end + +def run_licenser() + puts "It's a good idea to check the licenses before proceeding." + yn = prompt "Ready to run the licenser? (y/n)" + if (yn.downcase == 'y') + LicenserCaller.new() + else + puts "OK - forget it." + end +end + +# +# ------------------------------------------------------------------------------------ +# Main method +# ------------------------------------------------------------------------------------ +# + +begin + get_base_directory() + get_user_name() + get_email() + get_release_label() + get_candidate_label() + run_licenser() +rescue BadState + puts + puts "#{$!.message} - Aborting." + puts +end diff --git a/utilities/releaseScripts/2_create_branches.rb b/utilities/releaseScripts/2_create_branches.rb new file mode 100644 index 00000000..f938b8cb --- /dev/null +++ b/utilities/releaseScripts/2_create_branches.rb @@ -0,0 +1,59 @@ +=begin +-------------------------------------------------------------------------------- + +Get the branch name. + +If either repository already contains the branch, complain. + +Otherwise, pull develop to the latest commit and create the branches. Don't push. + +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +=end + +$: << File.dirname(File.expand_path(__FILE__)) +require '_common' + +# +# Create a branch by this name in this repository. +# +def create_branch(dir, branch) + Dir.chdir(dir) do |path| + approve_and_execute([ + "git checkout develop", + "git pull", + "git checkout -b #{branch}" + ], "in #{path}") + end +end + +# +# ------------------------------------------------------------------------------------ +# Main method +# ------------------------------------------------------------------------------------ +# + +begin + branch = Settings.branch_name + vivo_path = Settings.vivo_path + vitro_path = Settings.vitro_path + + raise BadState.new("Branch #{branch} already exists in VIVO.") if branch_exists?(vivo_path, branch) + raise BadState.new("Branch #{branch} already exists in Vitro.") if branch_exists?(vitro_path, branch) + + puts + yn = prompt("OK to create branches named '#{branch}' (y/n)") + if yn.downcase == 'y' + puts + puts "Creating branches" + create_branch(vivo_path, branch) + create_branch(vitro_path, branch) + puts + else + puts + puts "OK - forget it." + puts + end +rescue BadState + puts "#{$!.message} - Aborting." +end diff --git a/utilities/releaseScripts/3_create_tags.rb b/utilities/releaseScripts/3_create_tags.rb new file mode 100644 index 00000000..e19c7c01 --- /dev/null +++ b/utilities/releaseScripts/3_create_tags.rb @@ -0,0 +1,70 @@ +=begin +-------------------------------------------------------------------------------- + +Get the branch name and the tag name. + +If either repository doesn't contain the branch, complain. +If either repository already contains the tag, complain. + +Otherwise, Checkout the branch, pull the branch to the latest commit (if it is +tracking a remote branch) and create the tag. Don't push. + +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +=end + +$: << File.dirname(File.expand_path(__FILE__)) +require '_common' + +# +# Create a tag by this name in this repository. +# +def create_tag(dir, branch, tag, message) + Dir.chdir(dir) do |path| + cmds = ["git checkout #{branch}", + "git pull", + "git tag -a #{tag} -m '#{message}'" + ] + cmds.delete_at(1) unless is_remote_branch?(branch) + approve_and_execute(cmds, "in #{path}") + end +end + +def is_remote_branch?(branch) + ! `git branch --list -a origin/#{branch}`.strip.empty? +end + +# +# ------------------------------------------------------------------------------------ +# Main method +# ------------------------------------------------------------------------------------ +# + +begin + branch = Settings.branch_name + tag = Settings.tag_name + message = Settings.tag_message + vivo_path = Settings.vivo_path + vitro_path = Settings.vitro_path + + raise BadState.new("Branch #{branch} doesn't exist in VIVO.") unless branch_exists?(vivo_path, branch) + raise BadState.new("Branch #{branch} doesn't exist in Vitro.") unless branch_exists?(vitro_path, branch) + raise BadState.new("Tag #{tag} already exists in VIVO.") if tag_exists?(vivo_path, tag) + raise BadState.new("Tag #{tag} already exists in Vitro.") if tag_exists?(vitro_path, tag) + + puts + yn = prompt("OK to create tags named '#{tag}' '#{message}' (y/n)") + if yn.downcase == 'y' + puts + puts "Creating tags" + create_tag(vivo_path, branch, tag, message) + create_tag(vitro_path, branch, tag, message) + puts + else + puts + puts "OK - forget it." + puts + end +rescue BadState + puts "#{$!.message} - Aborting." +end diff --git a/utilities/releaseScripts/4_extract_files.rb b/utilities/releaseScripts/4_extract_files.rb new file mode 100644 index 00000000..65eb7d4e --- /dev/null +++ b/utilities/releaseScripts/4_extract_files.rb @@ -0,0 +1,69 @@ +=begin +-------------------------------------------------------------------------------- + +Get the tag name. + +If either repository doesn't contain the tag, complain. + +Otherwise, checkout the tag, copy the files to an appropriate area. + +The files are specified so hidden files will not be copied, but this only works +at the top levels. So the .git directories are omitted, as well as some Eclipse +artifacts and Mac OS artifacts. However, this only works at the top levels. + +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +=end + +$: << File.dirname(File.expand_path(__FILE__)) +require '_common' + +# +# Get the VIVO files and the Vitro files, and remove the .git directories. +# +def export_files(vivo_path, vitro_path, tag, export_dir) + approve_and_execute([ + "rm -Rf #{export_dir}/..", + "mkdir -pv #{export_dir}", + "cp -R #{vivo_path}/* #{export_dir}", + "mkdir -pv #{export_dir}/vitro-core", + "cp -R #{vitro_path}/* #{export_dir}/vitro-core", + ]) +end + +# +# ------------------------------------------------------------------------------------ +# Main method +# ------------------------------------------------------------------------------------ +# + +begin + tag = Settings.tag_name + vivo_path = Settings.vivo_path + vitro_path = Settings.vitro_path + export_dir = Settings.export_dir + + raise BadState.new("Tag #{tag} doesn't exist in VIVO.") unless tag_exists?(vivo_path, tag) + raise BadState.new("Tag #{tag} doesn't exist in Vitro.") unless tag_exists?(vitro_path, tag) + + if File.directory?(export_dir) + p = "OK to overwrite export area at #{export_dir} ? (y/n)" + else + p = "OK to create export area at #{export_dir} ? (y/n)" + end + + puts + yn = prompt(p) + if yn.downcase == 'y' + puts + puts "Building export area" + export_files(vivo_path, vitro_path, tag, export_dir) + puts + else + puts + puts "OK - forget it." + puts + end +rescue BadState + puts "#{$!.message} - Aborting." +end diff --git a/utilities/releaseScripts/5_insert_revision_info.rb b/utilities/releaseScripts/5_insert_revision_info.rb new file mode 100644 index 00000000..92672421 --- /dev/null +++ b/utilities/releaseScripts/5_insert_revision_info.rb @@ -0,0 +1,70 @@ +=begin +-------------------------------------------------------------------------------- + +Figure the revision info and store it in the export directory, for both VIVO +and Vitro. + +If the tags don't exist in either repository, or if the export directory is not +populated, complain. + +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +=end + +$: << File.dirname(File.expand_path(__FILE__)) +require '_common' + +# +# Get the revision information from Git and store it in the export directory. +# +def create_revision_info(git_path, info_file_path, tag) + Dir.chdir(git_path) do |path| + commit = `git show-ref --tags --hash=7 #{tag}`.strip + puts "Writing '#{tag} ~ #{commit}' to #{info_file_path}" + File.open(info_file_path, "w") do |f| + f.puts tag + f.puts commit + end + end +end + +# +# ------------------------------------------------------------------------------------ +# Main method +# ------------------------------------------------------------------------------------ +# + +begin + tag = Settings.tag_name + vivo_path = Settings.vivo_path + vitro_path = Settings.vitro_path + export_dir = Settings.export_dir + vivo_revision_info_path = Settings.vivo_revision_info_path + vitro_revision_info_path = Settings.vitro_revision_info_path + + raise BadState.new("Tag #{tag} doesn't exist in VIVO.") unless tag_exists?(vivo_path, tag) + raise BadState.new("Tag #{tag} doesn't exist in Vitro.") unless tag_exists?(vitro_path, tag) + raise BadState.new("Files have not been exported to #{export_dir}") unless File.directory?(export_dir) + + if File.exist?(vivo_revision_info_path) || File.exist?(vitro_revision_info_path) + p = "OK to overwrite revision_info at these paths? \n #{vivo_revision_info_path} \n #{vitro_revision_info_path} ? (y/n)" + else + p = "OK to write revision_info at these paths? \n #{vivo_revision_info_path} \n #{vitro_revision_info_path} ? (y/n)" + end + + puts + yn = prompt(p) + if yn.downcase == 'y' + puts + puts "Building revision info" + create_revision_info(vivo_path, vivo_revision_info_path, tag) + create_revision_info(vitro_path, vitro_revision_info_path, tag) + puts + else + puts + puts "OK - forget it." + puts + end +rescue BadState + puts "#{$!.message} - Aborting." +end diff --git a/utilities/releaseScripts/6_create_distribution_files.rb b/utilities/releaseScripts/6_create_distribution_files.rb new file mode 100644 index 00000000..1bd67d77 --- /dev/null +++ b/utilities/releaseScripts/6_create_distribution_files.rb @@ -0,0 +1,73 @@ +=begin +-------------------------------------------------------------------------------- + +Create TAR and ZIP files for both VIVO and Vitro. + +Complain if the files have not been exported, or the revision info doesn't exist. + +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +=end + +$: << File.dirname(File.expand_path(__FILE__)) +require '_common' + +# +# Zip up the VIVO distribution. Extract the Vitro distribution and zip that. +# +def create_distribution_files(export_dir, vivo_filename, vitro_filename) + export_parent_dir = File.dirname(export_dir) + Dir.chdir(export_parent_dir) do |path| + cmds = [ + "cp -r #{vivo_filename}/vitro-core #{vitro_filename}", + "zip -rq #{vivo_filename}.zip #{vivo_filename}", + "tar -czf #{vivo_filename}.tar.gz #{vivo_filename}", + "zip -rq #{vitro_filename}.zip #{vitro_filename}", + "tar -czf #{vitro_filename}.tar.gz #{vitro_filename}" + ] + cmds.insert(0, "rm -r #{vitro_filename}") if File.exist?(vitro_filename) + approve_and_execute(cmds) + end +end + +# +# ------------------------------------------------------------------------------------ +# Main method +# ------------------------------------------------------------------------------------ +# + +begin + tag = Settings.tag_name + vivo_path = Settings.vivo_path + vitro_path = Settings.vitro_path + export_dir = Settings.export_dir + vivo_revision_info_path = Settings.vivo_revision_info_path + vitro_revision_info_path = Settings.vitro_revision_info_path + vivo_filename = Settings.vivo_distribution_filename + vitro_filename = Settings.vitro_distribution_filename + + raise BadState.new("Files have not been exported to #{export_dir}") unless File.directory?(export_dir) + raise BadState.new("Revision information file does not exist at #{vivo_revision_info_path}") unless File.exist?(vivo_revision_info_path) + raise BadState.new("Revision information file does not exist at #{vitro_revision_info_path}") unless File.exist?(vitro_revision_info_path) + + if File.exist?(vivo_revision_info_path) || File.exist?(vitro_revision_info_path) + p = "OK to overwrite revision_info at these paths? \n #{vivo_revision_info_path} \n #{vitro_revision_info_path} ? (y/n)" + else + p = "OK to write revision_info at these paths? \n #{vivo_revision_info_path} \n #{vitro_revision_info_path} ? (y/n)" + end + + puts + yn = prompt("OK to create distribution files in #{export_dir} ? (y/n)") + if yn.downcase == 'y' + puts + puts "Creating distribution files" + create_distribution_files(export_dir, vivo_filename, vitro_filename) + puts + else + puts + puts "OK - forget it." + puts + end +rescue BadState + puts "#{$!.message} - Aborting." +end diff --git a/utilities/releaseScripts/7_merge_to_master.rb b/utilities/releaseScripts/7_merge_to_master.rb new file mode 100644 index 00000000..fbc8a1dc --- /dev/null +++ b/utilities/releaseScripts/7_merge_to_master.rb @@ -0,0 +1,60 @@ +=begin +-------------------------------------------------------------------------------- + +Merge the new tags into the master branches. + +This will only work if the tag is present, and if the release candidate is "final". + +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +=end + +$: << File.dirname(File.expand_path(__FILE__)) +require '_common' + +# +# Merge the tag to the master branch. +# +def merge_tag_to_master(tag, repo_path) + Dir.chdir(repo_path) do |path| + approve_and_execute([ + "git checkout master", + "git merge #{tag}" + ]) + end +end + +# +# ------------------------------------------------------------------------------------ +# Main method +# ------------------------------------------------------------------------------------ +# + +begin + candidate_label = Settings.confirm_candidate_label(Settings.candidate_label) + tag = Settings.tag_name + vivo_path = Settings.vivo_path + vitro_path = Settings.vitro_path + + raise BadState.new("Only the final release gets merged to the master branch.") unless candidate_label == "final" + raise BadState.new("Tag #{tag} doesn't exist in VIVO.") unless tag_exists?(vivo_path, tag) + raise BadState.new("Tag #{tag} doesn't exist in Vitro.") unless tag_exists?(vitro_path, tag) + raise BadState.new("Tag has already been merged to master branch in VIVO." if tag_commit(tag, vivo_path) == master_commit(vivo_path) + raise BadState.new("Tag has already been merged to master branch in Vitro." if tag_commit(tag, vitro_path) == master_commit(vitro_path) + + puts + yn = prompt("OK to merge the #{tag} tags to the master branches? (y/n)") + if yn.downcase == 'y' + puts + puts "Merging tags" + merge_tag_to_master(tag, vivo_path) + merge_tag_to_master(tag, vitro_path) + puts + else + puts + puts "OK - forget it." + puts + end +rescue BadState + puts "#{$!.message} - Aborting." +end diff --git a/utilities/releaseScripts/_common.rb b/utilities/releaseScripts/_common.rb new file mode 100644 index 00000000..9b891415 --- /dev/null +++ b/utilities/releaseScripts/_common.rb @@ -0,0 +1,213 @@ +=begin +-------------------------------------------------------------------------------- + +Methods and classes used by the scripts. + +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +=end + +# +# ------------------------------------------------------------------------------------ +# This exception class says that we aren't happy with the state of things. +# ------------------------------------------------------------------------------------ +# + +class BadState < Exception +end + +# +# ------------------------------------------------------------------------------------ +# A class with methods for handling the settings. +# ------------------------------------------------------------------------------------ +# + +class Settings + def self.base_directory + `git config --get --global vivo.release.basedirectory`.strip + end + + def self.base_directory=(dir) + `git config --global vivo.release.basedirectory #{dir}` + end + + def self.confirm_base_directory(path) + expanded = File.expand_path(path) + vivo_git = File.expand_path("VIVO/.git", expanded) + vitro_git = File.expand_path("Vitro/.git", expanded) + raise BadState.new("#{expanded} is not a directory") unless File.directory?(expanded) + raise BadState.new("#{expanded} doesn't contain a VIVO repository") unless File.directory?(vivo_git) + raise BadState.new("#{expanded} doesn't contain a Vitro repository") unless File.directory?(vitro_git) + expanded + end + + + def self.username + `git config --get --global user.name`.strip + end + + def self.username=(name) + `git config --global user.name #{name}` + end + + def self.confirm_username(name) + # any string will do. + name + end + + + def self.email + `git config --get --global user.email`.strip + end + + def self.email=(address) + `git config --global user.email #{address}` + end + + def self.confirm_email(address) + # any string will do. + address + end + + + def self.release_label + `git config --get --global vivo.releaselabel`.strip + end + + def self.release_label=(label) + `git config --global vivo.releaselabel #{label}` + end + + def self.confirm_release_label(label) + raise BadState.new("Incorrect format for release label - '#{label}'") unless label =~ /^\d\.\d(\.\d)?$/ + label + end + + + def self.candidate_label + `git config --get --global vivo.candidatelabel`.strip + end + + def self.candidate_label=(label) + `git config --global vivo.candidatelabel #{label}` + end + + def self.confirm_candidate_label(label) + raise BadState.new("Incorrect format for candidate label - '#{label}'") unless label =~ /^(rc\d+|tc\d+|final)$/ + label + end + + # + # Values derived from the settings. + # + + # The name of the maintenance branch. Looks like "maint-rel-4.2" even for releases like "4.2.1" + def self.branch_name + label = Settings.confirm_release_label(Settings.release_label) + major_release = label[0..2] + "maint-rel-#{major_release}" + end + + # The name of the Git tag. Looks like "rel-1.9-tc2" or "rel-1.9" (for final) + def self.tag_name + release_label = Settings.confirm_release_label(Settings.release_label) + label = Settings.confirm_candidate_label(Settings.candidate_label) + suffix = label == "final" ? "" : "-#{label}" + "rel-#{release_label}#{suffix}" + end + + # The message for the Git tag. Looks like "Release 1.9 rc5 tag" + def self.tag_message + release_label = Settings.confirm_release_label(Settings.release_label) + candidate_label = Settings.confirm_candidate_label(Settings.candidate_label) + "Release #{release_label} #{candidate_label} tag" + end + + # Where is the local VIVO repository? Looks like "/Users/jeb228/git/VIVO" + def self.vivo_path + base_directory = Settings.confirm_base_directory(Settings.base_directory) + File.expand_path("VIVO", base_directory) + end + + # Where is the local Vitro repository? Looks like "/Users/jeb228/git/Vitro" + def self.vitro_path + base_directory = Settings.confirm_base_directory(Settings.base_directory) + File.expand_path("Vitro", base_directory) + end + + # Where will the distribution files be created? Looks like "/Users/jeb228/git/release_4.9/tc5/vivo-rel-4.9-tc5" + def self.export_dir + base_directory = Settings.confirm_base_directory(Settings.base_directory) + release_label = Settings.confirm_release_label(Settings.release_label) + candidate_label = Settings.confirm_candidate_label(Settings.candidate_label) + tag_name = Settings.tag_name + File.expand_path("release_#{release_label}/#{candidate_label}/vivo-#{tag_name}", base_directory) + end + + # Where to store the file for revision info in VIVO + def self.vivo_revision_info_path + File.expand_path("revisionInfo", Settings.export_dir) + end + + # Where to store the file for revision info in VIVO + def self.vitro_revision_info_path + File.expand_path("vitro-core/revisionInfo", Settings.export_dir) + end + + # Looks like "vivo-rel-4.9-tc3" or "vivo-rel-4.9" for final + def self.vivo_distribution_filename + "vivo-#{Settings.tag_name}" + end + + # Looks like "vitro-rel-4.9-tc3" or "vitro-rel-4.9" for final + def self.vitro_distribution_filename + "vitro-#{Settings.tag_name}" + end +end + +# +# ------------------------------------------------------------------------------------ +# General-purpose methods. +# ------------------------------------------------------------------------------------ +# + +def prompt(p) + print("#{p} ") + gets.strip +end + +def echo_command(c) + puts ">>>>>> #{c}" + `#{c}` +end + +def approve_and_execute(cmds, prompt="") + if prompt.empty? + puts "Execute these commands?" + else + puts "Execute these commands? (#{prompt})" + end + + puts ">>>>> #{cmds.join("\n>>>>> ")}" + yn = prompt "(y/n)" + raise BadState.new("OK") if (yn.downcase != 'y') + + cmds.each do |cmd| + puts ">>>>> #{cmd}" + puts `#{cmd}` + raise BadState.new("Command failed: code #{$?.exitstatus}") unless $?.success? + end + puts +end + +def branch_exists?(dir, branch) + Dir.chdir(dir) do |path| + ! `git branch --list #{branch}`.strip.empty? + end +end + +def tag_exists?(dir, tag) + Dir.chdir(dir) do |path| + !`git tag --list #{tag}`.strip.empty? + end +end