Comments, grooming, new phases (src-split, pre- and post-)

master
Philippe Pittoli 2021-03-03 05:21:54 +01:00
parent 5f2e4d603c
commit e218282594
11 changed files with 175 additions and 70 deletions

View File

@ -1,4 +1,27 @@
enum Package::BuildStatus
Success
Failed
Pass
end
# Building instructions
# phases: src-split, (pre-)(configure|build|install) and post-install
# name examples: autotools, cmake, make
# build(context, recipe)
class Package::Backend::Building
getter phase : String
getter name : String
getter callback : Proc(Context, Recipe, BuildStatus)
def initialize(@phase, @name, &block : Proc(Context, Recipe, BuildStatus))
@callback = block
end
def build(context : Context, recipe : Recipe)
@callback.call context, recipe
end
end
abstract class Package::Backend::Packaging
getter name : String

View File

@ -2,13 +2,21 @@ 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
# TODO: checksums + security
def package(pkgdir : String, architecture : String, package : ::Package::Package) : Bool
# Fake root example: /tmp/packages/<uuid>/root-xz-dev
fake_root = package.fake_root_directory
@ -23,7 +31,7 @@ class Package::Backend::Packaging::Baguette < Package::Backend::Packaging
package_target = "#{pkgdir}/"
package_target += "#{architecture}/"
package_target += "#{package.name}-#{package.version}-#{package.release}.baguette"
Dir.mkdir_p File.dirname package_target
Do.mkdir_p File.dirname package_target
::Baguette::Log.detail "Archiving package content"
Do.run fake_root, "tar", ["cvf", data_archive_path, "."]
@ -75,7 +83,7 @@ class Package::Backend::Packaging::Baguette < Package::Backend::Packaging
manifest = File.open(file_name, "w").not_nil!
Dir.cd package.fake_root_directory
Do.cd package.fake_root_directory
FileUtils.find "." do |path|
file = path.lchop
@ -94,7 +102,7 @@ class Package::Backend::Packaging::Baguette < Package::Backend::Packaging
manifest.close
ensure
Dir.cd old_pwd.not_nil!
Do.cd old_pwd.not_nil!
end
def self.install(packages : Array(String))

View File

@ -1,16 +1,33 @@
# Actual compilation instructions
#
# Backends:
# - make
# pass if no Makefile
class Package::Backend::Build
def self.make : Backend::Building
Backend::Building.new "build", "make" do |context, recipe|
next BuildStatus::Pass unless Dir.exists? recipe.dirname
Dir.cd recipe.dirname
unless Dir.exists? recipe.dirname
Baguette::Log.detail "no '#{recipe.dirname}' directory: pass"
next BuildStatus::Pass
end
Do.cd recipe.dirname
unless File.exists? "Makefile"
Baguette::Log.detail "no Makefile: pass"
next BuildStatus::Pass
end
child = Do.sh "make -j#{context.build_cores} #{recipe.options["make"]? || ""}"
ncores = recipe.options["build-cores"]? || context.build_cores
options = [
"-j#{ncores}",
recipe.options["make"]? || ""
]
child = Do.sh "make #{options.join " "}"
if child.exit_status == 0
BuildStatus::Success

View File

@ -1,18 +1,35 @@
# Configuration phase
#
# Backends:
# - autotools
# pass if no "configure" file
# - cmake
# pass if no "CMakeLists.txt" file
# Both backends quit if recipe.dirname doesn't exist.
class Package::Backend::Configure
def self.autotools : Backend::Building
Backend::Building.new "configure", "autotools" do |context, recipe|
next BuildStatus::Pass unless Dir.exists? recipe.dirname
Dir.cd recipe.dirname
unless File.exists? "configure"
unless Dir.exists? recipe.dirname
Baguette::Log.detail "no '#{recipe.dirname}' directory: pass"
next BuildStatus::Pass
end
child = Do.sh "./configure --prefix=#{recipe.prefix} #{recipe.options["configure"]? || ""}"
Do.cd recipe.dirname
unless File.exists? "configure"
Baguette::Log.detail "no 'configure' file: pass"
next BuildStatus::Pass
end
options = [
"--prefix=#{recipe.prefix}",
recipe.options["configure"]? || ""
]
child = Do.sh "./configure #{options.join " "}"
if child.exit_status == 0
BuildStatus::Success
else
@ -23,16 +40,23 @@ class Package::Backend::Configure
def self.cmake : Backend::Building
Backend::Building.new "configure", "cmake" do |context, recipe|
next BuildStatus::Pass unless Dir.exists? recipe.dirname
unless Dir.exists? recipe.dirname
Baguette::Log.detail "no '#{recipe.dirname}' directory: pass"
next BuildStatus::Pass
end
Dir.cd recipe.dirname
Do.cd recipe.dirname
next BuildStatus::Pass unless File.exists? "CMakeLists.txt"
unless File.exists? "CMakeLists.txt"
Baguette::Log.detail "no 'CMakeLists.txt' file: pass"
next BuildStatus::Pass
end
ncores = recipe.options["build-cores"]? || context.build_cores
options = [
"-DCMAKE_INSTALL_PREFIX='#{recipe.prefix}'",
"-DCMAKE_BUILD_TYPE=Release #{recipe.options["cmake"]}",
"-- -j#{context.build_cores}"
"-- -j#{ncores}"
]
child = Do.sh "cmake . #{options.join " "}"

View File

@ -1,15 +1,30 @@
# Installation process
# Files to package are put in recipe.fake_root_directory
#
# Backends:
# - make
# pass if no Makefile
class Package::Backend::Install
def self.make : Backend::Building
Backend::Building.new "install", "make" do |context, recipe|
next BuildStatus::Pass unless Dir.exists? recipe.dirname
Dir.cd recipe.dirname
unless Dir.exists? recipe.dirname
Baguette::Log.detail "no '#{recipe.dirname}' directory: pass"
next BuildStatus::Pass
end
Do.cd recipe.dirname
unless File.exists? "Makefile"
next BuildStatus::Pass
end
child = Do.sh "make install 'DESTDIR=#{recipe.fake_root_directory}' #{recipe.options["make install"]? || ""}"
options = [
"'DESTDIR=#{recipe.fake_root_directory}'",
recipe.options["make install"]? || ""
]
child = Do.sh "make install #{options.join " "}"
if child.exit_status == 0
BuildStatus::Success

View File

@ -53,23 +53,23 @@ class Package::Backend::Splitter
end
end
# Source files: prefix containing "src".
# 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.
def self.src(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}-src"
split.files = prefixes.map do |prefix|
[
"#{prefix}/lib/share/src",
"#{prefix}/src",
"#{prefix}/usr/src",
"#{prefix}/usr/local/src"
]
end.flatten
# No need, files are already in the package fake root.
# split.files = prefixes.map do |prefix|
# [ "#{prefix}/src" ]
# end.flatten
split.recipe.require_stripping = false
end
end
end
end

View File

@ -28,9 +28,6 @@ class Package::Context
# By default, building a package only uses one core
property build_cores = 1
# list of environment variables we want to have when building
#property environment = {} of String => String
property recipe : Recipe? = nil
@ -53,6 +50,7 @@ class Package::Context
# 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
# src isn't really a split, files already are copied before build.
@splitter_backends << Backend::Splitter.src @prefixes
end

View File

@ -5,6 +5,7 @@ class Baguette::Context
end
class Do < Process
# list of environment variables we want to have when building
class_property environment = {} of String => String
def self.require_cmd(cmd : String)
@ -14,6 +15,11 @@ class Do < Process
end
end
def self.cd(chdir)
Baguette::Log.detail "cd " + chdir.colorize(:light_magenta).to_s
Dir.cd chdir
end
def self.run(chdir, command, args)
output = Process::Redirect::Inherit
@ -21,9 +27,9 @@ class Do < Process
output = Process::Redirect::Close
else
# log sub-commands outputs
STDOUT.puts "logging command " +
Baguette::Log.info "logging command " +
"#{command} #{args}".colorize(:light_magenta).to_s
STDOUT.puts "in " + Baguette::Context.logfile_path.colorize(:blue).mode(:bright).to_s
Baguette::Log.info "in " + Baguette::Context.logfile_path.colorize(:blue).mode(:bright).to_s
File.open Baguette::Context.logfile_path, "a" do |file|
file.puts ""

View File

@ -1,21 +1,11 @@
enum Package::BuildStatus
Success
Failed
Pass
end
class Package::Backend::Building
getter phase : String
getter name : String
getter callback : Proc(Context, Recipe, BuildStatus)
def initialize(@phase, @name, &block : Proc(Context, Recipe, BuildStatus))
@callback = block
end
def build(context : Context, recipe : Recipe)
@callback.call context, recipe
end
end
# Instructions for src-split, [pre-](configure|build|install) and post-install
# Simple array of strings with a name.
#
# Methods:
# - run(context, recipe) : BuildStatus
# execute each instruction
# instructions are provided by the recipe or by a backend if not specified
class Package::Instructions
class Set < Array(String)
@ -26,6 +16,7 @@ class Package::Instructions
# 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]
@ -38,8 +29,14 @@ class Package::Instructions
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|
Dir.cd recipe.building_directory
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
@ -53,18 +50,29 @@ class Package::Instructions
BuildStatus::Pass
rescue e
# Possible TODO: print the origin of the exception (backend, user-provided code, other/unknown).
STDERR << "Exception caught: " << e.message << "\n"
Baguette::Log.error "Exception during phase '#{@phase}', backend '#{last_backend}'"
Baguette::Log.error "Exception caught: #{e.message}"
BuildStatus::Failed
end
end
# FIXME: Switch to an enum at some point?
getter configure = Set.new "configure"
getter build = Set.new "build"
getter install = Set.new "install"
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 to_a
[configure, build, install]
[
src_split,
pre_configure, configure,
pre_build, build,
pre_install, install, post_install
]
end
end

View File

@ -2,7 +2,13 @@ require "specparser"
class Package::Package
getter recipe : Recipe
getter automatic : Bool
getter automatic : Bool # Was the package created by automatic splitting?
# Reference for splits. Recipe#packages[0] should keep this set to `nil`.
property files : Array(String)?
property file_patterns : Array(Regex)?
property fake_root_directory : String?
def initialize(@recipe, @automatic = false, @fake_root_directory = nil)
end
@ -38,6 +44,8 @@ class Package::Package
end
end
# Most of the attributes are inherited from recipe.
macro inherit(attribute)
@{{attribute.var.id}} : {{attribute.type.id}}?
@ -61,14 +69,8 @@ class Package::Package
inherit conflicts : Array(String)
inherit provides : Array(String)
# Reference for splits. Recipe#packages[0] should keep this set to `nil`.
property files : Array(String)?
property file_patterns : Array(Regex)?
inherit prefix : String
property fake_root_directory : String?
def dependencies
@dependencies || @recipe.run_dependencies
end

View File

@ -182,6 +182,9 @@ class Package::Recipe
# Packages can only be created once @name and @version exist!
@packages << Package.new self, false, fake_root_directory
# Spec sections are like this:
# %split my-application
# split = name of the section
specs.sections.each do |section|
case section.name
when "split"
@ -275,12 +278,9 @@ class Package::Recipe
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.
# - 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
Do.mkdir_p fake_root_directory
@ -289,8 +289,13 @@ class Package::Recipe
# Safety precautions.
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
if instruction.run(@context, self).failed?
raise BuildError.new self, "Building (#{instruction.phase} phase) failed."
@ -298,7 +303,7 @@ class Package::Recipe
end
end
Dir.cd old_dir
Do.cd old_dir
ENV["PKG"] = nil
@ -493,4 +498,3 @@ class Package::Recipe
spec
end
end