require "uuid" require "uri" require "file_utils" require "./context.cr" require "./package.cr" require "./instructions.cr" require "./sources.cr" # 🤔 class URI def basename File.basename path end end class Package::Recipe @context : Context # Core recipe informations. getter name : String getter version : String getter release = 1 property url : String? property description : String = "" # Informations not specific to individual packages. getter sources : Sources getter packages : Array(Package) property packager : String? property maintainer : String? getter contributors = Array(String).new # Relations to other packages. # FIXME: `dependencies` needs splitting between run-time and build-time. getter dependencies = Array(String).new getter provides = Array(String).new getter conflicts = Array(String).new # Build instructions, helpers and other build-only data. setter dirname : String? # matching getter defined manually later. getter instructions = Instructions.new def initialize(@context, @name, @version) @sources = Sources.new @packages = [] of Package @working_uuid = UUID.random end def self.new(context, name, version) instance = Recipe.allocate.tap &.initialize(context, name, version) instance.packages << Package.new instance instance end def working_directory "#{@context.working_directory}/#{@working_uuid}" end def building_directory "#{working_directory}/build" end def fake_root_directory @packages[0].fake_root_directory end def dirname @dirname || "#{name}-#{version}" end def download sources.map do |url| unless File.exists? url.basename @context.run @context.sources_directory, "wget", [ url.to_s, "-O", url.basename ] end end end def extract Dir.mkdir_p building_directory sources.map do |url| basename = url.basename @context.run building_directory, "tar", [ "xvf", @context.sources_directory + "/" + url.basename ] end end # TODO: # - Export packaging directory in $PKG. # - Add (pre|post)-(configure|build|install) instructions. # - Have some instructions be non-critical, like the (pre|post] ones. # - Be careful about return values, flee from everything if something # goes somehow wrong. # - Make things thread-safe. (those ENV[]= calls are definitely not) def build success = true Dir.mkdir_p fake_root_directory ENV["PKG"] = fake_root_directory # Safety precautions. old_dir = Dir.current instructions.to_a.each do |instruction| if instruction.run(@context, self).failed? break BuildStatus::Failed end end Dir.cd old_dir ENV["PKG"] = nil do_splits success end private def do_splits @packages.each do |package| next if package == @packages[0] files = package.files next if files.nil? # Should only happen to @packages[0]. # TODO: ↑ add public APIs that ensure it. Dir.mkdir_p package.fake_root_directory # FIXME: What do we do if those are not on the filesystem? files.each do |file| puts "#{package.fake_root_directory}#{File.dirname file}" Dir.mkdir_p "#{package.fake_root_directory}#{File.dirname file}" FileUtils.mv( "#{fake_root_directory}#{file}", "#{package.fake_root_directory}#{file}" ) puts file @context.run "find", [ "#{package.fake_root_directory}/#{file}" ] end end end # TODO: # - Errors management. Stop at first failure? # - Splits. This should be done between #build and #package. def package @packages.find do |package| ! @context.package package end end def clean FileUtils.rm_rf building_directory FileUtils.rm_rf fake_root_directory end def to_s "#{name}-#{version}" end end