blob: 59f345935019a84255a7e9d6dfbebb4a638515d5 [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
require "English"
require "open-uri"
require "time"
class PackageTask
include Rake::DSL
def initialize(package, version, release_time, options={})
@package = package
@version = version
@release_time = release_time
@archive_base_name = "#{@package}-#{@version}"
@archive_name = "#{@archive_base_name}.tar.gz"
@full_archive_name = File.expand_path(@archive_name)
@rpm_package = @package
case @version
when /-((dev|rc)\d+)\z/
base_version = $PREMATCH
sub_version = $1
type = $2
if type == "rc" and options[:rc_build_type] == :release
@deb_upstream_version = base_version
@deb_archive_base_name_version = base_version
@rpm_version = base_version
@rpm_release = "1"
else
@deb_upstream_version = "#{base_version}~#{sub_version}"
@deb_archive_base_name_version = @version
@rpm_version = base_version
@rpm_release = "0.#{sub_version}"
end
else
@deb_upstream_version = @version
@deb_archive_base_name_version = @version
@rpm_version = @version
@rpm_release = "1"
end
@deb_release = ENV["DEB_RELEASE"] || "1"
end
def define
define_dist_task
define_apt_task
define_yum_task
define_version_task
define_docker_tasks
end
private
def env_value(name)
value = ENV[name]
raise "Specify #{name} environment variable" if value.nil?
value
end
def debug_build?
ENV["DEBUG"] != "no"
end
def git_directory?(directory)
candidate_paths = [".git", "HEAD"]
candidate_paths.any? do |candidate_path|
File.exist?(File.join(directory, candidate_path))
end
end
def latest_commit_time(git_directory)
return nil unless git_directory?(git_directory)
cd(git_directory) do
return Time.iso8601(`git log -n 1 --format=%aI`.chomp).utc
end
end
def download(url, output_path)
if File.directory?(output_path)
base_name = url.split("/").last
output_path = File.join(output_path, base_name)
end
absolute_output_path = File.expand_path(output_path)
unless File.exist?(absolute_output_path)
mkdir_p(File.dirname(absolute_output_path))
rake_output_message "Downloading... #{url}"
URI(url).open do |downloaded_file|
File.open(absolute_output_path, "wb") do |output_file|
output_file.print(downloaded_file.read)
end
end
end
absolute_output_path
end
def substitute_content(content)
content.gsub(/@(.+?)@/) do |matched|
yield($1, matched)
end
end
def docker_image(os, architecture)
image = "#{@package}-#{os}"
image << "-#{architecture}" if architecture
image
end
def docker_run(os, architecture, console: false)
id = os
id = "#{id}-#{architecture}" if architecture
image = docker_image(os, architecture)
build_command_line = [
"docker",
"build",
"--cache-from", image,
"--tag", image,
]
run_command_line = [
"docker",
"run",
"--rm",
"--log-driver", "none",
"--volume", "#{Dir.pwd}:/host:rw",
]
if $stdin.tty?
run_command_line << "--interactive"
run_command_line << "--tty"
else
run_command_line.concat(["--attach", "STDOUT"])
run_command_line.concat(["--attach", "STDERR"])
end
build_dir = ENV["BUILD_DIR"]
if build_dir
build_dir = "#{File.expand_path(build_dir)}/#{id}"
mkdir_p(build_dir)
run_command_line.concat(["--volume", "#{build_dir}:/build:rw"])
end
if debug_build?
build_command_line.concat(["--build-arg", "DEBUG=yes"])
run_command_line.concat(["--env", "DEBUG=yes"])
end
pass_through_env_names = [
"DEB_BUILD_OPTIONS",
"RPM_BUILD_NCPUS",
]
pass_through_env_names.each do |name|
value = ENV[name]
next unless value
run_command_line.concat(["--env", "#{name}=#{value}"])
end
if File.exist?(File.join(id, "Dockerfile"))
docker_context = id
else
from = File.readlines(File.join(id, "from")).find do |line|
/^[a-z]/i =~ line
end
build_command_line.concat(["--build-arg", "FROM=#{from.chomp}"])
docker_context = os
end
build_command_line.concat(docker_build_options(os, architecture))
run_command_line.concat(docker_run_options(os, architecture))
build_command_line << docker_context
run_command_line << image
run_command_line << "/host/build.sh" unless console
sh(*build_command_line)
sh(*run_command_line)
end
def docker_build_options(os, architecture)
[]
end
def docker_run_options(os, architecture)
[]
end
def docker_pull(os, architecture)
image = docker_image(os, architecture)
command_line = [
"docker",
"pull",
image,
]
command_line.concat(docker_pull_options(os, architecture))
sh(*command_line)
end
def docker_pull_options(os, architecture)
[]
end
def docker_push(os, architecture)
image = docker_image(os, architecture)
command_line = [
"docker",
"push",
image,
]
command_line.concat(docker_push_options(os, architecture))
sh(*command_line)
end
def docker_push_options(os, architecture)
[]
end
def define_dist_task
define_archive_task
desc "Create release package"
task :dist => [@archive_name]
end
def enable_apt?
true
end
def apt_targets
return [] unless enable_apt?
targets = (ENV["APT_TARGETS"] || "").split(",")
targets = apt_targets_default if targets.empty?
targets.find_all do |target|
Dir.exist?(File.join(apt_dir, target))
end
end
def apt_targets_default
# Disable arm64 targets by default for now
# because they require some setups on host.
[
"debian-buster",
# "debian-buster-arm64",
"debian-bullseye",
# "debian-bullseye-arm64",
"ubuntu-bionic",
# "ubuntu-bionic-arm64",
"ubuntu-focal",
# "ubuntu-focal-arm64",
"ubuntu-groovy",
# "ubuntu-groovy-arm64",
]
end
def deb_archive_base_name
"#{@package}-#{@deb_archive_base_name_version}"
end
def deb_archive_name
"#{@package}-#{@deb_upstream_version}.tar.gz"
end
def apt_dir
"apt"
end
def apt_prepare_debian_dir(tmp_dir, target)
source_debian_dir = nil
specific_debian_dir = "debian.#{target}"
distribution, code_name, _architecture = target.split("-", 3)
platform = [distribution, code_name].join("-")
platform_debian_dir = "debian.#{platform}"
if File.exist?(specific_debian_dir)
source_debian_dir = specific_debian_dir
elsif File.exist?(platform_debian_dir)
source_debian_dir = platform_debian_dir
else
source_debian_dir = "debian"
end
prepared_debian_dir = "#{tmp_dir}/debian.#{target}"
cp_r(source_debian_dir, prepared_debian_dir)
control_in_path = "#{prepared_debian_dir}/control.in"
if File.exist?(control_in_path)
control_in = File.read(control_in_path)
rm_f(control_in_path)
File.open("#{prepared_debian_dir}/control", "w") do |control|
prepared_control = apt_prepare_debian_control(control_in, target)
control.print(prepared_control)
end
end
end
def apt_prepare_debian_control(control_in, target)
message = "#{__method__} must be defined to use debian/control.in"
raise NotImplementedError, message
end
def apt_build(console: false)
tmp_dir = "#{apt_dir}/tmp"
rm_rf(tmp_dir)
mkdir_p(tmp_dir)
cp(deb_archive_name,
File.join(tmp_dir, deb_archive_name))
apt_targets.each do |target|
apt_prepare_debian_dir(tmp_dir, target)
end
env_sh = "#{apt_dir}/env.sh"
File.open(env_sh, "w") do |file|
file.puts(<<-ENV)
PACKAGE=#{@package}
VERSION=#{@deb_upstream_version}
ENV
end
apt_targets.each do |target|
cd(apt_dir) do
distribution, version, architecture = target.split("-", 3)
os = "#{distribution}-#{version}"
docker_run(os, architecture, console: console)
end
end
end
def define_apt_task
namespace :apt do
source_build_sh = "#{__dir__}/apt/build.sh"
build_sh = "#{apt_dir}/build.sh"
repositories_dir = "#{apt_dir}/repositories"
file build_sh => source_build_sh do
cp(source_build_sh, build_sh)
end
directory repositories_dir
desc "Build deb packages"
if enable_apt?
build_dependencies = [
deb_archive_name,
build_sh,
repositories_dir,
]
else
build_dependencies = []
end
task :build => build_dependencies do
apt_build if enable_apt?
end
namespace :build do
desc "Open console"
task :console => build_dependencies do
apt_build(console: true) if enable_apt?
end
end
end
desc "Release APT repositories"
apt_tasks = [
"apt:build",
]
task :apt => apt_tasks
end
def enable_yum?
true
end
def yum_targets
return [] unless enable_yum?
targets = (ENV["YUM_TARGETS"] || "").split(",")
targets = yum_targets_default if targets.empty?
targets.find_all do |target|
Dir.exist?(File.join(yum_dir, target))
end
end
def yum_targets_default
# Disable aarch64 targets by default for now
# because they require some setups on host.
[
"centos-7",
# "centos-7-aarch64",
"centos-8",
# "centos-8-aarch64",
]
end
def rpm_archive_base_name
"#{@package}-#{@rpm_version}"
end
def rpm_archive_name
"#{rpm_archive_base_name}.tar.gz"
end
def yum_dir
"yum"
end
def yum_build_sh
"#{yum_dir}/build.sh"
end
def yum_expand_variable(key)
case key
when "PACKAGE"
@rpm_package
when "VERSION"
@rpm_version
when "RELEASE"
@rpm_release
else
nil
end
end
def yum_spec_in_path
"#{yum_dir}/#{@rpm_package}.spec.in"
end
def yum_build(console: false)
tmp_dir = "#{yum_dir}/tmp"
rm_rf(tmp_dir)
mkdir_p(tmp_dir)
cp(rpm_archive_name,
File.join(tmp_dir, rpm_archive_name))
env_sh = "#{yum_dir}/env.sh"
File.open(env_sh, "w") do |file|
file.puts(<<-ENV)
SOURCE_ARCHIVE=#{rpm_archive_name}
PACKAGE=#{@rpm_package}
VERSION=#{@rpm_version}
RELEASE=#{@rpm_release}
ENV
end
spec = "#{tmp_dir}/#{@rpm_package}.spec"
spec_in_data = File.read(yum_spec_in_path)
spec_data = substitute_content(spec_in_data) do |key, matched|
yum_expand_variable(key) || matched
end
File.open(spec, "w") do |spec_file|
spec_file.print(spec_data)
end
yum_targets.each do |target|
cd(yum_dir) do
distribution, version, architecture = target.split("-", 3)
os = "#{distribution}-#{version}"
docker_run(os, architecture, console: console)
end
end
end
def define_yum_task
namespace :yum do
source_build_sh = "#{__dir__}/yum/build.sh"
file yum_build_sh => source_build_sh do
cp(source_build_sh, yum_build_sh)
end
repositories_dir = "#{yum_dir}/repositories"
directory repositories_dir
desc "Build RPM packages"
if enable_yum?
build_dependencies = [
repositories_dir,
rpm_archive_name,
yum_build_sh,
yum_spec_in_path,
]
else
build_dependencies = []
end
task :build => build_dependencies do
yum_build if enable_yum?
end
namespace :build do
desc "Open console"
task :console => build_dependencies do
yum_build(console: true) if enable_yum?
end
end
end
desc "Release Yum repositories"
yum_tasks = [
"yum:build",
]
task :yum => yum_tasks
end
def define_version_task
namespace :version do
desc "Update versions"
task :update do
update_debian_changelog
update_spec
end
end
end
def package_changelog_message
"New upstream release."
end
def packager_name
ENV["DEBFULLNAME"] || ENV["NAME"] || guess_packager_name_from_git
end
def guess_packager_name_from_git
name = `git config --get user.name`.chomp
return name unless name.empty?
`git log -n 1 --format=%aN`.chomp
end
def packager_email
ENV["DEBEMAIL"] || ENV["EMAIL"] || guess_packager_email_from_git
end
def guess_packager_email_from_git
email = `git config --get user.email`.chomp
return email unless email.empty?
`git log -n 1 --format=%aE`.chomp
end
def update_content(path)
if File.exist?(path)
content = File.read(path)
else
content = ""
end
content = yield(content)
File.open(path, "w") do |file|
file.puts(content)
end
end
def update_debian_changelog
return unless enable_apt?
Dir.glob("debian*") do |debian_dir|
update_content("#{debian_dir}/changelog") do |content|
<<-CHANGELOG.rstrip
#{@package} (#{@deb_upstream_version}-#{@deb_release}) unstable; urgency=low
* New upstream release.
-- #{packager_name} <#{packager_email}> #{@release_time.rfc2822}
#{content}
CHANGELOG
end
end
end
def update_spec
return unless enable_yum?
release_time = @release_time.strftime("%a %b %d %Y")
update_content(yum_spec_in_path) do |content|
content = content.sub(/^(%changelog\n)/, <<-CHANGELOG)
%changelog
* #{release_time} #{packager_name} <#{packager_email}> - #{@rpm_version}-#{@rpm_release}
- #{package_changelog_message}
CHANGELOG
content = content.sub(/^(Release:\s+)\d+/, "\\11")
content.rstrip
end
end
def define_docker_tasks
namespace :docker do
pull_tasks = []
push_tasks = []
(apt_targets + yum_targets).each do |target|
distribution, version, architecture = target.split("-", 3)
os = "#{distribution}-#{version}"
namespace :pull do
desc "Pull built image for #{target}"
task target do
docker_pull(os, architecture)
end
pull_tasks << "docker:pull:#{target}"
end
namespace :push do
desc "Push built image for #{target}"
task target do
docker_push(os, architecture)
end
push_tasks << "docker:push:#{target}"
end
end
desc "Pull built images"
task :pull => pull_tasks
desc "Push built images"
task :push => push_tasks
end
end
end