tzinfo-data/Rakefile

545 lines
14 KiB
Ruby

require 'rubygems'
require 'rubygems/package_task'
require 'rake/testtask'
require 'open-uri'
require 'zlib'
# Ignore errors loading rdoc/task (the rdoc tasks will be excluded if
# rdoc is unavailable).
begin
require 'rdoc/task'
rescue LoadError, RuntimeError
end
BASE_DIR = File.dirname(__FILE__)
LIB_DIR = File.join(BASE_DIR, 'lib')
BUILD_TZ_MODULES_DIR = File.join(BASE_DIR, '.build_tz_modules')
DATA_OUTPUT_DIR = File.join(BASE_DIR, 'lib', 'tzinfo', 'data')
TZDB_DIR = 'tzdb'
TZDB_GPG_KEYRING = 'gpg.keyring'
TZDB_GPG_KEYRING_PATH = File.join(TZDB_DIR, TZDB_GPG_KEYRING)
require_relative File.join('lib', 'tzinfo', 'data', 'version')
class << self
def tzdb_version
TZInfo::Data::Version::TZDATA
end
def tzdb_name(type, options = {})
temp = options[:temp]
prefix = options[:prefix]
suffix = options[:suffix]
version = options[:version] ? tzdb_version : ''
"#{temp ? '.' : ''}#{prefix}#{type}#{version}#{suffix}#{temp ? '.tmp' : ''}"
end
def tzdb_dir_name(type, options = {})
tzdb_name(type, options)
end
def tzdb_tgz_name(type, options = {})
tzdb_name(type, options.merge(:prefix => 'tz', :suffix => '.tar.gz', :version => true))
end
def tzdb_asc_name(type, options = {})
tzdb_name(type, options.merge(:prefix => 'tz', :suffix => '.tar.gz.asc', :version => true))
end
def tzdb_ext_name(ext, type, options = {})
send("tzdb_#{ext}_name", type, options)
end
[:bin, :combined, :zoneinfo].each do |t|
name = t.to_s
define_method("tzdb_#{name}_name") do |*args|
tzdb_name(name, args.first || {})
end
define_method("tzdb_#{name}_path") do |*args|
tzdb_path(send("tzdb_#{name}_name", args.first || {}))
end
end
def tzdb_combined_name(options = {})
tzdb_name('combined', options)
end
def tzdb_bin_name(options = {})
tzdb_name('bin', options)
end
def tzdb_path(name = nil)
components = [TZDB_DIR, tzdb_version]
components << name if name
File.join(*components)
end
[:dir, :tgz, :asc].each do |t|
define_method("tzdb_#{t}_path") do |type, *args|
tzdb_path(send("tzdb_#{t}_name", type, args.first || {}))
end
end
def tzdb_data_dir_path
tzdb_dir_path('data')
end
def tzdb_ext_path(ext, type, options = {})
send("tzdb_#{ext}_path", type, options)
end
def tzdb_exe_path(exe)
File.join(tzdb_bin_path, exe.to_s)
end
end
task :default => [:test]
spec = eval(File.read('tzinfo-data.gemspec'))
class TZInfoPackageTask < Gem::PackageTask
alias_method :orig_sh, :sh
private :orig_sh
def sh(*cmd, &block)
if cmd[0] == '__tar_with_owner__' && cmd[1] =~ /\A-?[zjcvf]+\z/
opts = cmd[1]
cmd = ['tar', 'c', '--owner', '0', '--group', '0', "#{opts.start_with?('-') ? '' : '-'}#{opts.gsub('c', '')}"] + cmd.drop(2)
elsif cmd.first =~ /\A__tar_with_owner__ -?([zjcvf]+)(.*)\z/
opts = $1
args = $2
cmd[0] = "tar c --owner 0 --group 0 -#{opts.gsub('c', '')}#{args}"
end
orig_sh(*cmd, &block)
end
end
def add_signing_key(spec)
# Attempt to find the private key and add options to sign the gem if found.
private_key_path = File.expand_path(File.join(BASE_DIR, '..', 'key', 'gem-private_key.pem'))
if File.exist?(private_key_path)
spec = spec.clone
spec.signing_key = private_key_path
spec.cert_chain = [File.join(BASE_DIR, 'gem-public_cert.pem')]
else
puts 'WARNING: Private key not found. Not signing gem file.'
end
spec
end
package_task = TZInfoPackageTask.new(add_signing_key(spec)) do |pkg|
pkg.need_zip = true
pkg.need_tar_gz = true
pkg.tar_command = '__tar_with_owner__'
end
# Skip the rdoc task if RDoc::Task is unavailable
if defined?(RDoc) && defined?(RDoc::Task)
RDoc::Task.new do |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.options.concat spec.rdoc_options
rdoc.rdoc_files.include(spec.extra_rdoc_files)
rdoc.rdoc_files.include('lib')
end
end
Rake::Task[package_task.package_dir_path].enhance do
recurse_chmod(package_task.package_dir_path)
end
Rake::Task[:package].enhance do
FileUtils.rm_rf(package_task.package_dir_path)
end
def recurse_chmod(dir)
File.chmod(0755, dir)
Dir.entries(dir).each do |entry|
if entry != '.' && entry != '..'
path = File.join(dir, entry)
if File.directory?(path)
recurse_chmod(path)
else
File.chmod(0644, path)
end
end
end
end
namespace :env do
[[:tzdata, :data], [:zoneinfo, :zoneinfo]].each do |env, dir|
env_name = env.to_s.upcase
path = ENV[env_name]
if path
task env do
puts "The #{env_name} environment variable is set. Using #{env} files from: #{File.absolute_path(path)}"
end
else
task env => tzdb_dir_path(dir) do
ENV[env_name] = tzdb_dir_path(dir)
end
end
end
if ENV['ZDUMP']
task :zdump do
path = ENV['ZDUMP']
puts "The ZDUMP environment variable is set. Using zdump from: #{File.absolute_path(path)}"
version = `#{path} --version`
unless version =~ Regexp.new("\\b#{Regexp.escape(tzdb_version)}\\b")
puts "Warning: The version of zdump specified by ZDUMP does not match the tzdata version (#{tzdb_version})."
puts "The reported zdump version is: #{version}"
end
end
else
task :zdump => tzdb_exe_path(:zdump) do
ENV['ZDUMP'] = tzdb_exe_path(:zdump)
end
end
end
Rake::TestTask.new(:test => ['env:tzdata', 'env:zdump', 'env:zoneinfo']) do |t|
require 'tzinfo'
t.libs = ['lib']
$:.each do |dir|
if File.exist?(File.join(dir, 'tzinfo.rb'))
t.libs << File.expand_path(dir)
end
end
t.pattern = File.join(File.expand_path(File.dirname(__FILE__)), 'test', 'ts_all.rb')
t.warning = true
end
test_task = Rake::Task[:test]
test_task.clear_comments
test_task.add_description('Run tests')
desc 'Produce TZInfo::Data Ruby modules from the IANA Time Zone Database'
task :build_tz_modules => 'env:tzdata' do
require File.join(LIB_DIR, 'tzinfo', 'data', 'tzdataparser')
FileUtils.mkdir_p(BUILD_TZ_MODULES_DIR)
begin
p = TZInfo::Data::TZDataParser.new(ENV['TZDATA'], BUILD_TZ_MODULES_DIR)
p.execute
scm = Scm.create(BASE_DIR)
['indexes', 'definitions'].each do |dir|
scm.sync(File.join(BUILD_TZ_MODULES_DIR, dir), File.join(DATA_OUTPUT_DIR, dir))
end
ensure
FileUtils.rm_rf(BUILD_TZ_MODULES_DIR)
end
end
class Scm
def self.create(dir)
if File.directory?(File.join(dir, '.git'))
GitScm.new(dir)
elsif File.directory?(File.join(dir, '.svn'))
SvnScm.new(dir)
else
NullScm.new(dir)
end
end
def initialize(dir)
end
def sync(source_dir, target_dir)
puts "Sync from #{source_dir} to #{target_dir}#{command ? " using #{command}" : ''}"
sync_dirs(source_dir, target_dir)
end
protected
def exec_scm(params)
puts "#{command} #{params}"
`#{command} #{params}`
raise "#{command} exited with status #$?" if $? != 0
end
private
def sync_dirs(source_dir, target_dir)
# Assumes a directory will never turn into a file and vice-versa
# (files will all end in .rb, directories won't).
source_entries, target_entries = [source_dir, target_dir].collect do |dir|
Dir.entries(dir).delete_if {|entry| entry =~ /\A\./}.sort
end
until source_entries.empty? || target_entries.empty?
last_source = source_entries.last
last_target = target_entries.last
if last_source == last_target
source_file = File.join(source_dir, last_source)
target_file = File.join(target_dir, last_target)
if File.directory?(source_file)
sync_dirs(source_file, target_file)
else
FileUtils.cp(source_file, target_file)
end
source_entries.pop
target_entries.pop
elsif source_entries.last < target_entries.last
sync_only_in_target(target_dir, target_entries)
else
sync_only_in_source(source_dir, target_dir, source_entries)
end
end
until target_entries.empty?
sync_only_in_target(target_dir, target_entries)
end
until source_entries.empty?
sync_only_in_source(source_dir, target_dir, source_entries)
end
end
def sync_only_in_target(target_dir, target_entries)
target_file = File.join(target_dir, target_entries.last)
delete(target_file)
target_entries.pop
end
def sync_only_in_source(source_dir, target_dir, source_entries)
source_file = File.join(source_dir, source_entries.last)
target_file = File.join(target_dir, source_entries.last)
if File.directory?(source_file)
Dir.mkdir(target_file)
add(target_file)
sync_dirs(source_file, target_file)
else
FileUtils.cp(source_file, target_file)
add(target_file)
end
source_entries.pop
end
end
class NullScm < Scm
def command
nil
end
def add(file)
end
def delete(file)
puts "rm -rf \"#{file}\""
FileUtils.rm_rf(file)
end
end
class GitScm < Scm
def command
'git'
end
def add(file)
unless File.directory?(file)
exec_scm "add \"#{file}\""
end
end
def delete(file)
exec_scm "rm -rf \"#{file}\""
end
end
class SvnScm < Scm
def command
'svn'
end
def add(file)
exec_scm "add \"#{file}\""
end
def delete(file)
exec_scm "delete --force \"#{file}\""
end
end
desc "Rebuild the Ruby module for a single zone specified by the 'zone' environment variable"
task :build_tz_module => 'env:tzdata' do
require File.join(LIB_DIR, 'tzinfo', 'data', 'tzdataparser')
p = TZInfo::Data::TZDataParser.new(ENV['TZDATA'], DATA_OUTPUT_DIR)
p.generate_countries = false
p.only_zones = [ENV['zone']]
p.execute
end
desc 'Rebuild the countries index'
task :build_countries => 'env:tzdata' do
require File.join(LIB_DIR, 'tzinfo', 'data', 'tzdataparser')
p = TZInfo::Data::TZDataParser.new(ENV['TZDATA'], DATA_OUTPUT_DIR)
p.generate_countries = true
p.generate_zones = false
p.execute
end
directory TZDB_DIR
file TZDB_GPG_KEYRING_PATH => ['tzdb-gpg-keys.asc', TZDB_DIR] do
rm_f(TZDB_GPG_KEYRING_PATH)
sh("gpg --no-default-keyring --keyring '#{TZDB_GPG_KEYRING_PATH}' --import tzdb-gpg-keys.asc")
end
directory tzdb_path => TZDB_DIR
[:code, :data].each do |type|
[:tgz, :asc].each do |ext|
path = tzdb_ext_path(ext, type)
file_create path => tzdb_path do
temp_path = tzdb_ext_path(ext, type, :temp => true)
url = "https://data.iana.org/time-zones/releases/#{tzdb_ext_name(ext, type)}"
attempt = 1
begin
puts "Downloading #{url}"
URI.parse(url).open do |http|
File.open(temp_path, 'wb') do |temp_file|
copy_stream(http, temp_file)
end
end
rescue Exception => e
if attempt < 3
puts "Download failed: #{e}"
puts "Retrying"
attempt += 1
retry
end
raise
end
mv(temp_path, path)
end
end
tgz_path = tzdb_tgz_path(type)
asc_path = tzdb_asc_path(type)
dir_path = tzdb_dir_path(type)
file_create dir_path => [tzdb_path, tgz_path, asc_path, TZDB_GPG_KEYRING_PATH] do
sh("gpg --no-default-keyring --keyring '#{TZDB_GPG_KEYRING_PATH}' --verify '#{asc_path}'")
tmp_path = tzdb_dir_path(type, :temp => true)
rm_rf(tmp_path)
mkdir_p(tmp_path)
sh("tar xzf '#{tgz_path}' -C '#{tmp_path}'")
mv(tmp_path, dir_path)
end
namespace :tzdb do
namespace :download do
desc "Downloads the tz#{type} release (version #{tzdb_version})"
task type => [tgz_path, asc_path]
end
namespace :extract do
desc "Extracts the tz#{type} release (version #{tzdb_version})"
task type => dir_path
end
end
end
file_create tzdb_combined_path => [tzdb_path, tzdb_dir_path('code'), tzdb_dir_path('data')] do
tmp_path = tzdb_combined_path(:temp => true)
rm_rf(tmp_path)
mkdir_p(tmp_path)
%w(code data).each do |type|
src_dir = tzdb_dir_path(type)
Dir.entries(src_dir).each do |entry|
if entry != '.' && entry != '..'
dest_path = File.join(tmp_path, entry)
ln(File.join(src_dir, entry), dest_path) unless File.exist?(dest_path)
end
end
end
mv(tmp_path, tzdb_combined_path)
end
directory tzdb_bin_path => tzdb_path
[:zdump, :zic].each do |exe|
file_create tzdb_exe_path(exe) => [tzdb_combined_path, tzdb_bin_path] do
sh("make -C '#{tzdb_combined_path}' #{exe}")
ln(File.join(tzdb_combined_path, exe.to_s), tzdb_exe_path(exe))
end
end
file_create tzdb_zoneinfo_path => [tzdb_path, tzdb_data_dir_path, tzdb_exe_path(:zic)] do
tmp_path = tzdb_zoneinfo_path(:temp => true)
rm_rf(tmp_path)
mkdir_p(tmp_path)
data_path = tzdb_data_dir_path
files = Dir.entries(data_path).select do |name|
name =~ /\A[^\.]+\z/ &&
!%w(backzone calendars leapseconds CONTRIBUTING LICENSE Makefile NEWS README SECURITY SOURCE Theory version).include?(name) &&
File.file?(File.join(data_path, name))
end
sh("#{tzdb_exe_path(:zic)} -d \"#{tmp_path}\" #{files.map {|f| "\"#{File.join(data_path, f)}\""}.join(' ')}")
mv(tmp_path, tzdb_zoneinfo_path)
end
namespace :tzdb do
[:download, :extract].each do |task_name|
desc "#{task_name.to_s.capitalize}s the tzcode and tzdata releases (version #{tzdb_version})"
task task_name => [:code, :data].map {|t| "tzdb:#{task_name}:#{t}"}
end
desc "Builds the zdump and zic executables (version #{tzdb_version})"
task :bin => [:zdump, :zic].map {|e| "tzdb:bin:#{e}" }
namespace :bin do
[:zdump, :zic].each do |exe|
desc "Builds the #{exe} executable (version #{tzdb_version})"
task exe => tzdb_exe_path(exe)
end
end
desc "Builds zoneinfo files (version #{tzdb_version})"
task :zoneinfo => tzdb_zoneinfo_path
desc "Removes all Time Zone Database files"
task :clean do
rm_rf(TZDB_DIR)
end
namespace :clean do
desc "Removes all Time Zone Database files for the current version (#{tzdb_version})"
task :current do
rm_rf(tzdb_path)
end
desc "Removes all Time Zone Database files for versions other than the current version (#{tzdb_version})"
task :other do
version = tzdb_version
Dir.entries(TZDB_DIR).each do |entry|
if version != entry && entry =~ /\A\d{4,}[a-z]+\z/
path = File.join(TZDB_DIR, entry)
rm_rf(path) if File.directory?(path)
end
end
end
end
end