Skip to content

Commit

Permalink
Add modulemd file writer tool
Browse files Browse the repository at this point in the history
This takes a modulemd stream file and adds the information for
every built rpm.
  • Loading branch information
mlschroe committed Aug 2, 2021
1 parent ae78040 commit 2c2b17a
Show file tree
Hide file tree
Showing 5 changed files with 324 additions and 0 deletions.
3 changes: 3 additions & 0 deletions Build/Rpm.pm
Expand Up @@ -842,6 +842,7 @@ my %rpmstag = (
"SUMMARY" => 1004,
"DESCRIPTION" => 1005,
"BUILDTIME" => 1006,
"LICENSE" => 1014,
"ARCH" => 1022,
"OLDFILENAMES" => 1027,
"SOURCERPM" => 1044,
Expand Down Expand Up @@ -1167,6 +1168,7 @@ sub query {
push @tags, qw{SUMMARY DESCRIPTION} if $opts{'description'};
push @tags, qw{DISTURL} if $opts{'disturl'};
push @tags, qw{BUILDTIME} if $opts{'buildtime'};
push @tags, qw{LICENSE} if $opts{'license'};
push @tags, qw{CONFLICTNAME CONFLICTVERSION CONFLICTFLAGS OBSOLETENAME OBSOLETEVERSION OBSOLETEFLAGS} if $opts{'conflicts'};
push @tags, qw{RECOMMENDNAME RECOMMENDVERSION RECOMMENDFLAGS SUGGESTNAME SUGGESTVERSION SUGGESTFLAGS SUPPLEMENTNAME SUPPLEMENTVERSION SUPPLEMENTFLAGS ENHANCENAME ENHANCEVERSION ENHANCEFLAGS OLDSUGGESTSNAME OLDSUGGESTSVERSION OLDSUGGESTSFLAGS OLDENHANCESNAME OLDENHANCESVERSION OLDENHANCESFLAGS} if $opts{'weakdeps'};

Expand Down Expand Up @@ -1248,6 +1250,7 @@ sub query {
}
$data->{'buildtime'} = $res{'BUILDTIME'}->[0] if $opts{'buildtime'};
$data->{'disturl'} = $res{'DISTURL'}->[0] if $opts{'disturl'} && $res{'DISTURL'};
$data->{'license'} = $res{'LICENSE'}->[0] if $opts{'license'} && $res{'LICENSE'};
return $data;
}

Expand Down
207 changes: 207 additions & 0 deletions Build/SimpleYAML.pm
@@ -0,0 +1,207 @@
################################################################
#
# Copyright (c) 2021 SUSE Linux Products GmbH
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or 3 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
#
################################################################

package Build::SimpleYAML;

use strict;

use Scalar::Util;

sub unparse_keys {
my ($d) = @_;
my @k = grep {$_ ne '_start' && $_ ne '_end' && $_ ne '_order' && $_ ne '_type'} sort keys %$d;
return @k unless $d->{'_order'};
my %k = map {$_ => 1} @k;
my @ko;
for (@{$d->{'_order'}}) {
push @ko, $_ if delete $k{$_};
}
return (@ko, grep {$k{$_}} @k);
}

my %specialescapes = (
"\0" => '\\0',
"\a" => '\\a',
"\b" => '\\b',
"\t" => '\\t',
"\n" => '\\n',
"\013" => '\\v',
"\f" => '\\f',
"\r" => '\\r',
"\e" => '\\e',
"\x85" => '\\N',
);

sub unparse_string {
my ($d, $inline) = @_;
return "''" unless length $d;
return "\"$d\"" if Scalar::Util::looks_like_number($d);
if ($d =~ /[\x00-\x1f\x7f-\x9f\']/) {
$d =~ s/\\/\\\\/g;
$d =~ s/\"/\\\"/g;
$d =~ s/([\x00-\x1f\x7f-\x9f])/$specialescapes{$1} || '\x'.sprintf("%X",ord($1))/ge;
return "\"$d\"";
} elsif ($d =~ /^[\!\&*{}[]|>@`"'#%, ]/s) {
return "'$d'";
} elsif ($inline && $d =~ /[,\[\]\{\}]/) {
return "'$d'";
} elsif ($d =~ /: / || $d =~ / #/ || $d =~ /[: \t]\z/) {
return "'$d'";
} elsif ($d eq '~' || $d eq 'null' || $d eq 'true' || $d eq 'false' && $d =~ /^(?:---|\.\.\.)/s) {
return "'$d'";
} elsif ($d =~ /^[-?:](?:\s|\z)/s) {
return "'$d'";
} else {
return $d;
}
}

sub unparse_literal {
my ($d, $indent) = @_;
return unparse_string($d) if !defined($d) || $d eq '' || $d =~ /[\x00-\x09\x0b-\x1f\x7f-\x9f]/;
my @lines = split("\n", $d, -1);
return "''" unless @lines;
my $r = '|';
my @nonempty = grep {$_ ne ''} @lines;
$r .= '2' if @nonempty && $nonempty[0] =~ /^ /;
if ($lines[-1] ne '') {
$r .= '-';
} else {
pop @lines;
$r .= '+' if @lines && $lines[-1] eq '';
}
$r .= $_ ne '' ? "\n$indent$_" : "\n" for @lines;
return $r;
}

sub unparse_folded {
my ($d, $indent) = @_;
return unparse_string($d) if !defined($d) || $d eq '' || $d =~ /[\x00-\x09\x0b-\x1f\x7f-\x9f]/;
my @lines = split("\n", $d, -1);
return "''" unless @lines;
my $r = '>';
my @nonempty = grep {$_ ne ''} @lines;
$r .= '2' if @nonempty && $nonempty[0] =~ /^ /;
if ($lines[-1] ne '') {
$r .= '-';
} else {
pop @lines;
$r .= '+' if @lines && $lines[-1] eq '';
}
my $neednl;
my $ll = 78 - length($indent);
$ll = 40 if $ll < 40;
for (splice(@lines)) {
if ($_ =~ /^ /) {
push @lines, $_;
$neednl = 0;
next;
}
push @lines, '' if $neednl;
while (length($_) > $ll && (/^(.{1,$ll}[^ ]) [^ ]/s || /^(..*?[^ ]) [^ ]/s)) {
push @lines, $1;
$_ = substr($_, length($1) + 1);
}
push @lines, $_;
$neednl = 1;
}
$r .= $_ ne '' ? "\n$indent$_" : "\n" for @lines;
return $r;
}


sub unparse_bool {
my ($d) = @_;
return $d ? 'true' : 'false';
}

sub unparse_number {
my ($d) = @_;
return sprintf("%.f", $d) if $d == int($d);
return sprintf("%g", $d);
}

sub unparse {
my ($d, %opts) = @_;

return "---\n".unparse($d, %opts, 'noheader' => 1)."\n...\n" unless $opts{'noheader'};
my $r = '';
if (ref($d) eq 'ARRAY') {
return '[]' unless @$d;
$opts{'inline'} = 1 if $opts{'_type'} && $opts{'_type'} =~ s/^inline_?//;
if ($opts{'inline'}) {
my $first = 0;
for my $dd (@$d) {
$r .= ", " if $first++;
$r .= unparse($dd, %opts);
}
return "\[$r\]";
}
my $indent = $opts{'indent'} || '';
my $first = 0;
for my $dd (@$d) {
$r .= "\n$indent" if $first++;
$r .= "- ".unparse($dd, %opts, 'indent' => " $indent");
}
return $r;
}
if (ref($d) eq 'HASH') {
my @k = unparse_keys($d);
return '{}' unless @k;
$opts{'inline'} = 1 if $opts{'_type'} && $opts{'_type'} =~ s/^inline_?//;
if ($opts{'inline'}) {
my $first = 0;
for my $k (@k) {
$r .= ", " if $first++;
my $dd = $d->{$k};
my $type = ($d->{'_type'} || {})->{$k};
$r .= unparse_string($k).": ".unparse($dd, %opts, '_type' => $type);
}
return "\{$r\}";
}
my $indent = $opts{'indent'} || '';
my $first = 0;
for my $k (@k) {
my $dd = $d->{$k};
my $type = ($d->{'_type'} || {})->{$k} || ($d->{'_type'} || {})->{'*'};
$r .= "\n$indent" if $first++;
$r .= unparse_string($k).":";
if (($type && $type =~ /^inline_?/) || (ref($dd) ne 'ARRAY' && ref($dd) ne 'HASH')) {
$r .= " ".unparse($dd, %opts, 'indent' => " $indent", '_type' => $type);
} elsif (ref($dd) eq 'HASH') {
$r .= "\n$indent ";
$r .= unparse($dd, %opts, 'indent' => " $indent", '_type' => $type);
} elsif (ref($dd) eq 'ARRAY') {
$r .= "\n$indent";
$r .= unparse($dd, %opts, 'indent' => "$indent", '_type' => $type);
}
}
return $r;
}
my $type = $opts{'_type'} || '';
return '~' unless defined $d;
return unparse_bool($d) if $type eq 'bool';
return unparse_number($d) if $type eq 'number';
return unparse_literal($d, $opts{'indent'} || '') if $type eq 'literal' && !$opts{'inline'};
return unparse_folded($d, $opts{'indent'} || '') if $type eq 'folded' && !$opts{'inline'};
return unparse_string($d, $opts{'inline'});
}

1;
1 change: 1 addition & 0 deletions Makefile
Expand Up @@ -85,6 +85,7 @@ install:
create_container_package_list \
call-podman \
queryobs \
writemodulemd \
$(DESTDIR)$(pkglibdir)
install -m644 \
qemu-reg \
Expand Down
11 changes: 11 additions & 0 deletions build
Expand Up @@ -1353,6 +1353,7 @@ fi

test "$BUILD_ARCH" = all && BUILD_ARCH=
BUILD_USER_ABUILD_USED=
MODULEMDFILE=

for RECIPEFILE in "${RECIPEFILES[@]}" ; do

Expand All @@ -1379,6 +1380,11 @@ for RECIPEFILE in "${RECIPEFILES[@]}" ; do
fi
MYSRCDIR="$SRCDIR"

# remember the modulemd file if we have one
test -s _modulemd.yml && MODULEMDFILE="$SRCDIR/_modulemd.yml"
test -s _modulemd.yaml && MODULEMDFILE="$SRCDIR/_modulemd.yaml"
test -s _modulemd.pst && MODULEMDFILE="$SRCDIR/_modulemd.pst"

# special hack to build from a .src.rpm (modifies MYSRCDIR)
test "$RECIPEFILE" != "${RECIPEFILE%.src.rpm}" && recipe_unpack_srcrpm

Expand Down Expand Up @@ -1726,6 +1732,11 @@ if test -n "$SCCACHE" ; then
chroot $BUILD_ROOT su -c "sccache -s" - $BUILD_USER
fi

if test -n "$MODULEMDFILE" ; then
mkdir -p "$BUILD_ROOT/$TOPDIR/OTHER"
find "$BUILD_ROOT/$TOPDIR/RPMS" -type f -name "*.rpm" | $BUILD_DIR/writemodulemd "$MODULEMDFILE" - > "$BUILD_ROOT/$TOPDIR/OTHER/_modulemd.yaml"
fi

exitcode=0

# post build work
Expand Down
102 changes: 102 additions & 0 deletions writemodulemd
@@ -0,0 +1,102 @@
#!/usr/bin/perl -w

################################################################
#
# Copyright (c) 2021 SUSE Linux GmbH
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or 3 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
#
################################################################

# obs: repo support

BEGIN {
unshift @INC, ($::ENV{"BUILD_DIR"} || "/usr/lib/build");
}

use Build::Rpm;
use Build::SimpleYAML;

die("usage: writemodulemd <modulemdfile> <rpmmanifestfile>\n") unless @ARGV == 2;
my ($modulemdfile, $manifestfile) = @ARGV;

my $md;
if ($modulemdfile =~ /\.pst$/) {
require Storable;
$md = Storable::retrieve($modulemdfile);
} elsif ($modulemdfile =~ /\.ya?ml$/) {
require YAML::XS;
$YAML::XS::LoadBlessed = $YAML::XS::LoadBlessed = 0;
$md = YAML::XS::LoadFile($modulemdfile);
} else {
die("unsupported modulemd file: $modulemdfile\n");
}
die("bad modulemd data\n") unless $md && ref($md) eq 'HASH' && $md->{'data'};
die("bad modulemd version \n") unless $md->{'version'} == 2;
delete $md->{'data'}->{'artifacts'};
delete $md->{'data'}->{'license'}->{'content'} if $md->{'data'}->{'license'} && $md->{'data'}->{'license'}->{'content'};

if ($manifestfile ne '-') {
open(STDIN, '<', $manifestfile) || die("$manifestfile: $!\n");
}

my %licenses;

while (<STDIN>) {
chomp;
my $r = Build::Rpm::query($_, 'evra' => 1, 'license' => 1);
$r->{'epoch'} ||= 0;
my $nevra = "$r->{'name'}-$r->{'epoch'}:$r->{'version'}-$r->{'release'}.$r->{'arch'}";
my $license = $r->{'license'};
$licenses{$license} = 1;
push @{$md->{'data'}->{'artifacts'}->{'rpms'}}, $nevra;
}
$md->{'data'}->{'license'}->{'content'} = [ sort keys %licenses ] if %licenses;

$md->{'_order'} = [ 'document', 'version', 'data' ];
$md->{'_type'}->{'version'} = 'number';
$md->{'data'}->{'_order'} = [ 'name', 'stream', 'version', 'context', 'arch', 'summary', 'description', 'license', 'xmd', 'dependencies', 'references', 'profiles', 'api', 'filter', 'buildopts', 'components', 'artifacts' ];
$md->{'data'}->{'_type'}->{'version'} = 'number';
if ($md->{'data'} && $md->{'data'}->{'license'}) {
$md->{'data'}->{'license'}->{'_order'} = [ 'module', 'content' ];
}
if ($md->{'data'} && $md->{'data'}->{'components'} && $md->{'data'}->{'components'}->{'rpms'}) {
for (values %{$md->{'data'}->{'components'}->{'rpms'}}) {
$_->{'_order'} = [ 'rationale', 'ref', 'buildorder', 'arches' ];
$_->{'_type'}->{'buildorder'} = 'number';
}
}
if ($md->{'data'} && $md->{'data'}->{'buildopts'} && $md->{'data'}->{'buildopts'}->{'rpms'}) {
$md->{'data'}->{'buildopts'}->{'rpms'}->{'_type'}->{'macros'} = 'literal';
}
$md->{'data'}->{'_type'}->{'description'} = 'folded';
for my $d (@{$md->{'data'}->{'dependencies'} || []}) {
for ($d->{'requires'}, $d->{'buildrequires'}) {
next if !defined($_) || ref($_) eq 'HASH';
my $nd = {};
for my $dd (@$_) {
my ($n, @v) = split(':', $dd);
$nd->{$n} = \@v;
}
$_ = $nd;
}
$d->{'requires'}->{'_type'}->{'*'} = 'inline' if $d->{'requires'};
$d->{'buildrequires'}->{'_type'}->{'*'} = 'inline' if $d->{'buildrequires'};
}

print Build::SimpleYAML::unparse($md);



0 comments on commit 2c2b17a

Please sign in to comment.