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 : Bool sources .compact_map do |url| unless File.exists? url.basename @context.run @context.sources_directory, "wget", [ url.to_s, "-O", url.basename ] end end .map(&.success?) .reduce(true) { |a, b| a && b } end def extract : Bool Dir.mkdir_p building_directory sources .map do |url| basename = url.basename @context.run building_directory, "tar", [ "xvf", @context.sources_directory + "/" + url.basename ] end .map(&.success?) .reduce true { |a, b| a && b } 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 : Bool 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. # FIXME: What do we do if those are not on the filesystem? files.each do |file| origin = "#{fake_root_directory}#{file}" destination ="#{package.fake_root_directory}#{file}" next unless File.exists? origin Dir.mkdir_p File.dirname destination FileUtils.mv( "#{fake_root_directory}#{file}", "#{package.fake_root_directory}#{file}" ) puts file @context.run "find", [ "#{package.fake_root_directory}/#{file}" ] end end end def auto_split @context.splitter_backends.each do |backend| @packages << backend.create_split self end end # TODO: # - Errors management. Stop at first failure? # - Splits. This should be done between #build and #package. def package : Bool # This tries to build them all and stops at the first failure # (failures are currently reported by Context#package) @packages .find do |package| if package.automatic && ! File.exists? package.fake_root_directory next end ! @context.package package end .== nil end def clean FileUtils.rm_rf building_directory FileUtils.rm_rf fake_root_directory end def to_s "#{name}-#{version}" end end