lib/gdk/package_helper.rb (155 lines of code) (raw):
# frozen_string_literal: true
require 'fileutils'
require 'rubygems/package'
require 'zlib'
require 'net/http'
require 'uri'
require 'tmpdir'
require_relative '../gdk'
module GDK
class PackageHelper
API_V4_URL = 'https://gitlab.com/api/v4'
GDK_PROJECT_ID = '74823'
SUPPORTED_ARCHS = %w[darwin-arm64 linux-amd64 linux-arm64].freeze
EXCLUDED_FILES = %w[build/metadata.txt build/checksums.txt].freeze
FALLBACK_BRANCHES = %w[main master].freeze
def self.supported_os_arch?(os_arch_name = GDK::Machine.package_platform)
SUPPORTED_ARCHS.include?(os_arch_name)
end
attr_reader :package_basename, :package_name, :package_path, :package_version, :project_path, :upload_path, :download_paths, :platform_specific, :project_id, :token
def initialize(package:, project_id: GDK_PROJECT_ID, token: ENV.fetch('CI_JOB_TOKEN', ''))
config = GDK::PackageConfig.project(package) { raise "Unknown package: #{package}" }
@platform_specific = config[:platform_specific]
@package_basename = config[:package_name]
@package_name = @platform_specific ? "#{@package_basename}-#{GDK::Machine.package_platform}" : @package_basename
@package_path = config[:package_path]
@package_version = ENV['PACKAGE_VERSION'] || config[:package_version]
@project_path = config[:project_path]
@upload_path = config[:upload_path]
@download_paths = config[:download_paths]
@project_id = project_id
@token = token
end
def create_package
File.open(package_path, 'wb') do |file|
Zlib::GzipWriter.wrap(file) do |gzip|
Gem::Package::TarWriter.new(gzip) do |tar|
upload_path.find do |path|
next if path == upload_path
if path.directory?
tar.mkdir(path.to_s, path.stat.mode)
else
add_file_to_tar(tar, path)
end
end
end
end
end
GDK::Output.success("Package created at #{package_path}")
rescue StandardError => e
raise "Package creation failed: #{e.message}"
end
def upload_package
create_package
base_uri = "#{API_V4_URL}/projects/#{project_id}/packages/generic/#{package_name}"
versions = [package_version, 'latest']
versions.each do |version|
uri = URI.parse("#{base_uri}/#{version}/#{File.basename(package_path)}")
request = Net::HTTP::Put.new(uri)
request['JOB-TOKEN'] = token
request.body = File.read(package_path)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
raise "Upload failed for version '#{version}': #{response.body}" unless response.is_a?(Net::HTTPSuccess)
GDK::Output.success("Package uploaded successfully to #{uri}")
end
end
def download_package
if platform_specific && !GDK::PackageHelper.supported_os_arch?
GDK::Output.info("Unsupported OS or architecture detected in #{GDK::Machine.package_platform}.
To continue, please enable local compilation and then update by running `gdk config set #{package_basename}.skip_compile false && gdk update`.
")
return
end
return GDK::Output.success("No changes detected in #{project_path}, skipping package download and extraction.") if current_commit_sha == stored_commit_sha
uri = URI.parse("#{API_V4_URL}/projects/#{project_id}/packages/generic/#{package_name}/#{package_version}/#{File.basename(package_path)}")
GDK::Output.info("Downloading package from #{uri}")
response = Net::HTTP.get_response(uri)
if response.is_a?(Net::HTTPNotFound)
GDK::Output.warn("Package not found for version '#{package_version}', trying 'latest' instead.")
uri = URI.parse("#{API_V4_URL}/projects/#{project_id}/packages/generic/#{package_name}/latest/#{File.basename(package_path)}")
GDK::Output.info("Retrying download from #{uri}")
response = Net::HTTP.get_response(uri)
end
raise "Download failed: #{response.body}" unless response.is_a?(Net::HTTPSuccess)
File.write(package_path, response.body)
GDK::Output.success("Package downloaded successfully to #{package_path}")
@download_paths.each { |path| extract_package(path) }
FileUtils.rm_f(package_path)
store_commit_sha(current_commit_sha)
end
def extract_package(destination_path)
destination_path.mkpath
Dir.mktmpdir do |tmp_dir|
tmp_download_path = Pathname.new(tmp_dir)
File.open(package_path, 'rb') do |file|
Zlib::GzipReader.wrap(file) do |gzip|
Gem::Package::TarReader.new(gzip) do |tar|
tar.each do |entry|
extract_entry(entry, tmp_download_path)
end
end
end
end
copy_entries(tmp_download_path, destination_path)
GDK::Output.success("Package extracted successfully to #{destination_path}")
end
end
private
def add_file_to_tar(tar, file_path)
stat = file_path.stat
tar.add_file_simple(file_path.to_s, stat.mode, stat.size) do |io|
io.write(file_path.binread)
print '.'
end
end
def copy_entries(source_dir, destination_dir)
source_dir.find do |entry|
next if entry.directory? && entry.find(&:file?).nil?
destination_path = destination_dir.join(entry.relative_path_from(source_dir))
FileUtils.mkdir_p(destination_path.dirname)
FileUtils.cp_r(entry, destination_path, remove_destination: true)
end
end
def current_commit_sha
Shellout.new(%W[git -C #{project_path} rev-parse HEAD]).run
end
def extract_entry(entry, destination_path)
return if entry.full_name.include?('..')
target_path = destination_path.join(File.basename(entry.full_name))
parent_dir = target_path.dirname
if entry.directory?
FileUtils.mkdir_p(parent_dir, mode: entry.header.mode)
return
end
return unless entry.file?
return if EXCLUDED_FILES.any? { |excluded| entry.full_name.end_with?(excluded) }
File.binwrite(target_path, entry.read)
File.chmod(entry.header.mode, target_path)
end
def sha_file_path
File.join(sha_file_root, '.cache', ".#{package_name.gsub(/[^a-z0-9]+/i, '_')}_commit_sha")
end
def sha_file_root
GDK.config.gdk_root
end
def store_commit_sha(commit_sha)
FileUtils.mkdir_p(File.dirname(sha_file_path))
File.write(sha_file_path, commit_sha)
end
def stored_commit_sha
return nil unless File.exist?(sha_file_path)
File.read(sha_file_path).chomp
end
end
end