2019-07-02 03:50:50 +02:00
|
|
|
require "uuid"
|
|
|
|
require "uri"
|
|
|
|
require "file_utils"
|
|
|
|
|
2019-08-13 18:11:20 +02:00
|
|
|
require "specfileparser"
|
2019-08-02 17:43:09 +02:00
|
|
|
|
2019-07-02 03:50:50 +02:00
|
|
|
require "./context.cr"
|
|
|
|
require "./package.cr"
|
|
|
|
require "./instructions.cr"
|
|
|
|
require "./sources.cr"
|
2019-08-16 14:55:25 +02:00
|
|
|
require "./exception.cr"
|
2019-07-02 03:50:50 +02:00
|
|
|
|
2019-07-23 17:33:38 +02:00
|
|
|
module FileUtils
|
|
|
|
def self.find(directory, &block : Proc(String, Nil))
|
|
|
|
Dir.each_child directory do |child|
|
|
|
|
child_path = directory + "/" + child
|
|
|
|
yield child_path
|
|
|
|
|
|
|
|
if File.directory? child_path
|
|
|
|
self.find child_path, &block
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-07-02 03:50:50 +02:00
|
|
|
class Package::Recipe
|
|
|
|
@context : Context
|
|
|
|
|
2019-07-03 04:24:33 +02:00
|
|
|
# Core recipe informations.
|
|
|
|
getter name : String
|
|
|
|
getter version : String
|
|
|
|
getter release = 1
|
2019-07-02 03:50:50 +02:00
|
|
|
|
2019-07-03 04:24:33 +02:00
|
|
|
property url : String?
|
2019-08-01 23:02:16 +02:00
|
|
|
property description : String?
|
2019-07-03 04:24:33 +02:00
|
|
|
|
|
|
|
# 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.
|
2019-08-06 20:55:24 +02:00
|
|
|
getter run_dependencies = Array(String).new
|
|
|
|
getter build_dependencies = Array(String).new
|
2019-07-03 04:24:33 +02:00
|
|
|
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
|
2019-07-23 16:41:34 +02:00
|
|
|
getter options = Hash(String, String).new
|
2019-07-02 03:50:50 +02:00
|
|
|
|
2019-08-01 23:02:16 +02:00
|
|
|
getter sources = Sources.new
|
|
|
|
getter packages = Array(Package).new
|
|
|
|
|
|
|
|
@working_uuid : UUID = UUID.random
|
2019-07-02 03:50:50 +02:00
|
|
|
|
2019-08-01 23:02:16 +02:00
|
|
|
def initialize(@context, @name, @version)
|
2019-07-02 03:50:50 +02:00
|
|
|
end
|
|
|
|
|
2019-07-03 03:17:01 +02:00
|
|
|
def self.new(context, name, version)
|
|
|
|
instance = Recipe.allocate.tap &.initialize(context, name, version)
|
|
|
|
|
2019-08-02 23:58:42 +02:00
|
|
|
instance.packages << Package.new instance, false, instance.fake_root_directory
|
2019-07-03 03:17:01 +02:00
|
|
|
|
|
|
|
instance
|
|
|
|
end
|
|
|
|
|
2019-08-02 23:58:42 +02:00
|
|
|
def initialize(@context, filename : String)
|
2019-08-13 18:11:20 +02:00
|
|
|
specs = SpecFileParser.parse filename, {
|
2019-08-02 23:58:42 +02:00
|
|
|
"pkg" => fake_root_directory
|
|
|
|
}
|
|
|
|
|
2019-08-03 12:55:47 +02:00
|
|
|
name : String? = nil
|
|
|
|
version : String? = nil
|
2019-08-02 23:58:42 +02:00
|
|
|
|
2019-08-03 12:55:47 +02:00
|
|
|
raise "Could not parse `#{filename}`" if specs.nil?
|
2019-08-02 17:43:09 +02:00
|
|
|
|
|
|
|
specs.assignments.each do |key, value|
|
|
|
|
case key
|
|
|
|
when "name"
|
|
|
|
name = value.as_s
|
|
|
|
when "version"
|
|
|
|
version = value.as_s
|
|
|
|
when "release"
|
|
|
|
@release = value.as_s.to_i
|
|
|
|
when "url"
|
|
|
|
@url = value.as_s
|
|
|
|
when "description"
|
|
|
|
@description = value.as_s_or_ls
|
|
|
|
when "packager"
|
|
|
|
@packager = value.as_s
|
|
|
|
when "maintainer"
|
|
|
|
@maintainer = value.as_s
|
|
|
|
when "sources"
|
|
|
|
value.as_a_or_s.each do |source|
|
|
|
|
@sources << source
|
|
|
|
end
|
2019-08-13 15:32:22 +02:00
|
|
|
when "configure"
|
2019-08-02 17:43:09 +02:00
|
|
|
@instructions.configure << value.as_s_or_ls
|
|
|
|
when "build"
|
|
|
|
@instructions.build << value.as_s_or_ls
|
|
|
|
when "install"
|
|
|
|
@instructions.install << value.as_s_or_ls
|
2019-08-14 20:33:11 +02:00
|
|
|
when "dirname"
|
|
|
|
@dirname = value.as_s
|
2019-08-02 17:43:09 +02:00
|
|
|
when "dependencies"
|
|
|
|
value.as_a_or_s.each do |atom|
|
2019-08-06 20:55:24 +02:00
|
|
|
@run_dependencies << atom
|
|
|
|
@build_dependencies << atom
|
|
|
|
end
|
|
|
|
when "build-dependencies"
|
|
|
|
value.as_a_or_s.each do |atom|
|
|
|
|
@build_dependencies << atom
|
|
|
|
end
|
|
|
|
when "run-dependencies"
|
|
|
|
value.as_a_or_s.each do |atom|
|
|
|
|
@run_dependencies << atom
|
2019-08-02 17:43:09 +02:00
|
|
|
end
|
|
|
|
when "conflicts"
|
|
|
|
value.as_a_or_s.each do |atom|
|
|
|
|
@conflicts << atom
|
|
|
|
end
|
|
|
|
when "provides"
|
|
|
|
value.as_a_or_s.each do |atom|
|
|
|
|
@provides << atom
|
|
|
|
end
|
|
|
|
when "options"
|
|
|
|
value.as_a_or_s.each do |option|
|
|
|
|
match = option.split(':').map(
|
|
|
|
&.gsub(/^[ \t]*/, "").gsub(/[ \t]*$/, ""))
|
|
|
|
|
|
|
|
if match.size != 2
|
|
|
|
puts "WARNING: misformed option: #{option}"
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
2019-08-03 15:52:11 +02:00
|
|
|
key, value = match
|
2019-08-02 17:43:09 +02:00
|
|
|
|
2019-08-03 15:52:11 +02:00
|
|
|
@options[key] = value
|
2019-08-02 17:43:09 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
raise "`name` was not provided" unless name
|
|
|
|
raise "`version` was not provided" unless version
|
|
|
|
|
|
|
|
@name = name
|
|
|
|
@version = version
|
|
|
|
|
2019-08-22 19:16:08 +02:00
|
|
|
# Packages can only be created once @name and @version exist!
|
|
|
|
@packages << Package.new self, false, fake_root_directory
|
2019-08-02 17:43:09 +02:00
|
|
|
|
2019-08-22 19:16:08 +02:00
|
|
|
specs.sections.each do |section|
|
|
|
|
if section.name == "split"
|
|
|
|
@packages << Package.new self, section
|
|
|
|
end
|
|
|
|
end
|
2019-08-02 17:43:09 +02:00
|
|
|
end
|
|
|
|
|
2019-07-02 03:50:50 +02:00
|
|
|
def working_directory
|
2019-07-03 05:23:48 +02:00
|
|
|
"#{@context.working_directory}/#{@working_uuid}"
|
2019-07-02 03:50:50 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
def building_directory
|
|
|
|
"#{working_directory}/build"
|
|
|
|
end
|
|
|
|
|
|
|
|
def fake_root_directory
|
2019-08-02 23:58:42 +02:00
|
|
|
"#{working_directory}/root"
|
2019-07-02 03:50:50 +02:00
|
|
|
end
|
|
|
|
|
2019-07-02 08:47:11 +02:00
|
|
|
def dirname
|
|
|
|
@dirname || "#{name}-#{version}"
|
|
|
|
end
|
|
|
|
|
2019-08-16 14:55:25 +02:00
|
|
|
def download
|
|
|
|
sources.each do |url|
|
2019-08-22 19:57:51 +02:00
|
|
|
unless File.exists? url.filename
|
|
|
|
status = @context.run @context.sources_directory, "wget", [ url.to_s, "-O", url.filename ]
|
2019-08-16 14:55:25 +02:00
|
|
|
|
|
|
|
raise DownloadError.new self, url unless status.success?
|
2019-07-02 03:50:50 +02:00
|
|
|
end
|
2019-08-16 14:55:25 +02:00
|
|
|
end
|
2019-07-02 03:50:50 +02:00
|
|
|
end
|
|
|
|
|
2019-08-16 14:55:25 +02:00
|
|
|
def extract
|
2019-07-02 03:50:50 +02:00
|
|
|
Dir.mkdir_p building_directory
|
|
|
|
|
2019-08-16 14:55:25 +02:00
|
|
|
sources.each do |url|
|
2019-08-22 19:57:51 +02:00
|
|
|
basename = url.filename
|
2019-07-02 03:50:50 +02:00
|
|
|
|
2019-08-16 14:55:25 +02:00
|
|
|
status = @context.run(
|
|
|
|
building_directory,
|
|
|
|
"bsdtar", [
|
|
|
|
"xvf",
|
2019-08-22 19:57:51 +02:00
|
|
|
@context.sources_directory + "/" + url.filename
|
2019-08-16 14:55:25 +02:00
|
|
|
]
|
|
|
|
)
|
|
|
|
|
|
|
|
raise ExtractionError.new self, url unless status.success?
|
|
|
|
end
|
2019-07-02 03:50:50 +02:00
|
|
|
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)
|
2019-08-16 14:55:25 +02:00
|
|
|
def build
|
2019-07-02 03:50:50 +02:00
|
|
|
Dir.mkdir_p fake_root_directory
|
|
|
|
|
|
|
|
ENV["PKG"] = fake_root_directory
|
|
|
|
|
2019-07-02 19:45:33 +02:00
|
|
|
# Safety precautions.
|
2019-07-02 08:47:11 +02:00
|
|
|
old_dir = Dir.current
|
|
|
|
|
|
|
|
instructions.to_a.each do |instruction|
|
2019-07-02 19:45:33 +02:00
|
|
|
if instruction.run(@context, self).failed?
|
2019-08-16 14:55:25 +02:00
|
|
|
raise BuildError.new self, "Building (#{instruction.phase} phase) failed."
|
2019-08-13 13:48:09 +02:00
|
|
|
break
|
2019-07-02 08:47:11 +02:00
|
|
|
end
|
2019-07-02 03:50:50 +02:00
|
|
|
end
|
|
|
|
|
2019-07-02 08:47:11 +02:00
|
|
|
Dir.cd old_dir
|
|
|
|
|
2019-07-02 03:50:50 +02:00
|
|
|
ENV["PKG"] = nil
|
2019-07-02 08:47:11 +02:00
|
|
|
|
2019-08-16 14:55:25 +02:00
|
|
|
if Dir.children(fake_root_directory).size == 0
|
|
|
|
raise BuildError.new self, "No file was installed in the fake root."
|
|
|
|
end
|
2019-07-04 07:51:00 +02:00
|
|
|
|
2019-08-16 14:55:25 +02:00
|
|
|
do_splits
|
2019-07-02 03:50:50 +02:00
|
|
|
end
|
|
|
|
|
2019-07-04 07:51:00 +02:00
|
|
|
private def do_splits
|
2019-07-20 13:52:24 +02:00
|
|
|
(@packages + auto_splits).each do |package|
|
2019-07-04 07:51:00 +02:00
|
|
|
next if package == @packages[0]
|
|
|
|
|
2019-07-23 17:33:38 +02:00
|
|
|
files = package.files || [] of String
|
|
|
|
file_patterns = package.file_patterns || [] of Regex
|
2019-07-04 07:51:00 +02:00
|
|
|
|
2019-07-23 17:33:38 +02:00
|
|
|
files_to_split = [] of String
|
2019-07-04 07:51:00 +02:00
|
|
|
|
|
|
|
files.each do |file|
|
2019-07-04 23:18:54 +02:00
|
|
|
origin = "#{fake_root_directory}#{file}"
|
|
|
|
|
2019-07-23 17:33:38 +02:00
|
|
|
if File.exists? origin
|
|
|
|
files_to_split << file
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
FileUtils.find fake_root_directory do |file|
|
|
|
|
file = file[fake_root_directory.size..file.size]
|
|
|
|
files_to_split << file if file_patterns.any? &.match file
|
|
|
|
end
|
|
|
|
|
|
|
|
# FIXME: What do we do if those are not on the filesystem?
|
|
|
|
files_to_split.each do |file|
|
|
|
|
origin = "#{fake_root_directory}#{file}"
|
|
|
|
destination ="#{package.fake_root_directory}#{file}"
|
2019-07-04 23:18:54 +02:00
|
|
|
|
|
|
|
Dir.mkdir_p File.dirname destination
|
2019-07-04 07:51:00 +02:00
|
|
|
|
|
|
|
FileUtils.mv(
|
|
|
|
"#{fake_root_directory}#{file}",
|
|
|
|
"#{package.fake_root_directory}#{file}"
|
|
|
|
)
|
|
|
|
|
|
|
|
puts file
|
|
|
|
@context.run "find", [
|
|
|
|
"#{package.fake_root_directory}/#{file}"
|
|
|
|
]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-07-20 13:52:24 +02:00
|
|
|
def auto_splits : Array(Package)
|
|
|
|
@context.splitter_backends.compact_map do |backend|
|
|
|
|
backend.create_split self
|
2019-07-04 23:18:54 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-07-03 03:17:01 +02:00
|
|
|
# TODO:
|
|
|
|
# - Errors management. Stop at first failure?
|
|
|
|
# - Splits. This should be done between #build and #package.
|
2019-08-16 14:55:25 +02:00
|
|
|
def package
|
2019-07-20 13:30:09 +02:00
|
|
|
# This tries to build them all and stops at the first failure
|
|
|
|
# (failures are currently reported by Context#package)
|
2019-08-16 14:55:25 +02:00
|
|
|
(@packages + auto_splits).each do |package|
|
|
|
|
if package.automatic && ! File.exists? package.fake_root_directory
|
|
|
|
next
|
|
|
|
end
|
2019-07-20 13:30:09 +02:00
|
|
|
|
2019-08-16 14:55:25 +02:00
|
|
|
unless @context.package package
|
|
|
|
raise PackagingError.new self, package
|
2019-07-04 23:18:54 +02:00
|
|
|
end
|
2019-08-16 14:55:25 +02:00
|
|
|
end
|
2019-07-02 03:50:50 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
def clean
|
|
|
|
FileUtils.rm_rf building_directory
|
|
|
|
FileUtils.rm_rf fake_root_directory
|
|
|
|
end
|
|
|
|
|
|
|
|
def to_s
|
|
|
|
"#{name}-#{version}"
|
|
|
|
end
|
2019-08-03 12:55:47 +02:00
|
|
|
|
|
|
|
def to_spec_s
|
|
|
|
spec = [
|
|
|
|
"name: #{name}",
|
|
|
|
"version: #{version}",
|
|
|
|
"release: #{release}"
|
|
|
|
].join("\n") + "\n"
|
|
|
|
|
|
|
|
if @sources.size == 1
|
|
|
|
spec += "sources: #{sources[0].to_s.gsub @version, "%{version}"}\n"
|
|
|
|
end
|
|
|
|
|
|
|
|
if maintainer
|
|
|
|
spec += "maintainer: #{maintainer}\n"
|
|
|
|
end
|
|
|
|
if packager
|
|
|
|
spec += "packager: #{packager}\n"
|
|
|
|
end
|
|
|
|
|
|
|
|
if @options.size > 0
|
|
|
|
spec += "options:\n"
|
|
|
|
@options.each do |key, value|
|
|
|
|
value = value.gsub fake_root_directory, "%{pkg}"
|
|
|
|
spec += "\t- #{key}: #{value}\n"
|
|
|
|
end
|
|
|
|
spec += "\n" # Limitation of the current implementation.
|
|
|
|
end
|
|
|
|
|
|
|
|
if @dependencies.size > 0
|
|
|
|
spec += "dependencies:\n"
|
|
|
|
@dependencies.each do |dep|
|
|
|
|
spec += "\t- #{dep}\n"
|
|
|
|
end
|
|
|
|
spec += "\n"
|
|
|
|
end
|
|
|
|
|
|
|
|
if @dirname
|
|
|
|
spec += "dirname: #{@dirname}\n"
|
|
|
|
end
|
|
|
|
|
|
|
|
[
|
|
|
|
{"configure", @instructions.configure},
|
|
|
|
{"build", @instructions.build},
|
|
|
|
{"install", @instructions.install}
|
|
|
|
].each do |name, instructions|
|
|
|
|
if instructions.size > 0
|
|
|
|
spec += "@#{name}\n"
|
|
|
|
instructions.each do |line|
|
|
|
|
line = line.gsub /^\t* *\n/, ""
|
|
|
|
line = line.gsub /\n *\t*\n/, "\n"
|
|
|
|
line = line.gsub /\n *\t*$/, ""
|
|
|
|
line = line.gsub fake_root_directory, "%{pkg}"
|
|
|
|
line = line.gsub name, "%{name}"
|
|
|
|
line = line.gsub version, "%{version}"
|
|
|
|
line = line.gsub dirname, "%{dirname}"
|
|
|
|
line = line.gsub(/^[\t ]*/, "").gsub(/\n\t*/, "\n\t")
|
|
|
|
spec += "\t#{line}\n"
|
|
|
|
end
|
|
|
|
spec += "\n" # Limitation of the current implementation.
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
spec
|
|
|
|
end
|
2019-07-02 03:50:50 +02:00
|
|
|
end
|
|
|
|
|