require "openssl" require "../backends.cr" # Baguette package backend # # Packages contain: # - data.tar.zst: compressed tarball containing all installed data # - manifest: list of directories, files and their checksum # - control.spec: meta data about the package # # TODO: # - security: add a signature file class Package::Backend::Packaging::Baguette < Package::Backend::Packaging def initialize @name = "package" end def package(pkgdir : String, architecture : String, package : ::Package::Package) : Bool # Fake root example: /tmp/packages//root-xz-dev fake_root = package.fake_root_directory # Temporary archive, before compression. data_archive_path = "#{fake_root}/data.tar" # All archive data. # Content of the final tarball. compressed_data_archive_path = "#{fake_root}/data.tar.zst" # Compressed version (in final tarball). control_spec_file_path = "#{fake_root}/control.spec" # Spec file, with package informations. manifest_file_path = "#{fake_root}/manifest" # List of all the included files. package_target = "#{pkgdir}/" package_target += "#{architecture}/" package_target += "#{package.name}-#{package.version}-#{package.release}.baguette" Do.mkdir_p File.dirname package_target ::Baguette::Log.detail "Archiving package content" Do.run fake_root, "tar", ["cvf", data_archive_path, "."] ::Baguette::Log.detail "Compressing the archive" # produces data.tar.zst Do.run fake_root, "zstd", ["--ultra", data_archive_path] ::Baguette::Log.detail "Removing the uncompressed archive" Do.run fake_root, "rm", [data_archive_path] ::Baguette::Log.detail "Generating control.spec" generate_spec package, control_spec_file_path ::Baguette::Log.detail "Generating manifest" generate_manifest package, manifest_file_path ::Baguette::Log.detail "Assembling '#{package_target}'" r = Do.run fake_root, "tar", [ "cf", package_target, # WARNING: relative paths are necessary. "control.spec", "manifest", "data.tar.zst" ] r.exit_status == 0 end def generate_spec(package : ::Package::Package, file_name : String) du = `du -sk #{package.fake_root_directory}` size = du.sub(/[ \t].*/, "").to_u64 * 1024_u64 File.open file_name, "w" do |file| file.puts "name: #{package.name}" file.puts "version: #{package.version}" file.puts "release: #{package.release}" file.puts file.puts "size: #{size}" file.puts "origin: #{package.recipe.name}" file.puts "slot: #{package.prefix}" file.puts file.puts "url: #{package.url}" file.puts "description: #{package.description}" file.puts file.puts "dependencies: #{package.dependencies.join ", "}" file.puts "conflicts: #{package.conflicts.join ", "}" file.puts "provides: #{package.provides.join ", "}" end end def generate_manifest(package : ::Package::Package, file_name : String) old_pwd = Dir.current manifest = File.open(file_name, "w").not_nil! Do.cd package.fake_root_directory FileUtils.find "." do |path| file = path.lchop if File.symlink? path manifest.puts [file, "symlink", File.readlink(path)].join ':' elsif File.directory? path manifest.puts [file, "directory"].join ':' elsif File.info?(path).try &.file? digest = OpenSSL::Digest.new("sha256").file(path).final.hexstring manifest.puts [file, "file", digest].join ':' else manifest.puts [file, "other"].join ':' end end manifest.close ensure Do.cd old_pwd.not_nil! end def self.install(packages : Array(String)) # TODO: install packages through package-install raise "call to package-install not implemented, yet" end end