diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..12ea855 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,27 @@ +name: Ruby + +on: + push: + branches: + - master + + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + name: Ruby ${{ matrix.ruby }} + strategy: + matrix: + ruby: + - '3.3.5' + + steps: + - uses: actions/checkout@v4 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + - name: Run the default task + run: bundle exec rake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3fe11ee --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +/.bundle/ +/.yardoc +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ +.idea +Gemfile.lock \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..62ade53 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,11 @@ +AllCops: + TargetRubyVersion: 3.3 + +Style/StringLiterals: + EnforcedStyle: double_quotes + +Style/StringLiteralsInInterpolation: + EnforcedStyle: double_quotes + +Style/FormatStringToken: + EnforcedStyle: template \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..78d409e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +## [Unreleased] + +## [0.1.0] - 2024-10-04 + +- Initial release diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..cfdec65 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +FROM fedora:41 AS pgpm-rpm + +RUN dnf -y install rpmlint ruby ruby-devel mock git gcc zlib-devel +VOLUME /pgpm +WORKDIR /pgpm \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..7299dc2 --- /dev/null +++ b/Gemfile @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +# Specify your gem's dependencies in pgpm.gemspec +gemspec + +gem "rake", "~> 13.0" + +gem "minitest", "~> 5.16" + +gem "rubocop", "~> 1.21" + +gem "debug", "~> 1.9" + +gem "rbs", "~> 3.6" diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..97f90f7 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2024 Yurii Rashkovskii + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0b0fe46 --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# Pgpm + +TODO: Delete this and the text below, and describe your gem + +Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/pgpm`. To experiment with that code, run `bin/console` for an interactive prompt. + +## Installation + +TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org. + +Install the gem and add to the application's Gemfile by executing: + +```bash +bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG +``` + +If bundler is not being used to manage dependencies, install the gem by executing: + +```bash +gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG +``` + +## Usage + +TODO: Write usage instructions here + +## Development + +After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. + +To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org). + +## Contributing + +Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/pgpm. + +## License + +The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..2bf771f --- /dev/null +++ b/Rakefile @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require "bundler/gem_tasks" +require "minitest/test_task" + +Minitest::TestTask.create + +require "rubocop/rake_task" + +RuboCop::RakeTask.new + +task default: %i[test rubocop] diff --git a/bin/console b/bin/console new file mode 100755 index 0000000..1225868 --- /dev/null +++ b/bin/console @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "bundler/setup" +require "pgpm" + +# You can add fixtures and/or initialization code here to make experimenting +# with your gem easier. You can also use a different console, if you like. + +require "irb" +IRB.start(__FILE__) diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000..dce67d8 --- /dev/null +++ b/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here diff --git a/exe/pgpm b/exe/pgpm new file mode 100755 index 0000000..d61b3a3 --- /dev/null +++ b/exe/pgpm @@ -0,0 +1,87 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "bundler/setup" +require "pgpm" +require "dry/cli" +require "parallel" + +module Pgpm + module CLI + module Commands + extend Dry::CLI::Registry + + class Version < Dry::CLI::Command + desc "Print version" + + def call(*) + puts Pgpm::VERSION + end + end + + class Build < Dry::CLI::Command + desc "Build packages" + + option :os, type: :string, default: Pgpm::OS.auto_detect.name, desc: "OS name" + argument :packages, type: :array, required: true, desc: "Package names" + + def call(packages:, os:, args: nil) + # os_class = Pgpm::OS::Base.all_subclasses.find { |s| s.name == os } + # unless os_class + # puts "Unsupported OS distribution `#{os}`" + # exit(1) + # end + # return if os_class.builder + + # puts "There is no build support for OS distribution `#{os}`" + # exit(1) + pkgs = packages.map do |package| + name, version = package.split("@") + version ||= :latest + p = Pgpm::Package[name] + if p.nil? + puts "Package #{name} not found" + exit(1) + end + pkg = p[version] + if pkg.nil? + puts "Package #{name} with version #{version} not found" + exit(1) + end + pkg + end + b = pkgs.reduce(nil) do |c, p| + spec = p.to_rpm_spec + builder = Pgpm::RPM::Builder.new(spec, os: Pgpm::OS::RockyEPEL9.new) + src_builder = builder.source_builder + c.nil? ? src_builder : c.and_then(src_builder) + end + + srpms = b.call + Pgpm::RPM::Builder.builder(srpms, os: Pgpm::OS::RockyEPEL9.new).call + end + end + + class Search < Dry::CLI::Command + argument :query, type: :string, default: ".*", desc: "Search query" + + def call(query:, args: nil) + query_regexp = Regexp.new(query, "i") + Parallel.filter_map(Pgpm::Package) do |p| + matches = p.all_searchable_texts.any? do |t| + t =~ query_regexp + end + "#{p.package_name}: #{p.description}" if matches + end.each { |l| puts l } + end + end + + register "version", Version, aliases: ["v", "-v", "--version"] + register "build", Build + register "search", Search + end + end +end + +Pgpm.load_packages(File.join(Dir.pwd, "packages")) +Dry::CLI.new(Pgpm::CLI::Commands).call diff --git a/lib/pgpm.rb b/lib/pgpm.rb new file mode 100644 index 0000000..ff4cfe7 --- /dev/null +++ b/lib/pgpm.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "zeitwerk" +require "pathname" + +class CustomInflector < Zeitwerk::GemInflector + def camelize(basename, _abspath) + # Specify your custom logic here + # This tells Zeitwerk that 'rpm' should be 'RPM' and 'os' should be 'OS' + case basename + when "rpm" + "RPM" + when "os" + "OS" + when "pgxn" + "PGXN" + else + super + end + end +end + +loader = Zeitwerk::Loader.for_gem +loader.inflector = CustomInflector.new(__FILE__) +loader.enable_reloading +loader.setup + +define_method(:reload!) do + loader.reload + loader.eager_load +end + +define_method(:load_packages) do |path = nil| + path ||= Pathname(File.dirname(__FILE__)).join("..", "packages") + loader.push_dir(path) + reload! +end + +module Pgpm +end diff --git a/lib/pgpm/aspects/inheritance_tracker.rb b/lib/pgpm/aspects/inheritance_tracker.rb new file mode 100644 index 0000000..9431c43 --- /dev/null +++ b/lib/pgpm/aspects/inheritance_tracker.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Pgpm + module Aspects + module InheritanceTracker + module ClassMethods + def all_subclasses + subclasses + subclasses.flat_map(&:all_subclasses) + end + end + + def self.included(base_class) + base_class.extend(ClassMethods) + end + end + end +end diff --git a/lib/pgpm/cache.rb b/lib/pgpm/cache.rb new file mode 100644 index 0000000..c9fc299 --- /dev/null +++ b/lib/pgpm/cache.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "xdg" + +module Pgpm + class Cache + def self.directory + XDG.new.cache_home.join("postgres.pm") + end + end +end diff --git a/lib/pgpm/commands.rb b/lib/pgpm/commands.rb new file mode 100644 index 0000000..680684a --- /dev/null +++ b/lib/pgpm/commands.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require "lspace" + +module Pgpm + module Commands + class Abstract + def to_s + raise "abstract implementation" + end + end + + class Make < Abstract + def initialize(*args) + @args = args + end + + def to_s + command = "make" + command += " %{?_smp_mflags}" if Pgpm::OS.in_scope.is_a?(Pgpm::OS::RedHat) + command += " #{@args.join(" ")}" unless @args.empty? + command + end + end + end +end diff --git a/lib/pgpm/on_demand_file.rb b/lib/pgpm/on_demand_file.rb new file mode 100644 index 0000000..ddfffdf --- /dev/null +++ b/lib/pgpm/on_demand_file.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Pgpm + class OnDemandFile + attr_reader :name + + def initialize(name, proc) + @name = name + @proc = proc + end + + private def method_missing(name, *args) + @io ||= @proc.call + @io.send(name, *args) + end + end +end diff --git a/lib/pgpm/os.rb b/lib/pgpm/os.rb new file mode 100644 index 0000000..f140c10 --- /dev/null +++ b/lib/pgpm/os.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Pgpm + module OS + class Base + attr_reader :arch + + include Pgpm::Aspects::InheritanceTracker + + def self.name + "unknown" + end + + def name + self.class.name + end + + def self.builder + nil + end + + def with_scope(&block) + LSpace.with(pgpm_target_operating_system: self) do + block.yield + end + end + end + + def self.auto_detect + return unless RUBY_PLATFORM =~ /linux$/ + + Pgpm::OS::Linux.auto_detect + end + + def self.in_scope + LSpace[:pgpm_target_operating_system] + end + end +end diff --git a/lib/pgpm/os/linux.rb b/lib/pgpm/os/linux.rb new file mode 100644 index 0000000..a821227 --- /dev/null +++ b/lib/pgpm/os/linux.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Pgpm + module OS + class Linux < Pgpm::OS::Unix + def self.auto_detect + return unless File.exist?("/etc/redhat-release") + + RedHat.auto_detect + end + + def self.name + "linux" + end + end + end +end diff --git a/lib/pgpm/os/red_hat.rb b/lib/pgpm/os/red_hat.rb new file mode 100644 index 0000000..8c320b8 --- /dev/null +++ b/lib/pgpm/os/red_hat.rb @@ -0,0 +1,398 @@ +# frozen_string_literal: true + +require "rbconfig" + +module Pgpm + module OS + class RedHat < Pgpm::OS::Linux + def self.auto_detect + new # TODO: distinguish between flavors of RedHat + end + + def self.name + "redhat" + end + end + + class RockyEPEL9 < Pgpm::OS::RedHat + def self.name + "rocky+epel-9" + end + + def self.builder + Pgpm::RPM::Builder + end + + def initialize(arch: nil) + @arch = arch || RbConfig::CONFIG["host_cpu"] + end + + def mock_config(_root = "rocky+epel-9-#{@arch}") + <<~EOF + include('rocky+epel-9-#{@arch}.cfg') + config_opts['root'] = 'rocky+epel+pgdg-9-aarch64' + + config_opts['description'] = 'Rocky Linux EPEL 9' + config_opts['dnf.conf'] += """ + + ######################################################### + # PGDG Red Hat Enterprise Linux / Rocky repositories # + ######################################################### + + # PGDG Red Hat Enterprise Linux / Rocky stable common repository for all PostgreSQL versions + + [pgdg-common] + name=PostgreSQL common RPMs for RHEL / Rocky / AlmaLinux $releasever - $basearch + baseurl=https://download.postgresql.org/pub/repos/yum/common/redhat/rhel-$releasever-$basearch + enabled=1 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + # Red Hat recently breaks compatibility between 9.n and 9.n+1. PGDG repo is + # affected with the LLVM repo. This is a band aid repo for the llvmjit users + # whose installations cannot be updated. + + [pgdg-rocky9-sysupdates] + name=PostgreSQL Supplementary ucommon RPMs for RHEL / Rocky / AlmaLinux $releasever - $basearch + baseurl=https://download.postgresql.org/pub/repos/yum/common/pgdg-rocky9-sysupdates/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + # We provide extra package to support some RPMs in the PostgreSQL RPM repo, like + # consul, haproxy, etc. + + [pgdg-rhel9-extras] + name=Extra packages to support some RPMs in the PostgreSQL RPM repo for RHEL / Rocky / AlmaLinux $releasever - $basearch + baseurl=https://download.postgresql.org/pub/repos/yum/common/pgdg-rhel$releasever-extras/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + # PGDG Red Hat Enterprise Linux / Rocky stable repositories: + + [pgdg17] + name=PostgreSQL 17 for RHEL / Rocky / AlmaLinux $releasever - $basearch + baseurl=https://download.postgresql.org/pub/repos/yum/17/redhat/rhel-$releasever-$basearch + enabled=1 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg16] + name=PostgreSQL 16 for RHEL / Rocky / AlmaLinux $releasever - $basearch + baseurl=https://download.postgresql.org/pub/repos/yum/16/redhat/rhel-$releasever-$basearch + enabled=1 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg15] + name=PostgreSQL 15 for RHEL / Rocky / AlmaLinux $releasever - $basearch + baseurl=https://download.postgresql.org/pub/repos/yum/15/redhat/rhel-$releasever-$basearch + enabled=1 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg14] + name=PostgreSQL 14 for RHEL / Rocky / AlmaLinux $releasever - $basearch + baseurl=https://download.postgresql.org/pub/repos/yum/14/redhat/rhel-$releasever-$basearch + enabled=1 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg13] + name=PostgreSQL 13 for RHEL / Rocky / AlmaLinux $releasever - $basearch + baseurl=https://download.postgresql.org/pub/repos/yum/13/redhat/rhel-$releasever-$basearch + enabled=1 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg12] + name=PostgreSQL 12 for RHEL / Rocky / AlmaLinux $releasever - $basearch + baseurl=https://download.postgresql.org/pub/repos/yum/12/redhat/rhel-$releasever-$basearch + enabled=1 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + # PGDG RHEL / Rocky / AlmaLinux Updates Testing common repositories. + + [pgdg-common-testing] + name=PostgreSQL common testing RPMs for RHEL / Rocky / AlmaLinux $releasever - $basearch + baseurl=https://download.postgresql.org/pub/repos/yum/testing/common/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + # PGDG RHEL / Rocky / AlmaLinux Updates Testing repositories. (These packages should not be used in production) + # Available for 12 and above. + + [pgdg17-updates-testing] + name=PostgreSQL 17 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Updates testing + baseurl=https://download.postgresql.org/pub/repos/yum/testing/17/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=0 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg16-updates-testing] + name=PostgreSQL 16 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Updates testing + baseurl=https://download.postgresql.org/pub/repos/yum/testing/16/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg15-updates-testing] + name=PostgreSQL 15 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Updates testing + baseurl=https://download.postgresql.org/pub/repos/yum/testing/15/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg14-updates-testing] + name=PostgreSQL 14 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Updates testing + baseurl=https://download.postgresql.org/pub/repos/yum/testing/14/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg13-updates-testing] + name=PostgreSQL 13 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Updates testing + baseurl=https://download.postgresql.org/pub/repos/yum/testing/13/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg12-updates-testing] + name=PostgreSQL 12 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Updates testing + baseurl=https://download.postgresql.org/pub/repos/yum/testing/12/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + # PGDG Red Hat Enterprise Linux / Rocky SRPM testing common repository + + [pgdg-source-common] + name=PostgreSQL 12 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Source + baseurl=https://download.postgresql.org/pub/repos/yum/srpms/common/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + # PGDG RHEL / Rocky / AlmaLinux testing common SRPM repository for all PostgreSQL versions + + [pgdg-common-srpm-testing] + name=PostgreSQL common testing SRPMs for RHEL / Rocky / AlmaLinux $releasever - $basearch + baseurl=https://download.postgresql.org/pub/repos/yum/srpms/testing/common/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + # PGDG Source RPMs (SRPM), and their testing repositories: + + [pgdg17-source-updates-testing] + name=PostgreSQL 17 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Source updates testing + baseurl=https://download.postgresql.org/pub/repos/yum/srpms/testing/17/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=0 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg16-source] + name=PostgreSQL 16 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Source + baseurl=https://download.postgresql.org/pub/repos/yum/srpms/16/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg16-source-updates-testing] + name=PostgreSQL 16 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Source updates testing + baseurl=https://download.postgresql.org/pub/repos/yum/srpms/testing/16/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg15-source] + name=PostgreSQL 15 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Source + baseurl=https://download.postgresql.org/pub/repos/yum/srpms/15/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg15-source-updates-testing] + name=PostgreSQL 15 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Source updates testing + baseurl=https://download.postgresql.org/pub/repos/yum/srpms/testing/15/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg14-source] + name=PostgreSQL 14 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Source + baseurl=https://download.postgresql.org/pub/repos/yum/srpms/14/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg14-source-updates-testing] + name=PostgreSQL 14 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Source updates testing + baseurl=https://download.postgresql.org/pub/repos/yum/srpms/testing/14/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg13-source] + name=PostgreSQL 13 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Source + baseurl=https://download.postgresql.org/pub/repos/yum/srpms/13/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg13-source-updates-testing] + name=PostgreSQL 13 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Source updates testing + baseurl=https://download.postgresql.org/pub/repos/yum/srpms/testing/13/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg12-source] + name=PostgreSQL 12 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Source + baseurl=https://download.postgresql.org/pub/repos/yum/srpms/12/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg12-source-updates-testing] + name=PostgreSQL 12 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Source update testing + baseurl=https://download.postgresql.org/pub/repos/yum/srpms/testing/12/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + # Debuginfo/debugsource packages for stable repos + + [pgdg16-debuginfo] + name=PostgreSQL 16 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Debuginfo + baseurl=https://dnf-debuginfo.postgresql.org/debug/16/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg15-debuginfo] + name=PostgreSQL 15 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Debuginfo + baseurl=https://dnf-debuginfo.postgresql.org/debug/15/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg14-debuginfo] + name=PostgreSQL 14 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Debuginfo + baseurl=https://dnf-debuginfo.postgresql.org/debug/14/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg13-debuginfo] + name=PostgreSQL 13 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Debuginfo + baseurl=https://dnf-debuginfo.postgresql.org/debug/13/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg12-debuginfo] + name=PostgreSQL 12 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Debuginfo + baseurl=https://dnf-debuginfo.postgresql.org/debug/12/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + # Debuginfo/debugsource packages for testing repos + # Available for 12 and above. + + [pgdg17-updates-testing-debuginfo] + name=PostgreSQL 17 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Debuginfo + baseurl=https://dnf-debuginfo.postgresql.org/testing/debug/17/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=0 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg16-updates-testing-debuginfo] + name=PostgreSQL 16 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Debuginfo + baseurl=https://dnf-debuginfo.postgresql.org/testing/debug/16/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=0 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg15-updates-testing-debuginfo] + name=PostgreSQL 15 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Debuginfo + baseurl=https://dnf-debuginfo.postgresql.org/testing/debug/15/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg14-updates-testing-debuginfo] + name=PostgreSQL 14 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Debuginfo + baseurl=https://dnf-debuginfo.postgresql.org/testing/debug/14/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg13-updates-testing-debuginfo] + name=PostgreSQL 13 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Debuginfo + baseurl=https://dnf-debuginfo.postgresql.org/testing/debug/13/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + + [pgdg12-updates-testing-debuginfo] + name=PostgreSQL 12 for RHEL / Rocky / AlmaLinux $releasever - $basearch - Debuginfo + baseurl=https://dnf-debuginfo.postgresql.org/testing/debug/12/redhat/rhel-$releasever-$basearch + enabled=0 + gpgcheck=1 + gpgkey=https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-AARCH64-RHEL + repo_gpgcheck = 1 + """ + + #config_opts['dnf_install_command'] += ' https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm postgresql16 postgresql16-devel' + config_opts['chroot_setup_cmd'] = 'install @development' + # https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-aarch64/pgdg-redhat-repo-latest.noarch.rpm' + + EOF + end + end + end +end diff --git a/lib/pgpm/os/unix.rb b/lib/pgpm/os/unix.rb new file mode 100644 index 0000000..d92a01e --- /dev/null +++ b/lib/pgpm/os/unix.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Pgpm + module OS + class Unix < Pgpm::OS::Base + def self.name + "unix" + end + end + end +end diff --git a/lib/pgpm/package.rb b/lib/pgpm/package.rb new file mode 100644 index 0000000..205cedb --- /dev/null +++ b/lib/pgpm/package.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require "dry/inflector" + +module Pgpm + class Package + include Pgpm::Aspects::InheritanceTracker + include AbstractPackage + include Sources + include Naming + include Metadata + include Dependencies + include Git + include GitHub + include PGXN + include Versioning + include Subscripting + include Enumerating + include Building + include Initialization + include Packaging + + def inspect + "#<#{self.class}:#{self.class.package_name} #{version}>" + end + + abstract_package + end +end diff --git a/lib/pgpm/package/abstract_package.rb b/lib/pgpm/package/abstract_package.rb new file mode 100644 index 0000000..9e55d71 --- /dev/null +++ b/lib/pgpm/package/abstract_package.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Pgpm + class Package + module AbstractPackage + module ClassMethods + def abstract_package? + @is_abstract_package || false + end + + protected + + def abstract_package + @is_abstract_package = true + end + end + + def self.included(base_class) + base_class.extend(ClassMethods) + end + end + end +end diff --git a/lib/pgpm/package/building.rb b/lib/pgpm/package/building.rb new file mode 100644 index 0000000..3e04638 --- /dev/null +++ b/lib/pgpm/package/building.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Pgpm + class Package + module Building + def configure_steps + [] + end + + def build_steps + return [Pgpm::Commands::Make.new] if has_makefile? + + [] + end + + def install_steps + return [Pgpm::Commands::Make.new("install", "DESTDIR=$PGPM_BUILDROOT")] if has_makefile? + + [] + end + + def has_makefile? + !Dir.glob(%w[Makefile GNUmakefile makefile], base: source.to_s).empty? + end + + def source_url_directory_name + nil + end + end + end +end diff --git a/lib/pgpm/package/dependencies.rb b/lib/pgpm/package/dependencies.rb new file mode 100644 index 0000000..e0268c9 --- /dev/null +++ b/lib/pgpm/package/dependencies.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Pgpm + class Package + module Dependencies + def build_dependencies + return ["gcc"] if has_c_files? + + [] + end + + def dependencies + [] + end + + def has_c_files? + Dir.glob("*.c", base: source).any? + end + end + end +end diff --git a/lib/pgpm/package/enumerating.rb b/lib/pgpm/package/enumerating.rb new file mode 100644 index 0000000..7e864d0 --- /dev/null +++ b/lib/pgpm/package/enumerating.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Pgpm + class Package + module Enumerating + def self.included(base_class) + class << base_class + include Enumerable + def each(&block) + if self == Pgpm::Package + all_subclasses.each(&block) + else + package_versions.map { |v| new(v) }.each(&block) + end + end + end + end + end + end +end diff --git a/lib/pgpm/package/git.rb b/lib/pgpm/package/git.rb new file mode 100644 index 0000000..0f16cbe --- /dev/null +++ b/lib/pgpm/package/git.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require "git" + +module Pgpm + class Package + module Git + Config = Data.define(:url, :download_version_tags) + + module Methods + def package_versions + if !git_config.download_version_tags + super + else + @tags ||= + ::Git.ls_remote(git_config.url)["tags"].keys + .filter { |key| !key.match?(/.+\^{}$/) } + versions = @tags.map { |tag| tag.gsub(/^v/, "") } + @tag_versions = Hash[@tags.zip(versions)] + @version_tags = Hash[versions.zip(@tags)] + versions + end + end + end + + def self.included(base_class) + base_class.class_eval do + def version_git_tag + self.class.package_versions if self.class.instance_variable_get(:@version_tags).nil? + version_tags = self.class.instance_variable_get(:@version_tags) || {} + version_tags[version] + end + + def version_git_commit + nil + end + + def source + directory = Pgpm::Cache.directory.join(name, version) + tag = version_git_tag + commit = version_git_commit + directory = Pgpm::Cache.directory.join(name, commit) if commit + if File.directory?(directory) && File.directory?(directory.join(".git")) + directory + elsif File.directory?(directory) + raise "Unexpected non-git directory #{directory}" + else + if tag + ::Git.clone(self.class.git_config.url, directory, depth: 1, branch: version_git_tag) + elsif commit + g = ::Git.clone(self.class.git_config.url, directory) + g.checkout("checkout-#{commit}", new_branch: true, start_point: commit) + else + ::Git.clone(self.class.git_config.url, directory, depth: 1) + end + directory + end + end + end + + class << base_class + @git_config = nil + attr_reader :git_config + + def git(url, download_version_tags: true) + @git_config = Config.new(url:, download_version_tags:) + extend Methods + end + end + end + end + end +end diff --git a/lib/pgpm/package/git_hub.rb b/lib/pgpm/package/git_hub.rb new file mode 100644 index 0000000..accd8da --- /dev/null +++ b/lib/pgpm/package/git_hub.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require "open-uri" + +module Pgpm + class Package + module GitHub + Config = Data.define(:name, :download_version_tags) + + def sources + commit = version_git_tag || version_git_commit + [Pgpm::OnDemandFile.new("#{version}.tar.gz", lambda { + URI.open("https://github.com/#{self.class.github_config.name}/archive/#{commit}.tar.gz") + })] + super + end + + module ClassMethods + attr_reader :github_config + + def github(name, download_version_tags: true) + @github_config = Config.new(name:, download_version_tags:) + include Pgpm::Package::Git + git "https://github.com/#{@github_config.name}", download_version_tags: + end + end + + def self.included(base_class) + base_class.extend(ClassMethods) + end + end + end +end diff --git a/lib/pgpm/package/initialization.rb b/lib/pgpm/package/initialization.rb new file mode 100644 index 0000000..5a1c424 --- /dev/null +++ b/lib/pgpm/package/initialization.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Pgpm + class Package + module Initialization + def initialize(version) + @version = version + end + end + end +end diff --git a/lib/pgpm/package/metadata.rb b/lib/pgpm/package/metadata.rb new file mode 100644 index 0000000..6f46e3b --- /dev/null +++ b/lib/pgpm/package/metadata.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Pgpm + class Package + module Metadata + def summary + self.class.summary + end + + def description + self.class.description + end + + def all_searchable_texts + self.class.all_searchable_texts + end + + def license + self.class.license + end + + module ClassMethods + def summary + "TODO: Summary" + end + + def description + "TODO: Description" + end + + def license + "TODO: License" + end + + def all_searchable_texts + [package_name, summary, description] + end + end + + def self.included(base_class) + base_class.extend(ClassMethods) + end + end + end +end diff --git a/lib/pgpm/package/naming.rb b/lib/pgpm/package/naming.rb new file mode 100644 index 0000000..603fc82 --- /dev/null +++ b/lib/pgpm/package/naming.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Pgpm + class Package + module Naming + module ClassMethods + def package_name + class_name = to_s.split("::").last + @name || Dry::Inflector.new.underscore(class_name) + end + + protected + + def name(name) + @name = name + end + end + + def self.included(base_class) + base_class.extend(ClassMethods) + end + + def name + self.class.package_name + end + + def extension_name + name + end + end + end +end diff --git a/lib/pgpm/package/packaging.rb b/lib/pgpm/package/packaging.rb new file mode 100644 index 0000000..cabcdc8 --- /dev/null +++ b/lib/pgpm/package/packaging.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Pgpm + class Package + module Packaging + def to_rpm_spec(**opts) + Pgpm::RPM::Spec.new(self, **opts) + end + end + end +end diff --git a/lib/pgpm/package/pgxn.rb b/lib/pgpm/package/pgxn.rb new file mode 100644 index 0000000..f12d854 --- /dev/null +++ b/lib/pgpm/package/pgxn.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require "oj" + +module Pgpm + class Package + module PGXN + def provides_pgxn_meta_json? + File.directory?(source) && File.file?(pgxn_meta_json_path) + end + + def pgxn_meta_json + @pgxn_meta_json ||= Oj.load(File.read(pgxn_meta_json_path)) + end + + def pgxn_meta_json_path + source.join("META.json") + end + + def extension_name + if provides_pgxn_meta_json? + pgxn_meta_json["name"] + else + super + end + end + + def summary + if provides_pgxn_meta_json? + pgxn_meta_json["abstract"] + else + super + end + end + + def description + if provides_pgxn_meta_json? + pgxn_meta_json["description"] + else + super + end + end + + def license + if provides_pgxn_meta_json? + lic = pgxn_meta_json["license"] + case lic + when Hash + lic.keys.join(" or ") + when Array + lic.join(" or ") + when String + lic + end + else + super + end + end + + module ClassMethods + def extension_name + self[:latest].extension_name + end + + def description + self[:latest].description + end + + def summary + self[:latest].summary + end + + def license + self[:latest].license + end + end + + def self.included(base_class) + base_class.extend(ClassMethods) + end + end + end +end diff --git a/lib/pgpm/package/sources.rb b/lib/pgpm/package/sources.rb new file mode 100644 index 0000000..bd2d006 --- /dev/null +++ b/lib/pgpm/package/sources.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Pgpm + class Package + module Sources + def sources + [] + end + end + end +end diff --git a/lib/pgpm/package/subscripting.rb b/lib/pgpm/package/subscripting.rb new file mode 100644 index 0000000..32a634e --- /dev/null +++ b/lib/pgpm/package/subscripting.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require "semver_dialects" + +module Pgpm + class Package + module Subscripting + module ClassMethods + def [](name) + if self == Pgpm::Package + all_subclasses.find { |klass| klass.package_name == name } + elsif name == :latest && package_versioning_scheme == :semver + return nil if package_versions.empty? + + version = package_versions.map { |ver| SemverDialects.parse_version("cargo", ver) }.last + new(version.to_s) + elsif name == :latest + null + elsif package_versions.include?(name) + new(name) + else + all_subclasses.find { |klass| klass.package_name == name } + end + end + end + + def self.included(base_class) + base_class.extend(ClassMethods) + end + end + end +end diff --git a/lib/pgpm/package/versioning.rb b/lib/pgpm/package/versioning.rb new file mode 100644 index 0000000..d86873b --- /dev/null +++ b/lib/pgpm/package/versioning.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Pgpm + class Package + module Versioning + module ClassMethods + def package_versions + warn("No versions defined for #{package_name}") + [] + end + + def package_versioning_scheme + @package_versioning_scheme ||= :semver + end + + def versioning_scheme(scheme) + @package_versioning_scheme = scheme + end + end + + def self.included(base_class) + base_class.extend(ClassMethods) + end + + attr_reader :version + end + end +end diff --git a/lib/pgpm/postgres.rb b/lib/pgpm/postgres.rb new file mode 100644 index 0000000..f9f2006 --- /dev/null +++ b/lib/pgpm/postgres.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require "semver_dialects" + +module Pgpm + module Postgres + class Distribution + def initialize(postgres_version) + @postgres_version = postgres_version + end + + def build_time_requirement_packages + raise NotImplementedError + end + end + + class RedhatBasedPGDG < Distribution + def build_time_requirement_packages + version = SemverDialects.parse_version("cargo", @postgres_version) + major = version.tokens.first + ["postgresql#{major}-#{version}-", "postgresql17-"] + end + end + end +end diff --git a/lib/pgpm/rpm.rb b/lib/pgpm/rpm.rb new file mode 100644 index 0000000..3fd93b7 --- /dev/null +++ b/lib/pgpm/rpm.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require "digest" +require "open-uri" + +module Pgpm + module RPM + end +end diff --git a/lib/pgpm/rpm/builder.rb b/lib/pgpm/rpm/builder.rb new file mode 100644 index 0000000..b1bbb59 --- /dev/null +++ b/lib/pgpm/rpm/builder.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module Pgpm + module RPM + class Builder + def initialize(spec, os: nil) + @spec = spec + @os = os || Pgpm::OS.auto_detect + end + + def source_builder(target_directory = nil) + target_directory ||= "." + @os.with_scope do + dir = Dir.mktmpdir("pgpm") + File.open(Pathname(dir).join("#{@spec.package.name}.spec"), "w") do |specfile| + specfile.write(@spec.to_s) + specfile.close + sources = File.join(dir, "sources") + FileUtils.mkdir_p(sources) + @spec.package.sources.map do |src| + print "Downloading #{src.name}..." + srcfile = File.join(sources, src.name) + File.write(srcfile, src.read) + puts " done." + end + cfg = Pgpm::RPM::Mock::Config.new(@os.mock_config, path: target_directory) + Pgpm::RPM::Mock::Operation.buildsrpm(specfile.path, sources, config: cfg.path, result_dir: target_directory, cb: lambda { + FileUtils.rm_rf(dir) + }) + end + end + end + + def self.builder(srpm, os: nil) + os ||= Pgpm::OS.auto_detect + os.with_scope do + target_directory ||= "." + cfg = Pgpm::RPM::Mock::Config.new(os.mock_config, path: target_directory) + srpm = [srpm] if srpm.is_a?(String) + srpm.reduce(nil) do |b, rpm| + op = Pgpm::RPM::Mock::Operation.rebuild(rpm, config: cfg.path, result_dir: target_directory) + b.nil? ? op : b.chain(op) + end + end + end + end + end +end diff --git a/lib/pgpm/rpm/mock/config.rb b/lib/pgpm/rpm/mock/config.rb new file mode 100644 index 0000000..dd9febe --- /dev/null +++ b/lib/pgpm/rpm/mock/config.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Pgpm + module RPM + module Mock + class Config + def initialize(source, path: nil) + @source = source + @path = path + @digest = Digest::SHA1.hexdigest(source) + end + + def path + f = Pathname(@path).join("mock-#{@digest}.cfg") + File.write(f, @source) if !File.exist?(f) || @digest != Digest::SHA1.hexdigest(File.read(f)) + f + end + end + end + end +end diff --git a/lib/pgpm/rpm/mock/operation.rb b/lib/pgpm/rpm/mock/operation.rb new file mode 100644 index 0000000..59cfd79 --- /dev/null +++ b/lib/pgpm/rpm/mock/operation.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module Pgpm + module RPM + module Mock + class Operation + def self.buildsrpm(spec, sources, config: nil, result_dir: nil, cb: nil) + buffer_result_dir = Dir.mktmpdir("pgpm") + args = ["--chain", "--buildsrpm", "--spec", spec, "--sources", sources, "--resultdir", + buffer_result_dir] + args.push("-r", config.to_s) unless config.nil? + new(*args, cb: lambda { + rpms = Dir.glob("*.rpm", base: buffer_result_dir).map do |f| + FileUtils.cp(Pathname(buffer_result_dir).join(f), result_dir) unless result_dir.nil? + f + end + FileUtils.rm_rf(buffer_result_dir) + cb.call unless cb.nil? + rpms + }) + end + + def self.rebuild(srpm, config: nil, result_dir: nil, cb: nil) + buffer_result_dir = Dir.mktmpdir("pgpm") + args = ["--chain", "--rebuild", srpm, "--resultdir", buffer_result_dir] + args.push("-r", config.to_s) unless config.nil? + new(*args, cb: lambda { + rpms = Dir.glob("*.rpm", base: buffer_result_dir).map do |f| + FileUtils.cp(Pathname(buffer_result_dir).join(f), result_dir) unless result_dir.nil? + f + end + FileUtils.rm_rf(buffer_result_dir) + cb.call unless cb.nil? + rpms + }) + end + + def initialize(*args, cb: nil) + @args = args + @cb = cb + end + + attr_reader :args, :cb + + def call + command = "mock #{@args.join(" ")}" + raise "Failed to execute `#{command}`" unless system command + + @cb.call unless @cb.nil? + end + + def chain(op) + self.class.new(*(@args + op.args), cb: lambda { + @cb.call unless @cb.nil? + op.cb.call unless op.cb.nil? + }) + end + + def and_then(op) + lambda do + res1 = call + res2 = op.call + return res1 + res2 if res1.is_a?(Array) and res2.is_a?(Array) + + [res1, res2] + end + end + end + end + end +end diff --git a/lib/pgpm/rpm/spec.rb b/lib/pgpm/rpm/spec.rb new file mode 100644 index 0000000..cb1113c --- /dev/null +++ b/lib/pgpm/rpm/spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require "digest" +require "open-uri" + +module Pgpm + module RPM + class Spec + attr_reader :package, :release, :postgres_version, :postgres_distribution + + def initialize(package, postgres_version: nil, postgres_distribution: nil) + @package = package + @release = 1 + + @postgres_version = postgres_version || "17" + @postgres_distribution = postgres_distribution || "postgresql#{@postgres_version}" + end + + def to_s + setup_opts = ["-q"] + setup_opts.push("-n", @package.source_url_directory_name) if @package.source_url_directory_name + sources = @package.sources + <<~EOF + Name: pgpm-#{@package.name}-#{@postgres_version}_#{@package.version} + Version: #{@package.version} + Release: #{@release}%{?dist} + Summary: #{@package.summary} + License: #{@package.license} + #{sources.each_with_index.map { |src, index| "Source#{index}: #{src.name}" }.join("\n")} + + BuildRequires: #{@postgres_distribution} #{@postgres_distribution}-devel #{@postgres_distribution}-server + #{@package.build_dependencies.map { |dep| "BuildRequires: #{dep}" }.join("\n")} + Requires: #{@postgres_distribution} + + %description + #{@package.description} + + %prep + %setup #{setup_opts.join(" ")} + #{(sources[1..] || []).filter { |s| unpack?(s) }.each_with_index.map { |_src, index| "%setup -T -D #{setup_opts.join(" ")} -a #{index + 1}" }.join("\n")} + + export PG_CONFIG=$(rpm -ql #{@postgres_distribution} | grep 'pg_config$') + #{@package.configure_steps.map(&:to_s).join("\n")} + + %build + export PG_CONFIG=$(rpm -ql #{@postgres_distribution} | grep 'pg_config$') + export PGPM_BUILDROOT=%{buildroot} + #{@package.build_steps.map(&:to_s).join("\n")} + + %install + export PG_CONFIG=$(rpm -ql #{@postgres_distribution} | grep 'pg_config$') + export PGPM_BUILDROOT=%{buildroot} + find %{buildroot} -type f | sort - | sed 's|^%{buildroot}||' > .pgpm_before | sort + #{@package.install_steps.map(&:to_s).join("\n")} + find %{buildroot} -type f | sort - | sed 's|^%{buildroot}||' > .pgpm_after | sort + comm -13 .pgpm_before .pgpm_after | sort -u > filelist.txt + + %files -f filelist.txt + + + %changelog + EOF + end + + private + + def unpack?(src) + src = src.name if src.respond_to?(:name) + src.to_s.end_with?(".tar.gz") + end + end + end +end diff --git a/lib/pgpm/version.rb b/lib/pgpm/version.rb new file mode 100644 index 0000000..48768ba --- /dev/null +++ b/lib/pgpm/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module Pgpm + VERSION = "0.1.0" +end diff --git a/packages/omnigres/extension_discovery.rb b/packages/omnigres/extension_discovery.rb new file mode 100644 index 0000000..711b3ea --- /dev/null +++ b/packages/omnigres/extension_discovery.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require "git" +require "parallel" + +module Omnigres + class ExtensionDiscovery + @@extensions = {} + @@git_revisions = {} + + def initialize(revision: nil, path: nil) + return if @@extensions[revision] + + suffix = revision ? "-#{revision}" : nil + path ||= Pgpm::Cache.directory.join("omnigres#{suffix}") + git = + if File.directory?(path) + ::Git.open(path) + else + ::Git.clone("https://github.com/omnigres/omnigres", path) + end + git.checkout(revision) if revision + @git = git + end + + attr_reader :git + + def extension_versions + process_git_log + @@extensions[@git.log.first.sha] + end + + def extension_git_revisions + process_git_log + @@git_revisions[@git.log.first.sha] + end + + private + + def process_git_log + return if @@extensions[@git.log.first.sha] + + merges = @git.log(:all).select { |c| c.parents.size > 1 } + commit_merges = Hash[Parallel.flat_map(merges) do |m| + before = m.parents[0].sha + after = m.parents[1].sha + commits = git.log.between(before, after).map(&:sha) + commits.product([m.sha]) + end] + + versions_maps = @git.log(:all).path("versions.txt").flat_map do |c| + Hash[@git.show(c, + "versions.txt").split("\n").map do |l| + ext, ver = l.split("=") + [[ext, c.sha], ver] + end] + end + extensions = versions_maps.flat_map(&:keys).map(&:first).uniq + @@extensions[@git.log.first.sha] = Hash[extensions.map do |e| + [e, versions_maps.map do |m| + m.transform_keys(&:first)[e] + end.compact.uniq.map { |v| SemverDialects.parse_version("cargo", v) }.sort.map(&:to_s)] + end] + last_hashes = Hash[versions_maps.flat_map do |h| + h.each_pair.map { |(ext, sha), ver| [[ext, ver], sha] } + end] + @@git_revisions[@git.log.first.sha] = last_hashes.each_with_object({}) do |((name, version), sha), result| + result[name] ||= {} + result[name][version] = commit_merges[sha] + end + end + end +end diff --git a/packages/omnigres/omni.rb b/packages/omnigres/omni.rb new file mode 100644 index 0000000..07ac9af --- /dev/null +++ b/packages/omnigres/omni.rb @@ -0,0 +1,172 @@ +# frozen_string_literal: true + +require "minitar" +require "find" +require "zlib" +require "progress" + +module Omnigres + class Omni < Pgpm::Package + github "omnigres/omnigres", download_version_tags: false + + def initialize(version) + @fetch_previous_version = true + super + end + + def summary + "Advanced adapter for Postgres extensions" + end + + def description + summary + end + + def self.package_versions + ExtensionDiscovery.new.extension_versions["omni"] + end + + def version_git_commit + ExtensionDiscovery.new.extension_git_revisions["omni"][version] + end + + def configure_steps + ["export PIP_CONFIG_FILE=$(pwd)/deps/pip.conf", + "cmake -S extensions/omni -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DOPENSSL_CONFIGURED=1 -DPG_CONFIG=$PG_CONFIG -DCPM_SOURCE_CACHE=$(pwd)/deps/_deps"] + end + + def build_steps + [ + "cmake --build build --parallel --target package_omni_extension", + "cmake --build build --parallel --target package_omni_migrations", + "scripts/generate_upgrade.sh #{name} #{previous_version.version} $(pwd)/omnigres-#{previous_version.version_git_commit} #{version} $(pwd)" + ] + end + + def install_steps + [ + "mkdir -p $PGPM_BUILDROOT/$($PG_CONFIG --sharedir)/extension", + "mkdir -p $PGPM_BUILDROOT/$($PG_CONFIG --pkglibdir)", + "cp build/packaged/*.so $PGPM_BUILDROOT/$($PG_CONFIG --pkglibdir)", + "cp build/packaged/extension/*.control $PGPM_BUILDROOT/$($PG_CONFIG --sharedir)/extension", + "cp build/packaged/extension/*.sql $PGPM_BUILDROOT/$($PG_CONFIG --sharedir)/extension" + ] + end + + def license + "Apache 2.0" + end + + def build_dependencies + %w[cmake openssl-devel python3 python3-devel nc sudo] + super + end + + def dependencies + ["openssl"] + super + end + + def source_url_directory_name + "omnigres-#{version_git_commit}" + end + + class UnsupportedVersion < StandardError + def message + "This version of omnigres extension is too old" + end + end + + def deps_tar_gz(dir_name = "deps") + @os = Pgpm::OS.auto_detect + if @os.is_a?(Pgpm::OS::RedHat) + system "sudo dnf -y install libxml2-devel libxslt-devel cmake gcc g++ python3-devel" + end + recipe_hash = Digest::SHA1.hexdigest(File.read(__FILE__) + scripts_tar_gz.read) + deps = File.join(source, dir_name) + src = source.to_s + ready_marker = File.join(src, ".complete." + recipe_hash) + targz = File.join(src, "deps-#{recipe_hash}.tar.gz") + unless File.exist?(ready_marker) + raise UnsupportedVersion unless File.directory?(File.join(src, "cmake", "dependencies")) + # FIXME: this is not perfect as we're still required to have the build-time dependencies + # required for this (such as g++, python3-devel, cmake, g++, etc...) + unless system "cmake -S #{src}/cmake/dependencies -B #{deps} -DCPM_SOURCE_CACHE=#{deps}/_deps" + raise "Can't fetch dependencies" + end + + begin + sgz = Zlib::GzipWriter.new(File.open(targz, "wb")) + tar = Minitar::Output.open(sgz) + files = Find.find(deps).filter do |entry| + # Don't include .git + !Pathname(entry).each_filename.map { |f| f == ".git" }.any? + end + files.with_progress("Preparing #{dir_name}.tar.gz") do |entry| + stat = File.stat(entry) + data = File.directory?(entry) ? nil : File.binread(entry) + info = { name: Pathname(entry).relative_path_from(src).to_s, + mode: stat.mode, uid: stat.uid, gid: stat.gid, mtime: stat.mtime } + Minitar.pack_as_file(info, data, tar) + end + ensure + tar.close + end + FileUtils.touch(ready_marker) + end + Pgpm::OnDemandFile.new("#{dir_name}.tar.gz", -> { File.open(targz) }) + end + + attr_accessor :fetch_previous_version + + def sources + srcs = super + srcs.push(deps_tar_gz) + if fetch_previous_version && previous_version + begin + prev_version = previous_version + puts "Fetching previous version #{prev_version.version} to be able to generate migrations" + prev_version.fetch_previous_version = false + srcs.push(prev_version.sources[0]) # archive + srcs.push(prev_version.deps_tar_gz("deps-prev")) # deps + rescue UnsupportedVersion + # ignore this one, just don't build an upgrade + puts "Ignore #{prev_version.version}, it is unsupported" + end + end + + srcs.push(scripts_tar_gz) + srcs + end + + private + + def previous_version + return @previous_version if @previous_version + + sorted_versions = self.class.package_versions.sort_by { |v| SemverDialects.parse_version("cargo", v) }.map(&:to_s) + index = sorted_versions.index(version) + return unless index > 0 + + @previous_version = self.class[sorted_versions[index - 1]] + end + + def scripts_tar_gz + s = String.new + begin + dir = File.join(File.dirname(__FILE__), "scripts") + sgz = Zlib::GzipWriter.new(StringIO.new(s)) + tar = Minitar::Output.open(sgz) + Find.find(dir) do |entry| + stat = File.stat(entry) + data = File.directory?(entry) ? nil : File.binread(entry) + info = { name: Pathname(entry).relative_path_from(File.dirname(__FILE__)).to_s, + mode: stat.mode, uid: stat.uid, gid: stat.gid, mtime: stat.mtime } + Minitar.pack_as_file(info, data, tar) + end + ensure + # Closes both tar and sgz. + tar.close + end + Pgpm::OnDemandFile.new("scripts.tar.gz", -> { StringIO.open(s) }) + end + end +end diff --git a/packages/omnigres/scripts/generate_upgrade.sh b/packages/omnigres/scripts/generate_upgrade.sh new file mode 100755 index 0000000..54dd439 --- /dev/null +++ b/packages/omnigres/scripts/generate_upgrade.sh @@ -0,0 +1,138 @@ +#! /usr/bin/env bash + +script_dir=$(dirname "$0") + +BUILD_TYPE=${BUILD_TYPE:=RelWithDebInfo} +DEST_DIR=${DESTDIR:=$script_dir} + +PG_CONFIG=${PG_CONFIG:=pg_config} +PG_BINDIR=$($PG_CONFIG --bindir) +PG_LIBDIR=$($PG_CONFIG --pkglibdir) +PGSHAREDIR=$($PG_CONFIG --sharedir) + +ext_name=$1 +old_ver=$2 +old_ver_path=$3 +new_ver=$4 +new_ver_path=$5 + +# for now, just for omni +new_ver_omni_ver=$(cat ${new_ver_path}/versions.txt | grep "^$ext_name=" | cut -d "=" -f 2) +old_ver_omni_ver=$(cat ${old_ver_path}/versions.txt | grep "^$ext_name=" | cut -d "=" -f 2) + +set -xe + +# Build the old extension +PIP_CONFIG_FILE=${new_ver_path}/deps-prev/pip.conf cmake -S "${old_ver_path}/extensions/${ext_name}" -B "${old_ver_path}/build" -DCPM_SOURCE_CACHE=$new_ver_path/deps-prev/_deps -DPG_CONFIG=$PG_CONFIG -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DOPENSSL_CONFIGURED=1 +cmake --build "${old_ver_path}/build" --parallel --target package_omni_extension +cmake --build "${old_ver_path}/build" --parallel --target package_omni_migrations +cmake --build "${old_ver_path}/build" --parallel --target package_${ext_name}_extension +cmake --build "${old_ver_path}/build" --parallel --target package_${ext_name}_migrations +# + +file $old_ver_path/build/packaged/omni--$old_ver_omni_ver.so +file $new_ver_path/build/packaged/omni--$new_ver_omni_ver.so + +# Determine the path to the extension in the new version +extpath=$(cat ${new_ver_path}/build/paths.txt | grep "^$ext_name " | cut -d " " -f 2) + +# List migrations in new version +new_migrations=() +new_migrate_path="$new_ver_path/$extpath/migrate" +if [ -d "$new_migrate_path/$ext_name" ]; then + # Extension that shares a folder with another extension + new_migrate_path="$new_migrate_path/$ext_name" +fi +for file in "$new_migrate_path/"*.sql; do + new_migrations+=($(basename "$file")) +done + +echo $new_migrations + +# Determine the path to the extension in the old version +extpath=$(cat ${old_ver_path}/build/paths.txt | grep "^$ext_name " | cut -d " " -f 2) +# List migrations in old version +old_migrations=() +old_migrate_path="$old_ver_path/$extpath/migrate" +if [ -d "$old_migrate_path/$ext_name" ]; then + # Extension that shares a folder with another extension + old_migrate_path="$old_migrate_path/$ext_name" +fi +for file in "$old_migrate_path/"*.sql; do + old_migrations+=($(basename "$file")) +done + +echo $old_migrations + + +for mig in ${new_migrations[@]}; do + if [ ! -f "$old_migrate_path/$mig" ]; then + $new_ver_path/build/inja/inja "$new_migrate_path/$mig" >> "$DEST_DIR/$ext_name--$old_ver--$new_ver.sql" + # Ensure a new line + echo >> "$DEST_DIR/$ext_name--$old_ver--$new_ver.sql" + fi +done + +cat "$DEST_DIR/$ext_name--$old_ver--$new_ver.sql" + +# Now, we need to replace functions that have changed +# For this, we'll: +# * prepare a database +db="$DEST_DIR/db_$ext_name-$old_ver-$new_ver" +rm -rf "$db" +chown -R postgres "$DEST_DIR" +sudo -u postgres "$PG_BINDIR/initdb" -D "$db" --no-clean --no-sync --locale=C --encoding=UTF8 +sockdir=$(sudo -u postgres mktemp -d) +# * install the extension in this revision, snapshot pg_proc, drop the extension +# We copy all scripts because there are dependencies +cp -v "$old_ver_path"/build/packaged/extension/*.sql "$old_ver_path"/build/packaged/extension/*.control "$PGSHAREDIR/extension" +cp -v "$old_ver_path"/build/packaged/*.so "$PG_LIBDIR" +sudo -u postgres "$PG_BINDIR/pg_ctl" start -D "$db" -o "-c max_worker_processes=64" -o "-c listen_addresses=''" -o "-k $sockdir" -o "-c shared_preload_libraries='$PG_LIBDIR/omni--$old_ver_omni_ver.so'" +sudo -u postgres "$PG_BINDIR/createdb" -h "$sockdir" "$ext_name" +cat <> "$DEST_DIR/$ext_name--$old_ver--$new_ver.sql" + -- Changed code + select pg_get_functiondef(pg_proc.oid) || ';' from (select * from pg_proc where oid not in (select aggfnoid from pg_aggregate)) as pg_proc + inner join pg_language on pg_language.oid = pg_proc.prolang and pg_language.lanname not in ('c', 'internal') + inner join pg_namespace on pg_namespace.oid = pg_proc.pronamespace and pg_namespace.nspname = '$ext_name' + inner join procs on + ((procs.proname::text = pg_proc.proname::text) and procs.pronamespace = pg_proc.pronamespace and + pg_get_function_identity_arguments(pg_proc.oid) = identity_args and pg_get_functiondef(pg_proc.oid) != procs.src); +EOF +# test the upgrade +# first, let's stop current database to load older extension again +sudo -u postgres "$PG_BINDIR/pg_ctl" stop -D "$db" -m smart +cp "$DEST_DIR/$ext_name--$old_ver--$new_ver.sql" "$old_ver_path/build/packaged/extension/$ext_name--$old_ver.control" "$old_ver_path/build/packaged/extension/$ext_name--$old_ver.sql" "$PGSHAREDIR/extension" +sudo -u postgres "$PG_BINDIR/pg_ctl" start -D "$db" -o "-c max_worker_processes=64" -o "-c listen_addresses=''" -o "-k $sockdir" -o "-c shared_preload_libraries='$PG_LIBDIR/omni--$old_ver_omni_ver.so'" +cat < 1.0" + + spec.add_dependency "dry-cli", "~> 1.1.0" + spec.add_dependency "dry-inflector", "~> 1.1.0" + spec.add_dependency "git", "~> 2.3.0" + spec.add_dependency "lspace", "~> 0.14" + spec.add_dependency "minitar", "~> 1.0.2" + spec.add_dependency "oj", "~> 3.16.6" + spec.add_dependency "parallel", "~> 1.26.3" + spec.add_dependency "progress", "~> 3.6.0" + spec.add_dependency "semver_dialects", "~> 3.4.3" + spec.add_dependency "xdg", "~> 8.7.0" + spec.add_dependency "zeitwerk", "~> 2.6.18" + spec.add_dependency "zlib", "~> 3.1.1" + + # For more information and examples about making a new gem, check out our + # guide at: https://bundler.io/guides/creating_gem.html +end diff --git a/sig/pgpm.rbs b/sig/pgpm.rbs new file mode 100644 index 0000000..8f93435 --- /dev/null +++ b/sig/pgpm.rbs @@ -0,0 +1,4 @@ +module Pgpm + VERSION: String + # See the writing guide of rbs: https://github.com/ruby/rbs#guides +end diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000..7e5d850 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require "pgpm" + +require "minitest/autorun" diff --git a/test/test_pgpm.rb b/test/test_pgpm.rb new file mode 100644 index 0000000..933450b --- /dev/null +++ b/test/test_pgpm.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require "test_helper" + +class TestPgpm < Minitest::Test + def test_that_it_has_a_version_number + refute_nil ::Pgpm::VERSION + end +end