diff --git a/src/backends.cr b/src/backends.cr index 4944d3d..a71b71b 100644 --- a/src/backends.cr +++ b/src/backends.cr @@ -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 diff --git a/src/backends/baguette.cr b/src/backends/baguette.cr index 6bb31bd..018d6b1 100644 --- a/src/backends/baguette.cr +++ b/src/backends/baguette.cr @@ -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//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)) diff --git a/src/backends/build.cr b/src/backends/build.cr index 3242937..e94a760 100644 --- a/src/backends/build.cr +++ b/src/backends/build.cr @@ -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 diff --git a/src/backends/configure.cr b/src/backends/configure.cr index 99ff575..71656b2 100644 --- a/src/backends/configure.cr +++ b/src/backends/configure.cr @@ -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 " "}" diff --git a/src/backends/install.cr b/src/backends/install.cr index 4b812e5..fb64286 100644 --- a/src/backends/install.cr +++ b/src/backends/install.cr @@ -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 diff --git a/src/backends/splitter.cr b/src/backends/splitter.cr index dfb32c0..ba6fad9 100644 --- a/src/backends/splitter.cr +++ b/src/backends/splitter.cr @@ -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 diff --git a/src/context.cr b/src/context.cr index b5e3dd3..58cfb35 100644 --- a/src/context.cr +++ b/src/context.cr @@ -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 diff --git a/src/do.cr b/src/do.cr index 84477c5..d14efad 100644 --- a/src/do.cr +++ b/src/do.cr @@ -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 "" diff --git a/src/instructions.cr b/src/instructions.cr index 4de3ace..b9912a1 100644 --- a/src/instructions.cr +++ b/src/instructions.cr @@ -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 diff --git a/src/package.cr b/src/package.cr index d52bfa7..9b66678 100644 --- a/src/package.cr +++ b/src/package.cr @@ -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 diff --git a/src/recipe.cr b/src/recipe.cr index 0bb4f8c..60e61fb 100644 --- a/src/recipe.cr +++ b/src/recipe.cr @@ -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 -