New package-create version. See more in git log.

This new version has a new code structure:
- context now has (configure|building|install)_backends attributes
- Package::Instructions now only is a wrapper around Array(String)
  with a phase name and a 'run' method
- new phases, with total user control through recipes:
  source-split, (pre-)?(configure|build|install) and post-install
- Do.cp

Misc:
- baguette backend removes the data.tar before processing the manifest
- new '-doc' split
- check for `bsdtar` in the PATH before doing anything
- logs are way more verbose
master
Philippe Pittoli 2021-03-09 17:51:51 +01:00
parent e218282594
commit c8ff92b508
8 changed files with 180 additions and 130 deletions

View File

@ -37,4 +37,22 @@ abstract class Package::Backend::Packaging
end
end
# Package::Backend::Splitter = create new package from a recipe
# takes (then stores) the given block
# this block takes a recipe as a parameter and create a new package
# the new package:
# keep prefixes
# new name = split name (-man, -src, ...)
# split files
class Package::Backend::Splitter
def initialize(&block : Proc(Recipe, Package))
@callback = block
end
def create_split(recipe : Recipe) : Package
@callback.call recipe
end
end
require "./backends/*"

View File

@ -40,6 +40,9 @@ class Package::Backend::Packaging::Baguette < Package::Backend::Packaging
# 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

View File

@ -1,20 +1,4 @@
class Package::Backend::Splitter
def initialize(&block : Proc(Recipe, Package))
@callback = block
end
def create_split(recipe : Recipe) : Package
@callback.call recipe
end
# Package::Backend::Splitter = create new package
# takes (then stores) the given block
# this block takes a recipe as a parameter and create a new package
# the new package:
# keep prefixes
# new name = split name (-man, -src, ...)
# split files
# Man-pages and documentation
def self.man(prefixes : Array(String)) : Splitter
@ -53,6 +37,26 @@ class Package::Backend::Splitter
end
end
# Documentation files: /share/doc/, *.html
def self.doc(prefixes : Array(String)) : Splitter
Backend::Splitter.new do |recipe|
Package.new(recipe, true).tap do |split|
prefixes = (prefixes + [recipe.prefix]).uniq
split.name = "#{recipe.name}-doc"
split.files = prefixes.map do |prefix|
[
"#{prefix}/share/doc",
]
end.flatten
split.file_patterns = prefixes.map do |prefix|
Regex.new("^" + prefix + ".*\\.html$")
end
end
end
end
# NOTE: src is not a regular split, _all_ files should be included,
# as well as the patches used for the build.
# Files are copied before build.

View File

@ -10,9 +10,22 @@ class Package::Context
property sources_directory = Dir.current
property packages_directory = Dir.current
getter packaging_backends = [] of Backend::Packaging
getter building_backends = [] of Backend::Building
getter splitter_backends = [] of Backend::Splitter
#alias Backends = Backend::Packaging | Backend::Building | Backend::Splitter
property user_instructions = Hash(String, Instructions).new
property configure_backends = Hash(String, Backend::Building).new
property building_backends = Hash(String, Backend::Building).new
property install_backends = Hash(String, Backend::Building).new
getter packaging_backends = Hash(String, Backend::Packaging).new
getter splitter_backends = Hash(String, Backend::Splitter).new
getter all_phase_names = [
"sources-split",
"pre-configure", "configure",
"pre-build", "build",
"pre-install", "install", "post-install",
]
# Directories where ports can be found.
getter repositories = [] of String
@ -35,27 +48,28 @@ class Package::Context
# Add package backends: baguette (package-tools) and apk.
# They implement the Backend::Packaging abstract class:
# init (@name) + package (context, package) method
@packaging_backends << Backend::Packaging::Baguette.new
@packaging_backends << Backend::Packaging::APK.new
@packaging_backends << Backend::Packaging::Pkgutils.new
@packaging_backends["baguette"] = Backend::Packaging::Baguette.new
@packaging_backends["apk"] = Backend::Packaging::APK.new
@packaging_backends["pkgutils"] = Backend::Packaging::Pkgutils.new
@selected_packaging_backend = @packaging_backends[0]
@selected_packaging_backend = @packaging_backends["baguette"]
# Add building backends: configuration, build and install.
@building_backends << Backend::Configure.autotools
@building_backends << Backend::Configure.cmake
@building_backends << Backend::Build.make
@building_backends << Backend::Install.make
@configure_backends["autotools"] = Backend::Configure.autotools
@configure_backends["cmake"] = Backend::Configure.cmake
@building_backends["make"] = Backend::Build.make
@install_backends["make"] = Backend::Install.make
# Split = new package from a recipe, each one with different files being included.
@splitter_backends << Backend::Splitter.man @prefixes
@splitter_backends << Backend::Splitter.dev @prefixes
@splitter_backends["man"] = Backend::Splitter.man @prefixes
@splitter_backends["dev"] = Backend::Splitter.dev @prefixes
@splitter_backends["doc"] = Backend::Splitter.doc @prefixes
# src isn't really a split, files already are copied before build.
@splitter_backends << Backend::Splitter.src @prefixes
#@splitter_backends << Backend::Splitter.src @prefixes
end
def packaging_backend=(name : String)
@selected_packaging_backend = @packaging_backends.find(&.name.==(name)).not_nil!
@selected_packaging_backend = @packaging_backends[name].not_nil!
end
def packaging_backend=(backend : Backend::Packaging)
@ -130,8 +144,8 @@ class Package::Context
STDERR.puts "Error during selecting packaging backend: #{e}"
STDERR.puts "#{value.as_s} seems not to be a valid backend"
STDERR.puts "valid backends:"
@packaging_backends.each do |backend|
STDERR.puts "- #{backend.name}"
@packaging_backends.each do |name, backend|
STDERR.puts "- #{name}"
end
exit 1
end

View File

@ -20,6 +20,13 @@ class Do < Process
Dir.cd chdir
end
def self.cp(origin : String, destination : String)
corig = origin.colorize(:light_magenta).to_s
cdest = destination.colorize(:light_orange).to_s
Baguette::Log.detail "cp #{corig} #{cdest}"
FileUtils.cp origin, destination
end
def self.run(chdir, command, args)
output = Process::Redirect::Inherit
@ -37,6 +44,8 @@ class Do < Process
file.puts "logging command $ #{command}"
file.puts " parameters $ #{args}"
end
output = File.open Baguette::Context.logfile_path, "a"
end
c = Process.run command, args, chdir: chdir, output: output, error: output, env: @@environment

View File

@ -3,76 +3,30 @@
# Simple array of strings with a name.
#
# Methods:
# - run(context, recipe) : BuildStatus
# - run(recipe) : BuildStatus
# execute each instruction
# instructions are provided by the recipe or by a backend if not specified
# instructions are provided by the recipe
class Package::Instructions
class Set < Array(String)
getter phase : String
def initialize(@phase)
super()
end
# FIXME: def execute
def run(context : Context, recipe : Recipe) : BuildStatus
# Does the recipe provided instructions for this phase?
if size > 0
each do |command|
child = Do.run recipe.building_directory, "sh", ["-x", "-c", command]
if child.exit_status != 0
return BuildStatus::Failed
end
end
return BuildStatus::Success
end
last_backend = "not-known"
# In case the recipe didn't provide instructions: checking backends.
context.building_backends.select(&.phase.==(@phase)).each do |backend|
last_backend = backend.name
Baguette::Log.info "Doing phase '#{@phase}' :: '" +
backend.name.colorize(:light_magenta).to_s +
"'"
Do.cd recipe.building_directory
rvalue = backend.build context, recipe
if rvalue == BuildStatus::Pass
next
end
return rvalue
end
BuildStatus::Pass
rescue e
# Possible TODO: print the origin of the exception (backend, user-provided code, other/unknown).
Baguette::Log.error "Exception during phase '#{@phase}', backend '#{last_backend}'"
Baguette::Log.error "Exception caught: #{e.message}"
BuildStatus::Failed
end
class Package::Instructions < Array(String)
getter phase : String
def initialize(@phase)
super()
end
# FIXME: Switch to an enum at some point?
getter src_split = Set.new "src-split"
getter configure = Set.new "configure"
getter pre_configure = Set.new "pre-configure"
getter build = Set.new "build"
getter pre_build = Set.new "pre-build"
getter install = Set.new "install"
getter pre_install = Set.new "pre-install"
getter post_install = Set.new "post-install"
def run(directory : String) : BuildStatus
if size == 0
Baguette::Log.error "Empty instructions for phase #{@phase}"
return BuildStatus::Pass
end
def to_a
[
src_split,
pre_configure, configure,
pre_build, build,
pre_install, install, post_install
]
each do |command|
child = Do.run directory, "sh", ["-x", "-c", command]
if child.exit_status != 0
return BuildStatus::Failed
end
end
BuildStatus::Success
end
end

View File

@ -99,7 +99,7 @@ end
# Verify package-create dependencies: tar, strip
Do.require_cmd "tar"
Do.require_cmd "bsdtar"
Do.require_cmd "strip"
if File.exists? CLIOpts.configuration_file
@ -184,7 +184,10 @@ begin
recipe.extract
recipe.build
# Perform every building operation:
# sources-split, (pre-)(configure|build|install) and post-install
recipe.run
recipe.package
recipe.clean unless CLIOpts.do_not_clean

View File

@ -65,7 +65,6 @@ class Package::Recipe
# Build instructions, helpers and other build-only data.
setter dirname : String? # matching getter defined manually later.
getter instructions = Instructions.new
getter options = Hash(String, String).new
getter watch_script : String?
@ -122,16 +121,17 @@ class Package::Recipe
value.as_a_or_s.each do |source|
@sources << source
end
when "configure"
@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
# User instructions.
when /(?<phase>(pre-)?(configure|build|install)|post-install)/
phase = $~["phase"]
@context.user_instructions[phase] = Instructions.new(phase).<<(value.as_s_or_ls)
when "dirname"
@dirname = value.as_s
when "prefix"
@prefix = value.as_s
when "dependencies"
value.as_a_or_s.each do |atom|
@run_dependencies << atom
@ -145,6 +145,7 @@ class Package::Recipe
value.as_a_or_s.each do |atom|
@run_dependencies << atom
end
when "conflicts"
value.as_a_or_s.each do |atom|
@conflicts << atom
@ -156,8 +157,7 @@ class Package::Recipe
when "options"
value.as_a_or_s.each do |option|
match = option.split(':').map(
&.gsub(/^[ \t]*/, "").gsub(/[ \t]*$/, ""))
match = option.split(':').map(&.strip)
if match.size != 2
STDERR.puts "WARNING: misformed option: #{option}"
@ -221,7 +221,7 @@ class Package::Recipe
if url.scheme == "file"
Baguette::Log.info "Copying '#{url.filename}'"
FileUtils.cp "#{recipe_directory}/#{url.filename}", filename
Do.cp "#{recipe_directory}/#{url.filename}", filename
else
Baguette::Log.info "Downloading '#{url.filename}'"
@ -271,17 +271,44 @@ class Package::Recipe
@context.sources_directory
end
FileUtils.cp "#{directory}/#{url.filename}",
Do.cp "#{directory}/#{url.filename}",
"#{building_directory}/#{url.filename}"
end
end
end
def execute_backend(backends : Hash(String, Backend::Building)) : BuildStatus
last_backend = "not-known"
# In case the recipe didn't provide instructions: checking backends.
backends.each do |name, backend|
last_backend = name
cname = name.colorize(:light_green).to_s
Baguette::Log.info "Backend :: '#{cname}'"
Do.cd building_directory
rvalue = backend.build @context, self
if rvalue == BuildStatus::Pass
Baguette::Log.info "Pass…"
next
end
return rvalue
end
BuildStatus::Pass
rescue e
Baguette::Log.error "Exception backend '#{last_backend}'"
Baguette::Log.error "Exception caught: #{e.message}"
BuildStatus::Failed
end
# TODO:
# - Have some instructions be non-critical, like the (pre|post) ones.
# - Be careful about return values, flee from everything if something
# goes somehow wrong.
def build
def run
Do.mkdir_p fake_root_directory
ENV["PKG"] = fake_root_directory
@ -290,16 +317,35 @@ class Package::Recipe
old_dir = Dir.current
# TODO: skip sequences
instructions.to_a.each do |instruction|
Baguette::Log.info "Building ('#{instruction.phase}' phase)"
# TODO: finish this following lines
#if Package::Context.any? &.phase==instruction.phase
# next
#end
@context.all_phase_names.each do |phase|
cphase = phase.colorize(:light_magenta).to_s
Baguette::Log.info "Building ('#{cphase}')"
if instruction.run(@context, self).failed?
raise BuildError.new self, "Building (#{instruction.phase} phase) failed."
break
ret = if instructions = @context.user_instructions[phase]?
instructions.run building_directory
else
# Some phases have available backends.
case phase
when "configure"
cphase = "configure".colorize(:light_blue).to_s
Baguette::Log.info "Executing phase :: #{cphase}"
execute_backend @context.configure_backends
when "build"
cphase = "build".colorize(:light_blue).to_s
Baguette::Log.info "Executing phase :: #{cphase}"
execute_backend @context.building_backends
when "install"
cphase = "install".colorize(:light_blue).to_s
Baguette::Log.info "Executing phase :: #{cphase}"
execute_backend @context.install_backends
else
# Not available backend and no user instructions.
BuildStatus::Pass
end
end
if ret.failed?
raise BuildError.new self, "Building ('#{phase}') failed."
end
end
@ -383,7 +429,9 @@ class Package::Recipe
end
def auto_splits : Array(Package)
@context.splitter_backends.compact_map do |backend|
@context.splitter_backends.compact_map do |name, backend|
csplit = name.colorize(:light_green).to_s
Baguette::Log.info "Creating split for #{csplit}"
backend.create_split self
end
end
@ -473,13 +521,10 @@ class Package::Recipe
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"
@context.all_phase_names.each do |phase|
if instructions = @context.user_instructions[phase]
next unless instructions.size > 0
spec += "@#{phase}\n"
instructions.each do |line|
line = line.gsub /^\t* *\n/, ""
line = line.gsub /\n *\t*\n/, "\n"