File bs_productconvert of Package obs-server

#!/usr/bin/perl -w
#
# Copyright (c) 2008 Klaas Freitag, Novell Inc.
# Copyright (c) 2008 Adrian Schroeter, Novell Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program (see the file COPYING); if not, write to the
# Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
#
################################################################
#
# Converter to create Kiwi- and Spec files from product definition
#
use strict;
use Getopt::Std;
use Data::Dumper;
use File::Basename;

use XML::Structured ':bytes';
use BSUtil;
use BSXML;
use BSProductXML;
use BSKiwiXML;
use BSXML;

my $bsdir;
eval{
  require BSConfig;
  $bsdir = "$BSConfig::bsdir" if defined($BSConfig::bsdir);
};


# read the product xml file

use vars qw ( $opt_h $opt_l $opt_d $opt_m 
              @errors %conditionals %repositories %groupRefs %archSets $indir $runOnServer);

my %product_requires;

sub usage()
{
  print<<END

  bs_productconvert product_file output_directory [project_name]

  convert a product definition file to a spec- and a kiwi source file.

  Options:

  -h:   help, print this text.
  -l:   Run local on server, use direct path instead of obs:// URL.
  -d:   debug, create debug output and files
  -m:   mediaset, build only for the given mediaset, identify by name
END
;

  exit;
}

sub readProductFile( $ )
{
    my ($filename) = @_;

    print "parsing product definition... ";
    my $xml = BSProductXML::readproductxml( "$filename", 0, $opt_d );

    if( defined($xml) ) {
	print "success!\n";
	# print Dumper $xml;
    } else {
	print "FAILED: Unable to parse $filename\n";
	die;
    }
    return $xml;
}


sub createDescription( $ )
{
    my( $prodRef ) = @_;
    my $re;

    $re->{type} = "system";
    $re->{author} = "The SUSE Team";
    $re->{contact} = "build\@opensuse.org";
    $re->{specification} = $prodRef->{summary}[0]->{_content};  # FIXME: lang dependent

    return $re;
}

sub convertFlags( $ )
{
    my ($flag)=@_;
    $flag =~ s/GE/>/sg;
    $flag =~ s/EQ/=/sg;
    $flag =~ s/LT/</sg;
    return $flag;
}

sub createPreferences( $ )
{
    my( $prodRef ) = @_;
    my $re = {};
    my $version = $prodRef->{version};
    $version = $version.".0" unless ( $version =~ /\./ ); # needed for products without minor number
    my $release = $prodRef->{release};
    $release = "0" unless (defined($prodRef->{release}));

    $re->{type} = [{_content => "product"}];
    $re->{version} = sprintf( "%s.%s",
			      $version,
			      $release  );

    # $re->{size} = ; do not give the optional size
    $re->{packagemanager} = "zypper" ;

    return $re;
}

sub parseRepositories( $ )
{
    my( $repoListRef ) = @_;
    my $priority=0;
    my $href="";
    foreach my $repoRef ( @{$repoListRef } ) {
	my $name = $repoRef->{name};
        $href=$repoRef->{path} if defined($repoRef->{path});
        $href=$repoRef->{href} if defined($repoRef->{href});
#	print "Repository $name parsed.\n";
	$priority=$repoRef->{'priority'} if (defined($repoRef->{'priority'}));
        $repositories{$name} = { href => "$href", priority => "$priority" };
	$priority++;
    }
}

#
# The conditionals are kind of macros which are used all over the product definition.
# The conditionals part of the product def is parsed into the global conditionalhash
# with the conditional name as key.
#
sub parseConditionals( $ )
{
    my ($conditionalRef) = @_;
    # print Dumper $conditionalRef;
    return unless( $conditionalRef );

    foreach my $condRef (@{$conditionalRef}) {
	my $name = $condRef->{name};
#	print "Parsed conditional $name\n";
	# print Dumper $condRef;
	$conditionals{$name} = $condRef;
    }
}

sub parseArchsets( $ ) 
{
    my ($archSetsRef ) = @_;

    foreach my $archSet ( @{$archSetsRef } ) {
	# print "Parsing Archset $archSet->{name}\n";

	# print "XXXX " . Dumper( $archSet ) . "\n";
	if( $archSet->{name} ) {
	    my %h;
	    $h{productarch} = $archSet->{productarch};
	    my @a;
	    foreach my $cRef ( @{$archSet->{arch}} ) {
		push @a, $cRef->{_content};
	    }
	    $h{archList} = \@a;
	    $archSets{$archSet->{name}} = \%h;
	}
    }
    # print Dumper %archSets;
}

sub createArchitectures( $ )
{
    my ($archSetList) = @_;

    my $re = {};

    my @archs;
    my %reqArchs;

    my %archMatrix;

    foreach my $requiredArch( @{$archSetList} ) {
	my $ref = $requiredArch->{ref};
	unless( $archSets{$ref} ) {
	    print "ERROR: No such archset $requiredArch\n";
	    next;
	}
	my @archis = @{ $archSets{$ref}->{archList} };
	my $border = @archis; # the amount of entries
	
	print "WARN: last arch in archset must be noarch\n" unless( $archis[$border-1] eq "noarch" );

	$reqArchs{ $archSets{$ref}->{productarch} } = 1; # will be requiredarch in kiwi
	
	for( my $i = 0; $i < $border; $i++ ) {
	    $archMatrix{ $archis[$i] } = { fallback => $archis[$i+1] };
	}
    }
    foreach my $arch ( sort keys %archMatrix ) {
	my %h;
	$h{id} = $arch;
	if( $archMatrix{$arch}->{name} ) {
	    $h{name} = $archMatrix{$arch}->{name};
	} else {
	    $h{name} = "dummy"; # FIXME: should become optional
	};
	$h{fallback} = $archMatrix{$arch}->{fallback} if( $archMatrix{$arch}->{fallback});
	push @archs, \%h;
    }
    
    my @reqXml;

    foreach ( sort keys %reqArchs ) {
	my %h;
	$h{ref} = $_;
	push @reqXml, \%h;
    }
    $re->{arch} = \@archs;
    $re->{requiredarch} = \@reqXml;
    
    return $re;
}

sub createProductOptions($$$)
{
    my( $prodRef, $medium, $archSetList ) = @_;

    # General FIXME: this works only with a single product on media.
    my $product = $prodRef->{products}{product}[0];
    die( "Handling of multiple products on one media is not specified !\n" ) if $prodRef->{products}{product}[1];

    my $re = {};
    my %varsH;
    # we need the "default" arch for 
    # - MEDIUM_NAME
    # - releasenotesurl
    my $arch="i586";
    foreach my $ar ( @$archSetList ) {
        $arch=$archSets{$ar->{ref}}->{productarch} if ($archSets{$ar->{ref}});
    }

    $varsH{PRODUCT_THEME} = $product->{'buildconfig'}->{'producttheme'};
    $varsH{MEDIUM_NAME} = $medium->{'name'}."-$arch";
    $varsH{MULTIPLE_MEDIA} = "no";
    $varsH{MULTIPLE_MEDIA} = "yes" if (defined($medium->{'sourcemedia'}) && $medium->{'sourcemedia'} > 1);
    $varsH{MULTIPLE_MEDIA} = "yes" if (defined($medium->{'debugmedia'}) && $medium->{'debugmedia'} > 1);
    $varsH{SHA1OPT} = "-x";

    $varsH{VENDOR} = $product->{'vendor'};
    $varsH{DISTNAME} = $product->{'name'};
    $varsH{VERSION} = $product->{'version'};
    $varsH{FLAVOR} = $medium->{'flavor'};
    $varsH{RELEASE} = "0";
    $varsH{PRODUCT_DIR} = "/";
    $varsH{PRODUCT_NAME} = '$DISTNAME-$FLAVOR';
    $varsH{PRODUCT_VERSION} = '$VERSION';

    my @vars;
    foreach my $opt ( sort keys %varsH ) {
	push @vars, { name => $opt, _content => $varsH{$opt} };
    }

    $re->{productvar} = \@vars;

    my %options;
    if (defined($medium->{'run_media_check'})) {
      $options{'RUN_MEDIA_CHECK'} = $medium->{'run_media_check'};
    };
    if (defined($medium->{'sourcemedia'})) {
      $options{'SOURCEMEDIUM'} = $medium->{'sourcemedia'};
    };
    if (defined($medium->{'debugmedia'})) {
      $options{'DEBUGMEDIUM'} = $medium->{'debugmedia'};
    };
    
    my $mediaStyle = "suse-11.1"; # fallback value
    $mediaStyle = $medium->{'mediastyle'} if (defined($medium->{'mediastyle'}));
    $options{'PLUGIN_DIR'} = "/usr/share/kiwi/modules/plugins/$mediaStyle";
    $options{'INI_DIR'} = "/usr/share/kiwi/modules/plugins/$mediaStyle";
    $options{'BETA_VERSION'} = $product->{'buildconfig'}->{'betaversion'} if (defined($product->{'buildconfig'}->{'betaversion'}));
    # add more as needed.
    my @vars1;
    foreach my $opt ( sort keys %options ) {
	push @vars1, { name => $opt, _content => $options{$opt} };
    }

    $re->{productoption} = \@vars1;

    my %info;
    $info{'VENDOR'}       = $product->{'vendor'};
    $info{'NAME'}         = $product->{'name'};
    my $rp = $product->{'installconfig'}->{'releasepackage'};
    $info{'REFERENCES'}   = "$rp->{'name'} ".convertFlags($rp->{'flag'})." $rp->{'version'}";
    $info{'VERSION'}      = $product->{'version'};
    $info{'SP_VERSION'}   = $product->{'patchlevel'} if (defined($product->{'patchlevel'}));
    $info{'DISTRIBUTION'} = $product->{'installconfig'}->{'distribution'};
    $info{'FLAVOR'}       = $medium->{'flavor'};
    $info{'DESCRDIR'}     = $product->{'installconfig'}->{'descriptiondir'};
    $info{'DATADIR'}      = $product->{'installconfig'}->{'datadir'};
    foreach my $summary ( @{$product->{'summary'}} ){
      $info{'SUMMARY'}      = $summary->{'_content'} if ( ! $summary->{'language'} );
      $info{'LABEL'}        = $summary->{'_content'}." ".$product->{'version'} if ( ! $summary->{'language'} );
    }
    foreach my $url ( @{$product->{'urls'}->{'url'}} ){
        if ( $url->{'name'} eq 'repository' ){
            $info{'REPO_LOCATION'} = $url->{'_content'};
        }elsif ( $url->{'name'} eq 'releasenotes' ){
            my $relnotesurl = $url->{'_content'};
            $relnotesurl =~ s/%{_target_cpu}/$arch/g;
            $info{'RELNOTESURL'} = $relnotesurl;
        }
    }
    $info{'LINGUAS'} = "";
    foreach my $language ( @{$product->{'linguas'}->{'language'}} ){
      $info{'LINGUAS'} .= "$language->{'_content'} ";
    }
    $info{'BASEARCHS'} = "";
    foreach my $ar ( @$archSetList ) {
        $info{'BASEARCHS'} .= "$archSets{$ar->{ref}}->{productarch} " if( $archSets{$ar->{ref}} );
    }

    # add more ...
    my @info;
    push @info, { name => 'CONTENTSTYLE', _content => '11' }; # Needs to be first !
    foreach my $opt ( sort keys %info) {
	push @info, { name => $opt, _content => $info{$opt} };
    }

    $re->{productinfo} = \@info;
    return $re;
}

sub createMetadata( $$$ )
{
    my( $prodRef, $medium, $archSetList ) = @_;
    
    return undef unless( $medium->{'metadata'} );
    my $re = {};

    # print "> " . Dumper $medium->{metadata};

    my @packages;
    my @files;
    my $onlyarch;
    my $removearch;
    foreach my $requiredArch( @{$archSetList} ) {
	my $ref = $requiredArch->{ref};
	unless( $archSets{$ref} ) {
	    print "ERROR: No such archset $requiredArch\n";
	    next;
	}
        $onlyarch .= "$archSets{$ref}->{productarch},";
    };
    my $metadata_medium = "0";
#    $metadata_medium = "1" if (defined($medium->{'sourcemedia'}) && $medium->{'sourcemedia'} > 1);
    foreach my $pack ( @{ $medium->{metadata}->{package} } ) {
        if ($pack->{removearch}){
          next if containsMyArch( $prodRef, $archSetList, $pack->{removearch} );
          $removearch = "$pack->{removearch},src,nosrc";
        }else{
          $removearch = "src,nosrc";
        }
	push @packages, { name => $pack->{name}, 
			  medium => $metadata_medium, 
			  removearch => $removearch,
			  onlyarch => $onlyarch };
    }

    my @a;
    return { repopackage => \@packages };
    
#     my @files;
#     foreach my $file ( @{ $medium->{metadata}->{file} } ) {
# 	push @files, { name => $file->{name} };
#     }
#     # push @a, { file => \@files }; CHECK: Needed?
# 
#     return \@a;
}

sub containsMyArch( $$$ )
{
    my ($prodRef, $archSetList, $archList ) = @_;

    foreach my $s( split( /\s*,\s*/, $archList ) ){
      foreach my $requiredArch( @{$archSetList} ) {
	my $ref = $requiredArch->{ref};
	unless( $archSets{$ref} ) {
	    print "ERROR: No such archset $requiredArch\n";
	    next;
	}
        return 1 if ( $s eq $archSets{$ref}->{productarch} );
      }
    }
    return 0;
}

sub useToPackages( $$$ )
{
    my ($prodRef, $medium, $archSetList ) = @_;

    return unless $medium;

    my @packages;

    if (defined($medium->{use_undecided}) && $medium->{use_undecided} eq "true" ) {
       # Simply take all packages ?
       push @packages, { name => "*" };
    };

    return unless $medium->{use};
    my @useStatements = @{$medium->{use} };

    # print "Use Required: <$useRequired>, Suggested: <$useSuggested>, Recommended: <$useRecommended>\n";

    foreach my $useState ( @useStatements ) {
        my $useRequired = ($medium->{use_required} eq "true" );
        my $useSuggested = ($medium->{use_suggested} eq "true" );
        my $useRecommended = ($medium->{use_recommended} eq "true" );

        $useRequired    = $useState->{'use_required'} if ( defined($useState->{'use_required'}) );
        $useRecommended = $useState->{'use_recommended'} if ( defined($useState->{'use_recommended'}) );
        $useSuggested   = $useState->{'use_suggested'} if ( defined($useState->{'use_suggested'}) );
    
	if( $useState->{group} ) {
#	    print "Handling use of group $useState->{group}\n";
	    push @packages, groupToPackages( $prodRef, $archSetList,
					     $useState->{group}, 
					     $useRequired, 
					     $useRecommended,
					     $useSuggested );
	    # there might be additional packages listed in the group.
	    if( $useState->{package} ) {
		foreach my $addPack ( @{$useState->{package} } ) {
		    # print Dumper( $addPack ) . "\n";
		    my $relType = $addPack->{relationship};
		    unless( $relType eq "requires" || $relType eq "recommends" || $relType eq "suggests" ) {
			print "ERROR: Unknown relation type string for package add!\n";
			exit;
		    }
		    if( ( $useRequired    && $addPack->{relationship} eq "requires") ||
			( $useRecommended && $addPack->{relationship} eq "recommends" ) ||
			( $useSuggested   && $addPack->{relationship} eq "suggests" ) ) {

			my %tmp;
                        $tmp{name} = $addPack->{name};
                        $tmp{medium} = $addPack->{medium} if (defined($addPack->{medium}));
                        if ($addPack->{removearch}) {
                          next if containsMyArch( $prodRef, $archSetList, $addPack->{removearch} );
                          $tmp{removearch} = $addPack->{removearch};
                        }
			push @packages, \%tmp;
		    }
		}
	    }
	} elsif( $useState->{pattern} ) {
	    print "ERROR: Patterns are not supported for repopackages!\n";

	}
    }
    return \@packages;
}

sub groupToPackages( $$$$$ ) 
{
    my ($prodRef, $archSetList, $group, $useReq, $useRec, $useSug ) = @_;

    # generate the list of current architectures out of the archSetList
    # FIXME: In all product configs I saw so far, there is only one entry 
    # in the archsetlist.
    # What does it mean if there are more? The following code takes all
    # and allows all.
    my @validArchs;
    foreach my $archHashRef (@$archSetList) {
	my $archSetRef = $archSets{$archHashRef->{ref}};
	push @validArchs, $archSetRef->{productarch};
    }

    my @groups = @{$prodRef->{group}}; 
    my $groupRef;

    # search for the group we should convert here.
    foreach my $gl( @groups ) {
	if( $gl->{name} eq $group ) {
	    $groupRef = $gl;
	    last;
	}
    }

    unless( $groupRef ) {
	print "ERROR: Group <$group> not found!\n";
	return ();
    }
    
    unless( $groupRef->{packagelist} ) {
	print "ERROR: Group <$group> has no package lists!\n";
	return;
    }

    # ok, here we have a valid group reference.
#    print " * resolving group <$groupRef->{name}>\n";
    my @packagelists = @{$groupRef->{packagelist}};

    my %conditionTemplate;
    foreach my $condList( @{$groupRef->{conditional} } ) {
#	print "Handling group conditional $condList->{name}\n";
	my $cond = $conditionals{ $condList->{name} };

	if( $cond->{platform} ) {
	    my @platforms = @{$cond->{platform}};

	    # the condition only becomes a template
	    foreach my $p ( @platforms ) {
		my @condArchs;
		@condArchs  = split( /\s*,\s*/, $p->{arch} ) if( $p->{arch} );
		# 
		my $takeIt = 1; # Take all condition tags if no arch-tag is there
		if( $p->{arch} ) {
		    $takeIt = 0;
		    foreach my $validArch( @validArchs ) {
			if( grep( /$validArch/, @condArchs ) ) {
			    $takeIt = 1;
			    last;
			}
		    }
		}
		if( $takeIt ) {
		    %conditionTemplate = (%conditionTemplate, %{$p});
		} else {
		    # This condition does not match, so drop it
		}
	    }
	}
    }

    # Drop this group, if condition(s) exist for it, but none matches for this platform
    return () if ( @{$groupRef->{conditional}} > 0 && !keys %conditionTemplate );
 
    my $useFlags = { requires => $useReq || 0, recommends => $useRec || 0, suggests => $useSug || 0 };

    my @resultList;

    foreach my $packList ( @packagelists ) {
	my $relation = $packList->{relationship} || 'requires';
	# print "Relation: $relation\n";
	if( $useFlags->{$relation} && $packList->{package} ) {
	    # parse the package in 
	    my @packs = @{$packList->{package}};
	    foreach my $pack ( @packs ) {
		my %h = %conditionTemplate;
                my $takeIt = 1;

                $takeIt = 0 unless $pack->{conditional};

		# print Dumper $pack;
		foreach my $condList( @{$pack->{conditional} } ) {
		    my $name = $condList->{name};
		    my $cond = $conditionals{$name};
                    next unless defined $h{$name};
                    $takeIt = 1;
		    print "Handling package conditional $name\n";
		    # print Dumper "Conditional: ". $cond . "\n";
		    
		    if( $cond->{platform} ) {
			my @platforms = @{$cond->{platform}};
			foreach my $p ( @platforms ) {
			    %h= (%h, %{$p});
			}
		    }
		    if( $cond->{media} ) {
			$h{medium} = $cond->{media}->{number};
		    }
		}
		$h{name} = $pack->{name};
		push @resultList, \%h;
	    }
	}

    }

    return @resultList;
}

#
# This sub expands the patterns 
sub expandPackages( $ )
{
    my ($groupRef) = @_;
    
    my $name = $groupRef->{name};

    print "Working on group $name\n";
    
    my @patterns = @{$groupRef->{pattern}};
    
    my $pat = @{$groupRef->{pattern}}[0];
    $groupRef->{_pattern} = $pat;


    foreach my $pack ( @{$groupRef->{group}} ) {
	my $packListRef = $pack->{package};
	my $relation = $pack->{relationship};
	my @resultPacks;
	foreach my $packRef ( @${packListRef} ) {
	    # print "Pushing $packRef->{name}\n";
	    my %packValues;
	    $packValues{name} = $packRef->{name};
	    if( $groupRef->{platform} ) {
		# forcerepo??
		foreach my $tag ('forcearch', 'addarch', 'onlyarch', 'removearch', 'source', 'script', 'medium' ) {
		    $packValues{$tag} = $groupRef->{platform}->{$tag} if( $groupRef->{platform}->{$tag} );
		}
	    }

	    push @resultPacks, \%packValues;
	}
	my $keyname = "_" . lc $relation;
	print "Keyname of package list: $keyname\n";
	$groupRef->{$keyname} = \@resultPacks;
    }
}

#
# Creation of the instsource part of the kiwi file
#
# note that the product spec contains a list of archsets. For each of these archsets and 
# for each of the media must be a separate kiwi file.
#
# 1. parameter: the reference on the product datastructure
# 2. parameter: the reference on the current media datastructure
# 3. parameter: list of the archs for this kiwi file.
#
sub createInstsource( $$$ )
{
    my( $prodRef, $medium, $archSetList ) = @_;
    my $re = {};

    $re->{architectures} = createArchitectures( $archSetList );
    $re->{productoptions} = createProductOptions( $prodRef, $medium, $archSetList );
    
    my @r;
    my $count = 0;
    foreach my $repo ( @{$prodRef->{repositories}{repository} } ) {
	my %h;
        my $localpath;
        $count = $count + 1;
	$h{name} = $repo->{'name'};
	$h{priority} = $count;
        $localpath = $repo->{path};
        if ($repo->{path} =~ /^obs:\/\/(.+)$/ ) {
	  $h{local} = "true";
          $localpath = $1;
        } else {
          warn( "ERROR: Non obs:// url as repository: $repo->{path} !\n" );
        };
        if ( $runOnServer ) {
          for my $arch ( @{$re->{architectures}->{requiredarch}} ) {
  	     my $path = "$bsdir/build/$localpath/$arch->{'ref'}/:full";
  	     $h{source} = { path => $path };
	     push @r, \%h;
             print "WARNING: local path $path does not exist !\n" if ( ! -e "$path" );
          };
        }else{
	  $h{source} = { path => $repo->{path} };
	  push @r, \%h;
        };
    }
    $re->{instrepo} = \@r;
    
    # metadata, media dependant
    my $ref = createMetadata( $prodRef, $medium, $archSetList );
    if( $ref ) {
	$re->{metadata} = createMetadata( $prodRef, $medium, $archSetList );
    }

    # repopackages
    my @packages;
    my $useToPacks = useToPackages( $prodRef, $medium, $archSetList );

    if( $useToPacks ) {
	push @packages, { repopackage => $useToPacks };
    }
    # print "Packlist: " . Dumper \@packages;
    $re->{repopackages} =  \@packages;

    return $re;
}

sub createRepository
{
    # This is for a dummy entry, it is required by the kiwi DTD, but not used
    # for installation medias.
    my( $prodRef ) = @_;
    my @repository;
    my $source;
    my $dummydir = "/var/lib/empty";

    # Do we have ever a different repo type than "yast" on products ?
    $source->{ 'path' } = $dummydir;
    push @repository, { 'type' => 'yast2', 'source' => $source };

    return \@repository;
}

sub writeProductSPECfile
{
    my( $file, $infile, $prodRef, $product ) = @_;

    my $product_flavors="";
    foreach my $flavor ( @{$prodRef->{mediasets}->{media}} ){
      next if ((!defined ($flavor->{'flavor'}) || ("$flavor->{'flavor'}" eq "")));
      $product_flavors.="%package $flavor->{flavor}\n";
      $product_flavors.="License:        BSD 3-Clause\n";
      $product_flavors.="Group:          System/Fhs\n";
      $product_flavors.="Provides:       product_flavor()\n";
      $product_flavors.="Provides:       product_flavor($product->{name}) = $product->{'version'}-$product->{'release'}\n";
      $product_flavors.="Provides:       flavor($flavor->{flavor})\n";
      foreach my $summary ( @{$product->{'summary'}} ){
        $product_flavors.="Summary:        $summary->{_content}\n" if ( ! $summary->{'language'} );
      }
      $product_flavors.="\n";
      $product_flavors.="%description $flavor->{flavor}\n";
      foreach my $description ( @{$product->{'description'}} ){
        $product_flavors.="$description->{_content}\n" if ( ! $description->{'description'} );
      }
      $product_flavors.="\n";
      $product_flavors.="%files $flavor->{flavor}\n";
      $product_flavors.="%defattr(-,root,root)\n";
      $product_flavors.="%doc %{_defaultdocdir}/$product->{name}-release-$flavor->{flavor}\n";
      $product_flavors.="\n"
    }

    # Create product file to be packaged
    my $zypp_product_file = "";
    $zypp_product_file = "mkdir -p \$RPM_BUILD_ROOT/etc/products.d\n";
    my $pfile = "\$RPM_BUILD_ROOT/etc/products.d/$product->{name}.prod";

    my $zypp_product;
    $zypp_product = $product;
    $zypp_product->{'arch'} = '%{_target_cpu}'; # write product architecture during rpm build
    $zypp_product->{'schemeversion'} = "0"; # FIXME: moving target, still in development.
    my $d;
    $d->{"target"}  = $product->{'register'}->{'target'};
    $d->{"release"} = $product->{'register'}->{'release'};
    $zypp_product->{'register'} = $d;
    my $xml = XMLout( $BSProductXML::product, $zypp_product );
    die ( "ERROR: Unable to create xml for $product->{name} !" ) unless $xml;
    $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n$xml";
    $zypp_product_file .= "cat >$pfile << EOF\n";
    $zypp_product_file .= "$xml\nEOF\n\n";

    foreach my $flavor ( @{$prodRef->{mediasets}->{media}} ){
      next if ((!defined ($flavor->{'flavor'}) || ("$flavor->{'flavor'}" eq "")));
      my $readmedir = "\$RPM_BUILD_ROOT/%{_defaultdocdir}/$product->{name}-release-$flavor->{flavor}";
      $zypp_product_file .= "mkdir -p $readmedir\n";
      $zypp_product_file .= "cat >$readmedir/README << EOF\n";
      $zypp_product_file .= "This package just exist just for providing the product flavor.\n";
      $zypp_product_file .= "\nEOF\n\n";
    }

    my $str = readstr($infile);

    # replace all strings
    $str =~ s/___VERSION___/$product->{version}/g;
    $str =~ s/___BETA_VERSION___/$product->{buildconfig}->{betaversion}/g;
    $str =~ s/___RELEASE___/0/g;
    $str =~ s/___PACKAGE_NAME___/$product->{name}-release/g;
    $str =~ s/___PRODUCT_NAME___/$product->{name}/g;
    foreach my $flavor ( @{$prodRef->{mediasets}->{media}} ){
      $str =~ s/___PRODUCT_REQUIRES___/$product_requires{$flavor}/g;
    }
    $str =~ s/___SUMMARY___/$product->{summary}[0]->{_content}/g; # FIXME: find the non-lang one
    $str =~ s/___DESCRIPTION___/$product->{description}[0]->{_content}/g; # FIXME: find the non-lang one
    $str =~ s/___FLAVOR_PACKAGES___/$product_flavors/g;
    $str =~ s/___CREATE_PRODUCT_FILES___/$zypp_product_file/g;

    # write out the modified file.
    writestr($file, undef, $str);
}

# Process the commandline arguments.
getopts('dlhm:');

usage() if $opt_h;
$runOnServer = "true" if $opt_l;

my ($infile, $outdir, $project) = @ARGV;

die( "Please specify input file, output directory and project name\n" ) unless $infile;
die( "Please specify output directory and project name\n" ) unless $outdir;

my $d;
# global indir
($d, $indir) = fileparse( $infile );

my $prodRef = readProductFile( $infile );

#
# Sanity checks
#
die("product definition contains no products\n") unless $prodRef->{'products'};
for my $product (@{$prodRef->{'products'}->{'product'} || []}) {
  die("no product name set\n") unless $product->{'name'};
  die("illegal product name: $product->{'name'}\n") if $product->{'name'} =~ /^[_\.]/;
  die("illegal product name: $product->{'name'}\n") if $product->{'name'} =~ /[\/\000-\037]/;
}

#
# Create a kiwi configuration for each distribution flavor
#

my $productRef = $prodRef->{products}->{product}->[0]; # FIXME: Support multiple products.

my $kiwiImage = {};
$kiwiImage->{schemeversion} = "2.4"; # ???
my $name = sprintf( "%s %s %s, Rel. %s",
		    $productRef->{vendor},
		    $productRef->{name},
		    $productRef->{version},
		    $productRef->{release} );

$kiwiImage->{name} = $name;

$kiwiImage->{description} = createDescription( $productRef );
$kiwiImage->{preferences} = createPreferences( $productRef );
# so far for all media types identical. Now loop over the media types
# to create media type specific versions;

parseConditionals( $prodRef->{conditionals}->{conditional} );
parseRepositories( $prodRef->{repositories}->{repository} );
parseArchsets( $prodRef->{archsets}{archset} );

#########

my %generalImage = %{$kiwiImage};

my $media = $prodRef->{mediasets}->{media};

if( $opt_m ) {
    print "Generating only media set $opt_m, due to commandline switch\n";
}

foreach my $medium ( @$media ){
    my $type = $medium->{type};
    my $flavor = $medium->{flavor};
    my $product = $medium->{product};  # note: this needs to reference a product from the products section 
    my $name = $medium->{name};
   
    next if( $opt_m && $name ne $opt_m );

    # create one kiwi file each for every of the archsets
    if ( defined(@{$medium->{archsets}}) ) {
      my @archSets = @{$medium->{archsets}};
      foreach my $arch ( @archSets ) {
          my $buildflags;
          my @archs;
          $buildflags->{'disable'} = [{}]; # disabled by default
      
          my $kiwi = \%generalImage;
      
          $kiwi->{instsource} = createInstsource ( $prodRef, $medium, $arch->{archset} );
          $kiwi->{repository} = createRepository ( $prodRef );
      
          my $archStr;
          my @archsets = @{$arch->{archset}};
          foreach my $ar ( @archsets ) {
              if( $archSets{$ar->{'ref'}} ) {
          	my $architecture = "$archSets{$ar->{'ref'}}->{'productarch'}";
          	$archStr .= "_" if $archStr;
          	$archStr .= "$architecture";
                  # enable this architecture in scheduler
                  # FIXME: the scheduler arch may have a different name than the rpm archs !
                  push @archs, { 'arch' => $architecture };
                  # Heuristic: we need to make this configurable at a more central place
                  push @archs, { 'arch' => 'i586' } if ( $architecture eq "x86_64" );
                  push @archs, { 'arch' => 'i586' } if ( $architecture eq "ia64" );
                  push @archs, { 'arch' => 'ppc' } if ( $architecture eq "ppc64" );
                  push @archs, { 'arch' => 'ppc64' } if ( $architecture eq "ppc" ); # ppc is using ppc64 stuff in openSUSE
                  push @archs, { 'arch' => 's390' } if ( $architecture eq "s390x" );
              }
          }
          $buildflags->{'enable'} = \@archs;
      
          my $file = "$product-$type-$flavor-$archStr";
          die("illegal kiwi product: $file\n") if $file =~ /^[_\.]/;
          die("illegal kiwi product: $file\n") if $file =~ /[\/\000-\037]/;
      
          my $pkgName = "_product:$file";
          my $kiwiDir = "$outdir/$pkgName/";
          my $outFile = "$kiwiDir/$file.kiwi";
          my $metaFile = "$kiwiDir/_meta";
      
          mkdir_p( $kiwiDir ) || die ("Unable to create $kiwiDir\n");
          writexml( "$outFile$$", $outFile, $kiwi, $BSKiwiXML::kiwidesc );
          print "$outFile written.\n";
      
          # Create meta file to enable it only for needed architectures
          if ( $project ) {
            my $pkgmeta;
            $pkgmeta->{'name'} = $pkgName;
            $pkgmeta->{'project'} = $project;
            $pkgmeta->{'title'} = "KIWI image build" ;
            $pkgmeta->{'description'} = "Automatically generate from _product" ;
            $pkgmeta->{'build'} = $buildflags;
            writexml( "$metaFile$$", $metaFile, $pkgmeta, $BSXML::pack );
#            print "metafile written.\n";
          } else {
            print "metafile SKIPPED ! (need project name argument for it)\n";
          }
      }
    }
}


#
# Create $product-release packages
#

for my $product (@{$prodRef->{'products'}->{'product'} || []}) {
  my $templateFile;
  if ($infile =~ /(.*\/)(.+)$/) {
    $templateFile = "$1/$product->{name}-release.spec";
  };
  if ( !$templateFile || ! -e $templateFile ) {
    if ($infile =~ /(.*\/)(.+)$/) {
      $templateFile = "$1/release.spec";
    };
  };
  if ( $templateFile && -e $templateFile ) {
    mkdir_p( "$outdir/_product:$product->{name}-release" ) || die ("Unable to create $outdir\n");
    writeProductSPECfile( "$outdir/_product:$product->{name}-release/$product->{name}-release.spec", $templateFile, $prodRef, $product );
  } else {
    print "No release template file $templateFile exists --> SPEC file generation skipped !\n";
  };
};




# end
openSUSE Build Service is sponsored by