#!/bin/env perl
use FindBin;
use lib $FindBin::Bin;
use strict;
use warnings FATAL => 'all';
use IO::Handle;
use File::Basename;
use File::Temp;
use _kxLab;
#
# Generate $(HARDWARE).pkglist file for current directory
#
# usage:
# $0 topdir toolchain hardware
#
# where:
# 'topdir' - is a absolute path to the top directory of checked out branch
# 'toolchain' - is a TOOLCHAIN name
# 'hardware' - is a HARDWARE variant
#
# global variables
my ($build_system);
my ($topdir, $toolchain, $hardware, $flavour);
my ($target_build_dir, $requires_file);
my ($pkglist_file);
my ($system_version, $distro_name, $distro_version, $url);
my $tarball_suffix = "txz";
my %sub_trees;
my %tree;
sub usage
{
print <<EOF;
Usage: $0 topdir toolchain hardware
Where:
topdir - is a absolute path to the top of checked out branch;
toolchain - is a TOOLCHAIN name;
hardware - is a HARDWARE variant.
EOF
exit;
}
#
# Getting information from build-system/constants.mk
#
sub system_version
{
my $build_system = shift;
my $version;
open( FILE, "< $build_system/constants.mk" );
while( <FILE> )
{
if( /^SYSTEM_VERSION(.+= +)(.+)/ )
{
$version = $2;
}
}
close( FILE );
return $version;
}
sub distro_name
{
my $build_system = shift;
my $name;
open( FILE, "< $build_system/constants.mk" );
while( <FILE> )
{
if( /^DISTRO_NAME(.+= +)(.+)/ )
{
$name = $2;
}
}
close( FILE );
return $name;
}
sub distro_version
{
my $build_system = shift;
my $name;
open( FILE, "< $build_system/constants.mk" );
while( <FILE> )
{
if( /^DISTRO_VERSION(.+= +)(.+)/ )
{
$name = $2;
}
}
close( FILE );
return $name;
}
sub bug_url
{
my $build_system = shift;
my $url;
open( FILE, "< $build_system/constants.mk" );
while( <FILE> )
{
if( /^BUG_URL(.+= +)(.+)/ )
{
$url = $2;
}
}
close( FILE );
return $url;
}
#
# value_from_makefile( $makefile, $variable_name, $stop_line ):
# ------------------------------------------------------------
# This function returns the value of variable defined in the
# Makefile on global level before od REQUIRES declaration.
#
# The optional $stop_line argument presents the start of the
# line which terminates the analized part of Makefile.
#
sub value_from_makefile
{
my $makefile = shift;
my $vname = shift;
my $stop_line = shift;
my $value = "";
if( !defined $stop_line || $stop_line eq '' )
{
$stop_line = "__END_OF_REQUIRES__";
}
else
{
$stop_line = "^" . $stop_line;
}
my $cdir = dirname( $makefile );
my $shell_output = <<`SHELL`;
cd $cdir
head -n `cat Makefile | grep -n "$stop_line" | cut -f 1 -d ':'` Makefile | \
make TOOLCHAIN=$toolchain HARDWARE=$hardware FLAVOUR=$flavour -f - -p __build_requires__ 2>/dev/null | grep "$vname"
exit 0
SHELL
if( $shell_output =~ m/^$vname(.+= +)(.+)/gm )
{
$value = $2;
}
return $value;
}
#
# Getting information from Makefile
#
sub pkg_rootfs_target
{
my $makefile = shift;
my $install = "";
open( FILE, "< $makefile" );
while( <FILE> )
{
if( /^ROOTFS_TARGETS(.+= +)(.+)/ )
{
if( $2 ne "" ) { $install = "install"; }
}
elsif( /^ROOTFS_UPDATE_TARGETS(.+= +)(.+)/ )
{
if( $2 ne "" ) { $install = "update"; }
}
}
close( FILE );
if( $install eq "" ) { $install = "no"; }
return $install;
}
sub pkg_group
{
my $makefile = shift;
my $group;
open( FILE, "< $makefile" );
while( <FILE> )
{
if( /^PKG_GROUP(.+= +)(.+)/ )
{
$group = $2;
}
}
close( FILE );
return $group;
}
sub pkg_name
{
my $makefile = shift;
my $name = "";
open( FILE, "< $makefile" );
while( <FILE> )
{
if( /^[A-Z_0-9]*_PKG_NAME(.+= +)(.+)/ )
{
$name = $2;
}
}
close( FILE );
return $name;
}
sub pkg_version
{
my $makefile = shift;
my $version;
my $stop_line;
open( FILE, "< $makefile" );
while( <FILE> )
{
if( /^([A-Z_0-9]*_PKG_VERSION)(.+= +)(.+)/ )
{
$stop_line = $1;
$version = $3;
}
}
close( FILE );
# check if version is present as a reference to some variable:
if( $version =~ m/^\$\((.+)\)/ )
{
my $vname = $1;
# get value of referenced variable
$version = value_from_makefile( $makefile, $vname, $stop_line );
}
return $version;
}
sub pkg_license
{
my $makefile = shift;
my $license;
open( FILE, "< $makefile" );
while( <FILE> )
{
if( /^[A-Z_0-9]*_PKG_LICENSE(.+= +)(.+)/ )
{
$license = $2;
}
}
close( FILE );
return $license;
}
sub pkg_short_description
{
my $makefile = shift;
my $description;
open( FILE, "< $makefile" );
while( <FILE> )
{
if( /^[A-Z_0-9]*_PKG_SHORT_DESCRIPTION(.+= +)(.+)/ )
{
$description = $2;
}
}
close( FILE );
#
# In Makefiles we have to mask characters '\', '&', '*', '(', ')' inside
# the new value in the assignment operator with backslash. So, for axample,
# the value "name & \ * ( ) end" we have to assign as follow
#
# ..._SHORT_DESCRIPTION = name \& \\ \* \( \) end
#
# Here we have to remove backslashes and fill escaped symbols as is:
#
$description =~ s/\\(.?)/$1/g;
return $description;
}
sub get_treedirs
{
my @list;
seek( REQUIRES_FILE, 0, SEEK_SET );
while( <REQUIRES_FILE> )
{
if( /^TREEDIRS(.+= +)(.+)/ )
{
@list = split( ' ', $2 );
}
}
return @list;
}
sub get_root
{
my $root;
seek( REQUIRES_FILE, 0, SEEK_SET );
while( <REQUIRES_FILE> )
{
if( /^# ROOT(=)(.+)/ )
{
$root = $2;
}
}
return $root;
}
sub get_deps
{
my %deps;
seek( REQUIRES_FILE, 0, SEEK_SET );
while( <REQUIRES_FILE> )
{
if( /(.+)(: +)(.+)/ )
{
$deps{$1} = $3;
}
}
return %deps;
}
my $root_node = 1;
#
# PACKAGE HASH:
# ============
#
# name => $(PKG_NAME) from Makefile
# version => $(PKG_VERSION) from Makefile
# group => $(PKG_GROUP) from Makefile {app,base,dev,libs,net,...}
#
# arch => $toolchain from comandline args
# hardware => $hardware from comandline args
# flavour => $flavour from comandline args for ROOT pkg, from REQUIRES for dependencies
# tarball => "$name-$version-$arch-$distro_name-$distro_version.$tarball_suffix"
#
# distro_name => $(DISTRO_NAME) from build-system/constants.mk
# distro_version => $(DISTRO_VERSION) from build-system/constants.mk
# url => $(BUG_URL) from build-system/constants.mk
# license => from Makefile
# short_description => from Makefile
# description => first line from .DESCRIPTION
# uncompressed_size => from .PKGINFO
# total_files => from .PKGINFO
#
# dir => path to Makefile from .$(HW)_requires
# children =>
#
sub fill_package_info
{
my $base_dir = shift;
my $makefile = shift;
my $flavour = shift;
my ( $product_path, $tarball_file );
my %pkg;
$pkg{'dir'} = $base_dir;
$pkg{'install'} = pkg_rootfs_target( $makefile );
$pkg{'name'} = pkg_name( $makefile );
if( $pkg{'name'} eq "" )
{
# There is no package for this Makefile
$pkg{'name'} = $pkg{'dir'};
return %pkg;
}
$pkg{'version'} = pkg_version( $makefile );
$pkg{'arch'} = $toolchain;
$pkg{'hardware'} = $hardware;
$pkg{'flavour'} = $flavour;
$pkg{'group'} = pkg_group( $makefile );
$pkg{'distro_name'} = $distro_name;
$pkg{'distro_version'} = $distro_version;
$pkg{'url'} = $url;
$pkg{'license'} = pkg_license( $makefile );
$pkg{'short_description'} = pkg_short_description( $makefile );
$pkg{'tarball'} = $pkg{'name'} . "-" .
$pkg{'version'} . "-" .
$pkg{'arch'} . "-" .
$distro_name . "-" .
$distro_version . "." .
$tarball_suffix;
return %pkg;
}
#
# Parse the command line options
#
# Get the rest arguments of the command line
$topdir = shift;
$toolchain = shift;
$hardware = shift;
$flavour = shift;
my $makefile = "Makefile";
if( ! defined $topdir or $topdir eq "" ) { usage; }
if( ! defined $toolchain or $toolchain eq "" ) { usage; }
if( ! defined $hardware or $hardware eq "" ) { usage; }
if( ! defined $flavour or $flavour eq "" ) { $flavour = ""; }
_kxLab::error( "$0: $topdir is not a directory" ) if( ! -d $topdir );
_kxLab::error( "$0: Makefile missing: $makefile" ) if ( ! -f $makefile );
$build_system = $topdir . "/build-system";
$system_version = system_version( $build_system );
$distro_name = distro_name( $build_system );
$distro_version = distro_version( $build_system );
$url = bug_url( $build_system );
if( $flavour eq "" )
{
$target_build_dir = "." . $toolchain . "/" . $hardware;
}
else
{
$target_build_dir = "." . $toolchain . "/" . $hardware . "/" . $flavour;
}
$requires_file = $target_build_dir . "/.requires";
if( $flavour eq "" )
{
$pkglist_file = $target_build_dir . "/" . $hardware . ".pkglist";
}
else
{
$pkglist_file = $target_build_dir . "/" . $hardware . "-" . $flavour . ".pkglist";
}
# open the intput file
open(REQUIRES_FILE, "< $requires_file") or
_kxLab::error( "$0: Could not open $requires_file file: $!" );
# open the output files
open(PKGLIST_FILE, "> $pkglist_file") or
_kxLab::error( "$0: Could not open $pkglist_file file: $!" );
my $root = get_root();
my @treedirs = get_treedirs();
my %deps = get_deps();
#
# This is the root package
#
%tree = fill_package_info( $root, $makefile, $flavour );
my %sequence;
my $order = 0;
#################################################################
# if( there is any dependencies )
#
if( %deps )
{
my @dep_keys = keys %deps;
my $count = scalar( keys %deps );
foreach my $dir ( @treedirs )
{
if( ! grep { $_ eq $dir } @dep_keys )
{
my $key = $dir;
$sequence{$key} = ++$order;
@treedirs = grep { $_ ne $key } @treedirs;
# Split dir^flavour:
my ($d, $f);
$d = `echo $dir | cut -f 1 -d '^'`;
$d =~ s/^\s+|\s+$//;
if( $dir =~ m/\^/ )
{
$f = `echo $dir | cut -f 2 -d '^'`;
$f =~ s/^\s+|\s+$//;
}
else
{
$f = "";
}
# Insert into sub_trees:
my %pkg = fill_package_info( $d, $topdir . "/" . $d . "/Makefile", $f );
$sub_trees{$dir} = \%pkg;
delete $deps{$dir};
}
}
for( my $i = 0; $i < $count; ++$i )
{
my @installed = keys %sequence;
foreach my $key (sort keys %deps)
{
my $ok = 1;
my @dirs = split( ' ', $deps{$key} );
if( $key ne "all" )
{
foreach my $dir ( @dirs )
{
if( ! grep { $_ eq $dir } @installed )
{
$ok = 0;
}
}
if( $ok == 1 )
{
$sequence{$key} = ++$order;
# Split dir^flavour:
my ($d, $f);
$d = `echo $key | cut -f 1 -d '^'`;
$d =~ s/^\s+|\s+$//;
if( $key =~ m/\^/ )
{
$f = `echo $key | cut -f 2 -d '^'`;
$f =~ s/^\s+|\s+$//;
}
else
{
$f = "";
}
# create package node:
my %pkg = fill_package_info( $d, $topdir . "/" . $d . "/Makefile", $f );
# add children:
foreach my $dir ( @dirs )
{
my $child = $sub_trees{$dir};
push( @{$pkg{'children'}}, $child );
}
# insert new sub tree into $sub_tree:
$sub_trees{$key} = \%pkg;
delete $deps{$key};
}
}
}
}
#
# The root node children
#
my @dirs = split( ' ', $deps{'all'} );
foreach my $dir ( @dirs )
{
my $child = $sub_trees{$dir};
push( @{$tree{'children'}}, $child );
}
}
else
{
my %pkg;
$pkg{'dir'} = "void";
$pkg{'name'} = "void";
push( @{$tree{'children'}}, \%pkg );
}
#
# End if( there is any dependencies )
#################################################################
#################################################################
# Building Required Packages List:
#
sub compare_order
{
$sequence{$a} <=> $sequence{$b};
}
print PKGLIST_FILE "#\n";
print PKGLIST_FILE "# file format:\n";
print PKGLIST_FILE "# ===========\n";
print PKGLIST_FILE "#\n";
print PKGLIST_FILE "# Each line contains six fields separated by colon symbol ':' like following.\n";
print PKGLIST_FILE "#\n";
print PKGLIST_FILE "# pkgname:version:description:tarball:procedure:priority\n";
print PKGLIST_FILE "#\n";
print PKGLIST_FILE "# where:\n";
print PKGLIST_FILE "#\n";
print PKGLIST_FILE "# pkgname - should be the same as the value of pkgname in the '.DESCRIPTION' file;\n";
print PKGLIST_FILE "# version - package version for showing in check list dialog box if this file is\n";
print PKGLIST_FILE "# used to complete common check dialog for installing group of packages;\n";
print PKGLIST_FILE "# description - short description for showing in check list dialog box if this file is\n";
print PKGLIST_FILE "# used to complete common check dialog for installing group of packages;\n";
print PKGLIST_FILE "# tarball - should end in '." . $tarball_suffix . "';\n";
print PKGLIST_FILE "# procedure - installation procedure {install | update}:\n";
print PKGLIST_FILE "# * 'install' - if package requires normal installation,\n";
print PKGLIST_FILE "# * 'update' - if already installed package should be updated by this\n";
print PKGLIST_FILE "# package archive;\n";
print PKGLIST_FILE "# priority - { REQUIRED|RECOMMENDED|OPTIONAL|SKIP }\n";
print PKGLIST_FILE "# synonims:\n";
print PKGLIST_FILE "# { REQUIRED | required | REQ | req }\n";
print PKGLIST_FILE "# { RECOMMENDED | recommended | REC | rec }\n";
print PKGLIST_FILE "# { OPTIONAL | optional | OPT | opt }\n";
print PKGLIST_FILE "# { SKIP | skip | SKP | skp }\n";
print PKGLIST_FILE "#\n";
my $packages_done = 0;
sub print_result
{
my $out_string = sprintf( "####### Packages Install List (done: %4d packages)\n", $packages_done );
print $out_string;
}
print "#######\n";
foreach my $dir (sort compare_order( (keys %sequence) ))
{
my $package;
if( $dir ne "all" )
{
$package = $sub_trees{$dir};
#
# Currently gcc-runtime has not ROOTFS_TARGET and not all packages requires GCC.
# We will not add GCC in this procedure forcibly. But developers have to care about
# competing packages GCC and gcc-runtime.
#
if( $package->{'install'} ne "no" )
{
if( $package->{'flavour'} eq "" )
{
print PKGLIST_FILE $package->{'name'} . ":" .
$package->{'version'} . ":" .
$package->{'short_description'} . ":" .
$package->{'group'} . "/" .
$package->{'tarball'} . ":" .
$package->{'install'} . ":REQUIRED\n";
}
else
{
print PKGLIST_FILE $package->{'name'} . ":" .
$package->{'version'} . ":" .
$package->{'short_description'} . ":" .
$package->{'group'} . "/" .
$package->{'flavour'} . "/" .
$package->{'tarball'} . ":" .
$package->{'install'} . ":REQUIRED\n";
}
++$packages_done;
}
}
}
if( $tree{'install'} ne "no" )
{
if( $tree{'flavour'} eq "" )
{
print PKGLIST_FILE $tree{'name'} . ":" .
$tree{'version'} . ":" .
$tree{'short_description'} . ":" .
$tree{'group'} . "/" .
$tree{'tarball'} . ":" .
$tree{'install'} . ":REQUIRED\n";
}
else
{
print PKGLIST_FILE $tree{'name'} . ":" .
$tree{'version'} . ":" .
$tree{'short_description'} . ":" .
$tree{'group'} . "/" .
$tree{'flavour'} . "/" .
$tree{'tarball'} . ":" .
$tree{'install'} . ":REQUIRED\n";
}
++$packages_done;
}
print_result();
print "#######\n";
#
# End of Building Required Packages List.
#################################################################
# close input files
close REQUIRES_FILE;
# close output files
close PKGLIST_FILE;