diff --git a/exe/pgpm b/exe/pgpm index 929a04b..dd6b974 100755 --- a/exe/pgpm +++ b/exe/pgpm @@ -101,35 +101,53 @@ module Pgpm exit(1) end - unless os.is_a?(Pgpm::OS::RedHat) - puts "#{os.name} is not a supported OS at this moment" - exit(1) - end - puts "Building #{pkgs.map { |p| "#{p.name}@#{p.version}" }.join(", ")} for Postgres #{matching_pgver}" - selected_pgdist = Postgres::RedhatBasedPgdg.new(matching_pgver.to_s) - - os.with_scope do - arch.with_scope do - selected_pgdist.with_scope do - pkgs = pkgs.flat_map(&:topologically_ordered_with_dependencies).uniq.reject(&:contrib?) - - b = pkgs.reduce(nil) do |c, p| - if p.broken? - puts "Can't build a broken package #{p.name}@#{p.version}" - exit(1) + if os.is_a? Pgpm::OS::Debian + puts "Building #{pkgs.map { |p| "#{p.name}@#{p.version}" }.join(", ")} for Postgres #{matching_pgver}" + selected_pgdist = Postgres::RedhatBasedPgdg.new(matching_pgver.to_s) + + os.with_scope do + arch.with_scope do + selected_pgdist.with_scope do + spec = nil + pkgs.reduce(nil) do |_c, p| + p = Pgpm::ScopedObject.new(p, os, arch) + spec = p.to_deb_spec end - p = Pgpm::ScopedObject.new(p, os, arch) - spec = p.to_rpm_spec - builder = Pgpm::RPM::Builder.new(spec) - src_builder = builder.source_builder - p = c.nil? ? src_builder : c.and_then(src_builder) - p.and_then(builder.versionless_builder) + builder = Pgpm::Deb::Builder.new(spec) + builder.build end + end + end + elsif os.is_a? Pgpm::OS::RedHat + puts "Building #{pkgs.map { |p| "#{p.name}@#{p.version}" }.join(", ")} for Postgres #{matching_pgver}" + selected_pgdist = Postgres::RedhatBasedPgdg.new(matching_pgver.to_s) + + os.with_scope do + arch.with_scope do + selected_pgdist.with_scope do + pkgs = pkgs.flat_map(&:topologically_ordered_with_dependencies).uniq.reject(&:contrib?) + + b = pkgs.reduce(nil) do |c, p| + if p.broken? + puts "Can't build a broken package #{p.name}@#{p.version}" + exit(1) + end + p = Pgpm::ScopedObject.new(p, os, arch) + spec = p.to_rpm_spec + builder = Pgpm::RPM::Builder.new(spec) + src_builder = builder.source_builder + p = c.nil? ? src_builder : c.and_then(src_builder) + p.and_then(builder.versionless_builder) + end - srpms = b.call - Pgpm::RPM::Builder.builder(srpms).call + srpms = b.call + Pgpm::RPM::Builder.builder(srpms).call + end end end + else + puts "#{os.name} is not a supported OS at this moment" + exit(1) end end diff --git a/lib/pgpm/deb/Dockerfile b/lib/pgpm/deb/Dockerfile new file mode 100644 index 0000000..46a9940 --- /dev/null +++ b/lib/pgpm/deb/Dockerfile @@ -0,0 +1,31 @@ +# syntax = docker/dockerfile:experimental + +# IMPORTANT: build it this way to allow for privileged execution +# +# Docker daemon config should have the entitlement +# ```json +# { "builder": {"Entitlements": {"security-insecure": true }} } +# ``` +# ``` +# DOCKER_BUILDKIT=1 docker build --allow security.insecure -t IMAGE_NAME /path/to/pgpm +# ``` + +# This Dockerfile is used to build a Debian image, which includes pbuilder and +# pbuilder chroot image with basic dependendencies needed for building most +# packages already pre-installed. + +FROM docker.io/library/debian + +MAINTAINER PGPM Debian Maintainer debian.maintainer@postgres.pm + +VOLUME /proc +ARG DEBIAN_FRONTEND=noninteractive +RUN apt update +RUN apt install -y build-essential pbuilder fakeroot fakechroot +RUN echo 'MIRRORSITE=http://deb.debian.org/debian' > /etc/pbuilderrc +RUN echo 'AUTO_DEBSIGN=${AUTO_DEBSIGN:-no}' > /root/.pbuilderrc +RUN echo 'HOOKDIR=/var/cache/pbuilder/hooks' >> /root/.pbuilderrc +RUN --security=insecure pbuilder create + +COPY pbuilder_install_script.sh /root/pbuilder_install_script.sh +RUN --security=insecure pbuilder execute --save-after-exec /root/pbuilder_install_script.sh diff --git a/lib/pgpm/deb/builder.rb b/lib/pgpm/deb/builder.rb new file mode 100644 index 0000000..a036c8c --- /dev/null +++ b/lib/pgpm/deb/builder.rb @@ -0,0 +1,207 @@ +# frozen_string_literal: true + +require "English" +require "debug" + +module Pgpm + module Deb + class Builder + def initialize(spec) + @spec = spec + @container_name = "pgpm-debian_build-#{Time.now.to_i}_#{rand(10_000)}" + @pgpm_dir = Dir.mktmpdir + end + + def build + pull_image + start_container + patch_pbuilder + + prepare_versioned_source + generate_deb_src_files(:versioned) + run_build(:versioned) + copy_build_from_container(:versioned) + + prepare_default_source + generate_deb_src_files(:default) + run_build(:default) + copy_build_from_container(:default) + + cleanup + end + + private + + # Depends on postgres version and arch + def image_name + "quay.io/qount25/pgpm-debian-pg#{@spec.package.postgres_major_version}-#{@spec.arch}" + end + + def prepare_versioned_source + puts "Preparing build..." + puts " Creating container dir structure..." + Dir.mkdir "#{@pgpm_dir}/source-versioned" + Dir.mkdir "#{@pgpm_dir}/out" + + puts " Downloading and unpacking sources to #{@pgpm_dir}" + + fn = nil + @spec.sources.map do |src| + srcfile = File.join(@pgpm_dir.to_s, src.name) + File.write(srcfile, src.read) + fn = src.name + end + + system("tar -xf #{@pgpm_dir}/#{fn} -C #{@pgpm_dir}/source-versioned/") + FileUtils.remove("#{@pgpm_dir}/#{fn}") + + untar_dir_entries = Dir.entries("#{@pgpm_dir}/source-versioned/").reject do |entry| + [".", ".."].include?(entry) + end + + if untar_dir_entries.size == 1 + entry = untar_dir_entries[0] + if File.directory?("#{@pgpm_dir}/source-versioned/#{entry}") + FileUtils.mv "#{@pgpm_dir}/source-versioned/#{entry}", "#{@pgpm_dir}/" + FileUtils.remove_dir "#{@pgpm_dir}/source-versioned/" + FileUtils.mv "#{@pgpm_dir}/#{entry}", "#{@pgpm_dir}/source-versioned" + end + end + + ["prepare_artifacts.sh"].each do |f| + script_fn = File.expand_path("#{__dir__}/scripts/#{f}") + FileUtils.cp script_fn, "#{@pgpm_dir}/source-versioned/" + end + end + + def prepare_default_source + Dir.mkdir "#{@pgpm_dir}/source-default" + + # 1. All pbuilder builds are in /var/cache/pbuilder/build. At this point + # there's only one build, but we don't know what the directory is named + # (the name is usually some numbers). So we just pick the first (and only) + # entry at this location and this is our build dir. + pbuilds_dir = "/var/cache/pbuilder/build" + cmd = "ls -U #{pbuilds_dir} | head -1" + build_dir = `podman exec #{@container_name} /bin/bash -c '#{cmd}'`.strip + puts "BUILD DIR IS: #{pbuilds_dir}/#{build_dir}" + + # 2. Determine the name of the .control file inside the versioned build + deb_dir = "#{pbuilds_dir}/#{build_dir}/build/#{@spec.deb_pkg_name(:versioned)}-0/debian/#{@spec.deb_pkg_name(:versioned)}" + control_fn = "#{deb_dir}/usr/share/postgresql/#{@spec.package.postgres_major_version}/extension/#{@spec.package.extension_name}--#{@spec.package.version}.control" + + # 3. Copy .control file to the source-default dir + puts "Copying #{control_fn} into /root/pgpm/source-default/" + target_control_fn = "/root/pgpm/source-default/#{@spec.package.extension_name}.control" + cmd = "cp #{control_fn} #{target_control_fn}" + system("podman exec #{@container_name} /bin/bash -c '#{cmd}'") + + ["install_default_control.sh"].each do |fn| + script_fn = File.expand_path("#{__dir__}/scripts/#{fn}") + FileUtils.cp script_fn, "#{@pgpm_dir}/source-default/" + end + end + + def pull_image + puts "Checking if podman image exists..." + # Check if image exists + system("podman image exists #{image_name}") + if $CHILD_STATUS.to_i.positive? # image doesn't exist -- pull image from a remote repository + puts " No. Pulling image #{image_name}..." + system("podman pull #{image_name}") + else + puts " Yes, image #{image_name} already exists! OK" + end + end + + def generate_deb_src_files(pkg_type = :versioned) + puts "Generating debian files..." + Dir.mkdir "#{@pgpm_dir}/source-#{pkg_type}/debian" + %i[changelog control copyright files rules].each do |f| + puts " -> #{@pgpm_dir}/source-#{pkg_type}/debian/#{f}" + File.write "#{@pgpm_dir}/source-#{pkg_type}/debian/#{f}", @spec.generate(f, pkg_type) + end + File.chmod 0o740, "#{@pgpm_dir}/source-#{pkg_type}/debian/rules" # rules file must be executable + end + + def start_container + # podman create options + create_opts = " -v #{@pgpm_dir}:/root/pgpm" + create_opts += ":z" if selinux_enabled? + create_opts += " --privileged --tmpfs /tmp" + create_opts += " --name #{@container_name} #{image_name}" + + puts " Creating and starting container #{@container_name} & running pbuilder" + system("podman create -it #{create_opts}") + exit(1) if $CHILD_STATUS.to_i.positive? + system("podman start #{@container_name}") + exit(1) if $CHILD_STATUS.to_i.positive? + end + + # Prevents clean-up after pbuilder finishes. There's no option + # in pbuilder to do it, so we have to patch it manually. The issue is + # with pbuilder not being able to delete some directories (presumably, + # due to directory names starting with ".") and returning error. + # + # This little patch avoids the error by returning from the python cleanup + # function early -- because the package itself is built successfully and + # we don't actually care that pbuilder is unable to clean something up. + # The container is going to be removed anyway, so it's even less work as + # a result. + def patch_pbuilder + cmd = "sed -E -i \"s/(^function clean_subdirectories.*$)/\\1\\n return/g\" /usr/lib/pbuilder/pbuilder-modules" + system("podman exec #{@container_name} /bin/bash -c '#{cmd}'") + end + + def run_build(pkg_type = :versioned) + dsc_fn = "#{@spec.deb_pkg_name(pkg_type)}_0-1.dsc" + deb_fn = "#{@spec.deb_pkg_name(pkg_type)}_0-1_#{@spec.arch}.deb" + + cmds = [] + cmds << "dpkg-buildpackage --build=source -d" # -d flag helps with dependencies error + cmds << "fakeroot pbuilder build ../#{dsc_fn}" + cmds << "mv /var/cache/pbuilder/result/#{deb_fn} /root/pgpm/out/" + + puts " Building package with pbuilder..." + cmds.each do |cmd| + system("podman exec -w /root/pgpm/source-#{pkg_type} #{@container_name} /bin/bash -c '#{cmd}'") + exit(1) if $CHILD_STATUS.to_i.positive? + end + end + + def copy_build_from_container(pkg_type = :versioned) + puts "Copying .deb file from podman container into current directory..." + deb_fn = "#{@spec.deb_pkg_name(pkg_type)}_0-1_#{@spec.arch}.deb" + deb_copy_fn = "#{@spec.deb_pkg_name(pkg_type)}_#{@spec.arch}.deb" + FileUtils.cp("#{@pgpm_dir}/out/#{deb_fn}", "#{Dir.pwd}/#{deb_copy_fn}") + end + + def cleanup + puts "Cleaning up..." + + puts " Stopping destroying podman container: #{@container_name}" + system("podman container stop #{@container_name}") + system("podman container rm #{@container_name}") + + # Remove temporary files + # + # Make sure @pgpm_dir starts with "/tmp/" or we may accidentally + # delete something everything! You can never be sure! + if @pgpm_dir.start_with?("/tmp/") + puts " Removing temporary files in #{@pgpm_dir}" + FileUtils.rm_rf(@pgpm_dir) + else + puts "WARNING: will not remove temporary files, strange path: \"#{@pgpm_dir}\"" + end + end + + # Needed because SELinux requires :z suffix for mounted directories to + # be accessible -- otherwise we get "Permission denied" when cd into a + # mounted dir inside the container. + def selinux_enabled? + # This returns true or false by itself + system("sestatus | grep 'SELinux status' | grep -o 'enabled'") + end + end + end +end diff --git a/lib/pgpm/deb/pbuilder_install_script.sh b/lib/pgpm/deb/pbuilder_install_script.sh new file mode 100644 index 0000000..484850f --- /dev/null +++ b/lib/pgpm/deb/pbuilder_install_script.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +apt update +DEBIAN_FRONTEND=noninteractive apt -y install build-essential curl lsb-release ca-certificates + +### PostgreSQL installation +# +install -d /usr/share/postgresql-common/pgdg +curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc + +# Create the repository configuration file: +sh -c 'echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' + +# Update the package lists: +apt update + +# Install the latest version of PostgreSQL: +# If you want a specific version, use 'postgresql-16' or similar instead of 'postgresql' +apt -y install postgresql-17 postgresql-server-dev-17 postgresql-common +# +### END OF PostgreSQL installation + diff --git a/lib/pgpm/deb/scripts/install_default_control.sh b/lib/pgpm/deb/scripts/install_default_control.sh new file mode 100644 index 0000000..4b66bbc --- /dev/null +++ b/lib/pgpm/deb/scripts/install_default_control.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +ext_dir="$PGPM_INSTALL_ROOT/$(pg_config --sharedir)/extension" +control_fn="$ext_dir/$PGPM_EXTENSION_NAME.control" + +echo "Creating extension dir: $ext_dir" +mkdir -p "$ext_dir" + +echo "Creating control file: $control_fn" +cp "$PGPM_BUILDROOT/$PGPM_EXTENSION_NAME.control" "$ext_dir/" +echo >> "$control_fn" +echo "default_version = '$PGPM_EXTENSION_VERSION'" >> "$control_fn" diff --git a/lib/pgpm/deb/scripts/pg_config.sh b/lib/pgpm/deb/scripts/pg_config.sh new file mode 100644 index 0000000..77fbe26 --- /dev/null +++ b/lib/pgpm/deb/scripts/pg_config.sh @@ -0,0 +1,21 @@ +#! /usr/bin/env bash + +# Ensure PG_CONFIG is set +if [[ -z "$PG_CONFIG" ]]; then + echo "Error: PG_CONFIG is not set." + exit 1 +fi + +# Wrapper function for pg_config +pg_config_wrapper() { + "$PG_CONFIG" "$@" | while read -r line; do + if [[ -n "$PGPM_REDIRECT_TO_BUILDROOT" && -f "$line" || -d "$line" ]]; then + echo "$PGPM_INSTALL_ROOT$line" + else + echo "$line" + fi + done +} + +# Call the wrapper function with the arguments passed to the script +pg_config_wrapper "$@" diff --git a/lib/pgpm/deb/scripts/prepare_artifacts.sh b/lib/pgpm/deb/scripts/prepare_artifacts.sh new file mode 100644 index 0000000..f0e87f0 --- /dev/null +++ b/lib/pgpm/deb/scripts/prepare_artifacts.sh @@ -0,0 +1,115 @@ +#! /usr/bin/env bash + +#set -xe + +new_extension_so= + +PG_CONFIG="${PG_CONFIG:-"pg_config"}" + +install_root=$PGPM_INSTALL_ROOT + +for file in $(find $PGPM_BUILDROOT -name '*.so'); do + filename=$(basename "$file") + if [[ "$filename" == "${PGPM_EXTENSION_NAME}.so" ]]; then + extension_so=$filename + dir=$(dirname "$file") + extension_dirname=${dir#"$PGPM_BUILDROOT"} + new_extension_so=$PGPM_EXTENSION_NAME--$PGPM_EXTENSION_VERSION.so + break + fi +done + +extdir=$install_root$($PG_CONFIG --sharedir)/extension + +# control files +default_control=$extdir/$PGPM_EXTENSION_NAME.control +versioned_control=$extdir/$PGPM_EXTENSION_NAME--$PGPM_EXTENSION_VERSION.control +controls=("$default_control" "$versioned_control") + +function rename_so() { + mv "$install_root$extension_dirname/$extension_so" \ + "$install_root$extension_dirname/$new_extension_so" +} + +function change_name_in_controls() { + echo "CHANGING EXTENSION NAME IN CONTROL FILES" + echo "----------------------------------------" + for control in "${controls[@]}"; do + if [[ -f "$control" ]]; then + echo "$control" + # extension.so + sed -i "s|${extension_so}'|${new_extension_so}'|g" "$control" + # extension + sed -i "s|${extension_so%".so"}'|${new_extension_so%".so"}'|g" "$control" + fi + done +} + +function rename_sql_files() { + echo "RENAMING EXTENSION SQL FILES" + echo "----------------------------" + for sql_file in $(find $install_root -name '*.sql' -type f); do + echo "$sql_file" + # extension.so + sed -i "s|/${extension_so}'|/${new_extension_so}'|g" "$sql_file" + # extension + sed -i "s|/${extension_so%".so"}'|/${new_extension_so}'|g" "$sql_file" + done +} + +function rename_bitcode() { + echo "RENAMING BITCODE" + echo "----------------" + + pkglibdir=$install_root$($PG_CONFIG --pkglibdir) + bitcode_extension=$pkglibdir/bitcode/${extension_so%".so"} + bitcode_index=$pkglibdir/bitcode/${extension_so%".so"}.index.bc + + if [[ -d "${bitcode_extension}" ]]; then + echo "$bitcode_extension" + mv "$bitcode_extension" "$pkglibdir/bitcode/${new_extension_so%".so"}" + fi + + if [[ -f "${bitcode_index}" ]]; then + echo "$bitcode_index" + mv "${bitcode_index}" "$pkglibdir/bitcode/${new_extension_so%".so"}.index.bc" + fi +} + +function rename_includes() { + includedir=$install_root$($PG_CONFIG --includedir-server) + echo "RENAMING INCLUDES" + echo "-----------------" + if [[ -d "${includedir}/extension/$PGPM_EXTENSION_NAME" ]]; then + echo "$includedir" + versioned_dir=${includedir}/extension/$PGPM_EXTENSION_NAME--$PGPM_EXTENSION_VERSION + mkdir -p "$versioned_dir" + mv "${includedir}/extension/$PGPM_EXTENSION_NAME" "$versioned_dir" + fi +} + +# Make sure we don't build a default control as it belongs +# to another package +function handle_default_control() { + if [[ -f "$default_control" ]]; then + if [[ -f "$versioned_control" ]]; then + # We don't need default control if versioned is present + rm -f "$default_control" + else + # Default becomes versioned + mv "$default_control" "$versioned_control" + # Don't need default_version + sed -i '/default_version/d' "$versioned_control" + fi + fi +} + +if [[ -n "$new_extension_so" ]]; then + rename_so + change_name_in_controls + rename_sql_files + rename_bitcode + rename_incluides +fi + +handle_default_control diff --git a/lib/pgpm/deb/spec.rb b/lib/pgpm/deb/spec.rb new file mode 100644 index 0000000..c3356f6 --- /dev/null +++ b/lib/pgpm/deb/spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require "digest" +require "open-uri" +require "erb" + +module Pgpm + module Deb + class Spec + attr_reader :package, :release, :postgres_version, :postgres_distribution + + def initialize(package) + @postgres_distribution = Pgpm::Postgres::Distribution.in_scope + @package = package + @package.postgres_major_version = @postgres_distribution.major_version + @release = 1 + end + + def sources + @package.sources + end + + def generate(template_name, pkg_type = :versioned) + fn = "#{__dir__}/templates/#{template_name}.erb" + raise "No such template: #{fn}" unless File.exist?(fn) + + erb = ERB.new(File.read(fn)) + + # Uses pkg_type parameter (which is in scope) to generate + # debian/* files for versionless and main packages. + erb.result(binding) + end + + def source_version + v = @package.version.to_s + v.match(/\Z\d+\.\d+\Z/) ? "#{v}.0" : v + end + + def deb_pkg_name(type = :versioned) + if type == :versioned + "#{@package.name.gsub("_", "-")}+#{source_version}-pg#{@package.postgres_major_version}" + else + "#{@package.name.gsub("_", "-")}-pg#{@package.postgres_major_version}" + end + end + + def arch + # https://memgraph.com/blog/ship-it-on-arm64-or-is-it-aarch64 + # Debian suffixes are "amd64" and "arm64". Here we translate: + case Pgpm::Arch.in_scope.name + when "amd64", "x86_64" + "amd64" + when "aarch64", "arm64" + "arm64" + end + end + + def cmds_if_not_empty(cmds, else_echo) + return "\techo \"#{else_echo}\"" if cmds.nil? || cmds.empty? + + cmds.map!(&:to_s) + cmds.map! { |c| c.gsub("$", "$$") } + cmds.join("\t") + end + end + end +end diff --git a/lib/pgpm/deb/templates/changelog.erb b/lib/pgpm/deb/templates/changelog.erb new file mode 100644 index 0000000..a09bdba --- /dev/null +++ b/lib/pgpm/deb/templates/changelog.erb @@ -0,0 +1,5 @@ +<%= deb_pkg_name(pkg_type) %> (0-1) stable; urgency=medium + + * Version <%= pkg_type == :versioned ? 1 : source_version %> package release. + + -- PGPM Debian maintainer <%= Time.now.strftime('%a, %d %b %Y %H:%M:%S %z')%> diff --git a/lib/pgpm/deb/templates/control.erb b/lib/pgpm/deb/templates/control.erb new file mode 100644 index 0000000..54f6fff --- /dev/null +++ b/lib/pgpm/deb/templates/control.erb @@ -0,0 +1,14 @@ +Source: <%= deb_pkg_name(pkg_type) %> +Description: <%= self.package.description %> +Section: libs +Priority: optional +Maintainer: PGPM Debian maintainer +Rules-Requires-Root: no +Build-Depends: debhelper-compat (= 13), <%= self.package.build_dependencies.join(", ") %> +Standards-Version: 4.6.2 + +Package: <%= deb_pkg_name(pkg_type) %> +Depends: <%= pkg_type == :versioned ? self.package.dependencies.join(", ") : deb_pkg_name(:versioned) %> +Section: libdevel +Architecture: <%= arch %> +Description: <%= self.package.description %> diff --git a/lib/pgpm/deb/templates/copyright.erb b/lib/pgpm/deb/templates/copyright.erb new file mode 100644 index 0000000..aa7b533 --- /dev/null +++ b/lib/pgpm/deb/templates/copyright.erb @@ -0,0 +1,9 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Source: +Upstream-Name: <%= self.package.name %>-<%= self.package.version.to_s %> +Upstream-Contact: + +Files: + * +Copyright: +<%= self.package.license %> diff --git a/lib/pgpm/deb/templates/files.erb b/lib/pgpm/deb/templates/files.erb new file mode 100644 index 0000000..af64618 --- /dev/null +++ b/lib/pgpm/deb/templates/files.erb @@ -0,0 +1 @@ +<%= deb_pkg_name(pkg_type) %>_source.buildinfo libs optional diff --git a/lib/pgpm/deb/templates/rules.erb b/lib/pgpm/deb/templates/rules.erb new file mode 100644 index 0000000..23c12f8 --- /dev/null +++ b/lib/pgpm/deb/templates/rules.erb @@ -0,0 +1,40 @@ +#!/usr/bin/make -f + +export DEB_BUILDDIR = $(CURDIR) +export PGPM_BUILDROOT = $(CURDIR) +export PG_CONFIG = $(shell /usr/bin/which pg_config) +export PGPM_EXTENSION_NAME = "<%= self.package.extension_name %>" +export PGPM_EXTENSION_VERSION = "<%= self.package.version %>" +export PGPM_INSTALL_ROOT = "$(CURDIR)/debian/<%= deb_pkg_name(pkg_type) %>" + +<% if pkg_type == :versioned %> +%: + dh $@ + +override_dh_auto_configure: + echo " --> configuring" + <%= cmds_if_not_empty self.package.configure_steps, '...nothing to configure' %> + +override_dh_auto_build: + echo " --> building" + <%= cmds_if_not_empty self.package.build_steps, '...nothing to build' %> + +override_dh_auto_install: + echo " --> installing" + dh_auto_install + <%= cmds_if_not_empty self.package.install_steps, '...no custom install steps' %> + chmod +x "$$DEB_BUILDDIR/prepare_artifacts.sh" + find $$PGPM_INSTALL_ROOT -type f | sort - | sed 's|^$$PGPM_INSTALL_ROOT||' > .pgpm_before | sort + ./prepare_artifacts.sh + +<% else %> +%: + dh $@ + +override_dh_auto_install: + dh_auto_install + echo " --> INSTALL" + chmod +x "$$DEB_BUILDDIR/install_default_control.sh" + ./install_default_control.sh + +<% end %> diff --git a/lib/pgpm/os/debian.rb b/lib/pgpm/os/debian.rb new file mode 100644 index 0000000..c559a17 --- /dev/null +++ b/lib/pgpm/os/debian.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "rbconfig" + +module Pgpm + module OS + class Debian < Pgpm::OS::Linux + def self.auto_detect + # TODO: distinguish between flavors of Debian + Debian12.new + end + + def self.name + "debian" + end + + def mock_config; end + end + + class Debian12 < Pgpm::OS::Debian + def self.name + "debian-12" + end + + def self.builder + Pgpm::Debian::Builder + end + + def mock_config + "debian-12-#{Pgpm::Arch.in_scope.name}+pgdg" + end + end + end +end diff --git a/lib/pgpm/package/building.rb b/lib/pgpm/package/building.rb index 886ce85..e7eb06d 100644 --- a/lib/pgpm/package/building.rb +++ b/lib/pgpm/package/building.rb @@ -7,14 +7,6 @@ def configure_steps [] end - def build_steps - [] - end - - def install_steps - [] - end - def source_url_directory_name nil end diff --git a/lib/pgpm/package/dependencies.rb b/lib/pgpm/package/dependencies.rb index 7280ea4..8869156 100644 --- a/lib/pgpm/package/dependencies.rb +++ b/lib/pgpm/package/dependencies.rb @@ -5,14 +5,31 @@ module Pgpm class Package module Dependencies - def build_dependencies - return ["gcc"] if c_files_present? + attr_accessor :postgres_major_version - [] + def build_dependencies + case Pgpm::OS.in_scope.class.name + when "debian", "ubuntu" + deps = [ + "postgresql-#{postgres_major_version}", + "postgresql-server-dev-#{postgres_major_version}", + "postgresql-common" + ] + if native? + deps << "build-essential" + end + when "rocky+epel-9", "redhat", "fedora" + [] + end end def dependencies - [] + case Pgpm::OS.in_scope.class.name + when "debian", "ubuntu" + ["postgresql-#{postgres_major_version}"] + when "rocky+epel-9", "redhat", "fedora" + [] + end end def requires diff --git a/lib/pgpm/package/make.rb b/lib/pgpm/package/make.rb index dbe38c1..24c758d 100644 --- a/lib/pgpm/package/make.rb +++ b/lib/pgpm/package/make.rb @@ -4,15 +4,13 @@ module Pgpm class Package module Make def build_steps - return [Pgpm::Commands::Make.new("PG_CONFIG=$PG_CONFIG")] if makefile_present? - - super + [Pgpm::Commands::Make.new("PG_CONFIG=$PG_CONFIG")] if makefile_present? end def install_steps - return [Pgpm::Commands::Make.new("install", "DESTDIR=$PGPM_BUILDROOT", "PG_CONFIG=$PG_CONFIG")] if makefile_present? + return unless makefile_present? - super + [Pgpm::Commands::Make.new("install", "DESTDIR=$PGPM_INSTALL_ROOT", "PG_CONFIG=$PG_CONFIG")] end def makefile_present? diff --git a/lib/pgpm/package/packaging.rb b/lib/pgpm/package/packaging.rb index cabcdc8..c7fa0a9 100644 --- a/lib/pgpm/package/packaging.rb +++ b/lib/pgpm/package/packaging.rb @@ -6,6 +6,10 @@ module Packaging def to_rpm_spec(**opts) Pgpm::RPM::Spec.new(self, **opts) end + + def to_deb_spec(**opts) + Pgpm::Deb::Spec.new(self, **opts) + end end end end diff --git a/lib/pgpm/package/pgxn.rb b/lib/pgpm/package/pgxn.rb index b5a00b3..b7127f7 100644 --- a/lib/pgpm/package/pgxn.rb +++ b/lib/pgpm/package/pgxn.rb @@ -56,6 +56,20 @@ def license super end end + + def license_text + path = source.to_s + %w[license lisence unlicense unlisence copying].each do |fn| + [fn, fn.capitalize, fn.upcase].each do |fn2| + ["", ".txt", ".md"].each do |fn3| + if File.exist?("#{path}/#{fn2}#{fn3}") + return File.read("#{path}/#{fn2}#{fn3}") + end + end + end + end + nil + end end end end diff --git a/lib/pgpm/rpm/scripts/pg_config.sh b/lib/pgpm/rpm/scripts/pg_config.sh index 06da6ae..77fbe26 100755 --- a/lib/pgpm/rpm/scripts/pg_config.sh +++ b/lib/pgpm/rpm/scripts/pg_config.sh @@ -1,7 +1,5 @@ #! /usr/bin/env bash -#!/bin/bash - # Ensure PG_CONFIG is set if [[ -z "$PG_CONFIG" ]]; then echo "Error: PG_CONFIG is not set." @@ -12,7 +10,7 @@ fi pg_config_wrapper() { "$PG_CONFIG" "$@" | while read -r line; do if [[ -n "$PGPM_REDIRECT_TO_BUILDROOT" && -f "$line" || -d "$line" ]]; then - echo "$PGPM_BUILDROOT$line" + echo "$PGPM_INSTALL_ROOT$line" else echo "$line" fi @@ -20,4 +18,4 @@ pg_config_wrapper() { } # Call the wrapper function with the arguments passed to the script -pg_config_wrapper "$@" \ No newline at end of file +pg_config_wrapper "$@" diff --git a/lib/pgpm/rpm/scripts/prepare_artifacts.sh b/lib/pgpm/rpm/scripts/prepare_artifacts.sh index 7521c65..93bcff4 100755 --- a/lib/pgpm/rpm/scripts/prepare_artifacts.sh +++ b/lib/pgpm/rpm/scripts/prepare_artifacts.sh @@ -4,17 +4,17 @@ set -xe new_extension_so= -for file in $(find $PGPM_BUILDROOT -name '*.so'); do +for file in $(find $PGPM_INSTALL_ROOT -name '*.so'); do filename=$(basename "$file") if [[ "$filename" == "${PGPM_EXTENSION_NAME}.so" ]]; then extension_so=$filename dir=$(dirname "$file") - extension_dirname=${dir#"$PGPM_BUILDROOT"} + extension_dirname=${dir#"$PGPM_INSTALL_ROOT"} new_extension_so=$PGPM_EXTENSION_NAME--$PGPM_EXTENSION_VERSION.so fi done -extdir=$PGPM_BUILDROOT$($PG_CONFIG --sharedir)/extension +extdir=$PGPM_INSTALL_ROOT$($PG_CONFIG --sharedir)/extension # control files default_control=$extdir/$PGPM_EXTENSION_NAME.control @@ -24,7 +24,7 @@ controls=("$default_control" "$versioned_control") if [[ -n "$new_extension_so" ]]; then - mv "$PGPM_BUILDROOT$extension_dirname/$extension_so" "$PGPM_BUILDROOT$extension_dirname/$new_extension_so" + mv "$PGPM_INSTALL_ROOT$extension_dirname/$extension_so" "$PGPM_INSTALL_ROOT$extension_dirname/$new_extension_so" # Change the extension name in controls for control in "${controls[@]}"; do @@ -37,7 +37,7 @@ if [[ -n "$new_extension_so" ]]; then done # sql files - for sql_file in $(find $PGPM_BUILDROOT -name '*.sql' -type f); do + for sql_file in $(find $PGPM_INSTALL_ROOT -name '*.sql' -type f); do # extension.so sed -i "s|/${extension_so}'|/${new_extension_so}'|g" "$sql_file" # extension @@ -46,7 +46,7 @@ if [[ -n "$new_extension_so" ]]; then # bitcode - pkglibdir=$PGPM_BUILDROOT$($PG_CONFIG --pkglibdir) + pkglibdir=$PGPM_INSTALL_ROOT$($PG_CONFIG --pkglibdir) bitcode_extension=$pkglibdir/bitcode/${extension_so%".so"} bitcode_index=$pkglibdir/bitcode/${extension_so%".so"}.index.bc @@ -60,7 +60,7 @@ if [[ -n "$new_extension_so" ]]; then fi # includes - includedir=$PGPM_BUILDROOT$($PG_CONFIG --includedir-server) + includedir=$PGPM_INSTALL_ROOT$($PG_CONFIG --includedir-server) if [[ -d "${includedir}/extension/$PGPM_EXTENSION_NAME" ]]; then versioned_dir=${includedir}/extension/$PGPM_EXTENSION_NAME--$PGPM_EXTENSION_VERSION @@ -85,4 +85,4 @@ if [[ -f "$default_control" ]]; then # Don't need default_version sed -i '/default_version/d' "$versioned_control" fi -fi \ No newline at end of file +fi diff --git a/lib/pgpm/rpm/spec.rb b/lib/pgpm/rpm/spec.rb index 74bb3f3..fbc8aef 100644 --- a/lib/pgpm/rpm/spec.rb +++ b/lib/pgpm/rpm/spec.rb @@ -9,10 +9,13 @@ class Spec attr_reader :package, :release, :postgres_version, :postgres_distribution def initialize(package) + @postgres_distribution = Pgpm::Postgres::Distribution.in_scope @package = package @release = 1 - @postgres_distribution = Pgpm::Postgres::Distribution.in_scope + # Needed in order to return correct dependencies for the selected + # version of postgres and selected OS. + @package.postgres_major_version = @postgres_distribution.major_version end def versionless @@ -94,12 +97,12 @@ def to_s %build export PG_CONFIG=$(rpm -ql #{@postgres_distribution.pg_config_package} | grep 'pg_config$') - export PGPM_BUILDROOT=%{buildroot} + export PGPM_INSTALL_ROOT=%{buildroot} #{@package.build_steps.map(&:to_s).join("\n")} %install export PG_CONFIG=$(rpm -ql #{@postgres_distribution.pg_config_package} | grep 'pg_config$') - export PGPM_BUILDROOT=%{buildroot} + export PGPM_INSTALL_ROOT=%{buildroot} cp %{SOURCE#{sources.find_index { |src| src.name == "pg_config.sh" }}} ./pg_config.sh chmod +x ./pg_config.sh find %{buildroot} -type f | sort - | sed 's|^%{buildroot}||' > .pgpm_before | sort diff --git a/packages/omnigres/package.rb b/packages/omnigres/package.rb index ffa163b..c45575d 100644 --- a/packages/omnigres/package.rb +++ b/packages/omnigres/package.rb @@ -88,17 +88,17 @@ def build_steps def install_steps steps = [ - "mkdir -p $PGPM_BUILDROOT/$($PG_CONFIG --sharedir)/extension", - "mkdir -p $PGPM_BUILDROOT/$($PG_CONFIG --pkglibdir)", + "mkdir -p $PGPM_INSTALL_ROOT/$($PG_CONFIG --sharedir)/extension", + "mkdir -p $PGPM_INSTALL_ROOT/$($PG_CONFIG --pkglibdir)", # Package .so artifacts - "find build/packaged -name '#{name}*.so' -type f -exec cp {} $PGPM_BUILDROOT/$($PG_CONFIG --pkglibdir) \\;", + "find build/packaged -name '#{name}*.so' -type f -exec cp {} $PGPM_INSTALL_ROOT/$($PG_CONFIG --pkglibdir) \\;", # Package version-specific control file - "cp build/packaged/extension/#{name}--#{version}.control $PGPM_BUILDROOT/$($PG_CONFIG --sharedir)/extension", + "cp build/packaged/extension/#{name}--#{version}.control $PGPM_INSTALL_ROOT/$($PG_CONFIG --sharedir)/extension", # Package version-specific init file - "cp build/packaged/extension/#{name}--#{version}.sql $PGPM_BUILDROOT/$($PG_CONFIG --sharedir)/extension" + "cp build/packaged/extension/#{name}--#{version}.sql $PGPM_INSTALL_ROOT/$($PG_CONFIG --sharedir)/extension" ] if previous_version && !previous_version.broken? && !@no_migration - steps.push("cp build/packaged/extension/#{name}--#{previous_version.version}--#{version}.sql $PGPM_BUILDROOT/$($PG_CONFIG --sharedir)/extension") + steps.push("cp build/packaged/extension/#{name}--#{previous_version.version}--#{version}.sql $PGPM_INSTALL_ROOT/$($PG_CONFIG --sharedir)/extension") end steps end diff --git a/packages/pgsodium.rb b/packages/pgsodium.rb index bfac4e5..b2af14b 100644 --- a/packages/pgsodium.rb +++ b/packages/pgsodium.rb @@ -4,11 +4,23 @@ class Pgsodium < Pgpm::Package github "michelp/pgsodium" def build_dependencies - super + ["libsodium-devel >= 1.0.18"] + deps = case Pgpm::OS.in_scope.class.name + when "debian", "ubuntu" + ["libsodium-dev (>= 1.0.18)"] + when "rocky+epel-9", "redhat", "fedora" + ["libsodium-devel >= 1.0.18"] + end + super + deps end def dependencies - super + ["libsodium >= 1.0.18"] + deps = case Pgpm::OS.in_scope.class.name + when "debian", "ubuntu" + ["libsodium (>= 1.0.18)"] + when "rocky+epel-9", "redhat", "fedora" + ["libsodium >= 1.0.18"] + end + super + deps end def broken? diff --git a/packages/timescale/timescaledb.rb b/packages/timescale/timescaledb.rb index 1efb38f..59f0d55 100644 --- a/packages/timescale/timescaledb.rb +++ b/packages/timescale/timescaledb.rb @@ -11,37 +11,65 @@ def self.package_versions super.select { |v| v.to_s =~ /^(\d+\.\d+\.\d+)$/ } end - def summary - "TimescaleDB is an open-source database designed to make SQL scalable for time-series data. It is engineered up from PostgreSQL and packaged as a PostgreSQL extension, providing automatic partitioning across time and space (partitioning key), as well as full SQL support." + def description + "An open-source time-series SQL database optimized for fast ingest and " \ + "complex queries" end - def description - "An open-source time-series SQL database optimized for fast ingest and complex queries" + def summary + "TimescaleDB is an open-source database designed to make SQL " \ + "scalable for time-series data. It is engineered up from PostgreSQL " \ + "and packaged as a PostgreSQL extension, providing automatic " \ + "partitioning across time and space (partitioning key), as well as " \ + "full SQL support." end - def license - "Timescale License" + def dependencies + deps = case Pgpm::OS.in_scope.class.name + when "rocky+epel-9", "redhat", "fedora" + ["openssl"] + end + super + deps end - def build_steps - [ - "./bootstrap -DPG_CONFIG=$PG_CONFIG #{bootstrap_flags.map { |f| "-D#{f}" }.join(" ")}", - "cmake --build build --parallel" - ] + def build_dependencies + deps = case Pgpm::OS.in_scope.class.name + when "debian", "ubuntu" + %w[libssl-dev cmake] + when "rocky+epel-9", "redhat", "fedora" + %w[openssl-devel cmake] + end + super + deps end - def install_steps - [ - "DESTDIR=$PGPM_BUILDROOT cmake --build build --target install" - ] + def configure_steps + case Pgpm::OS.in_scope.class.name + when "debian", "ubuntu" + ["dh_auto_configure -- -DCMAKE_BUILD_TYPE=\"Release\""] + else + super + end end - def build_dependencies - super + %w[openssl-devel cmake] + def build_steps + case Pgpm::OS.in_scope.class.name + when "debian", "ubuntu" + ["dh_auto_build"] + when "rocky+epel-9", "redhat", "fedora" + [ + "./bootstrap -DPG_CONFIG=$PG_CONFIG #{bootstrap_flags.map { |f| "-D#{f}" }.join(" ")}", + "cmake --build build --parallel" + ] + end end - def dependencies - super + %w[openssl] + def install_steps + case Pgpm::OS.in_scope.class.name + when "rocky+epel-9", "redhat", "fedora" + ["DESTDIR=$PGPM_INSTALL_ROOT cmake --build build --target install"] + else + super + end end protected