b.liu | e958203 | 2025-04-17 19:18:16 +0800 | [diff] [blame] | 1 | #!/usr/bin/env perl |
| 2 | use FindBin; |
| 3 | use lib "$FindBin::Bin"; |
| 4 | use strict; |
| 5 | use metadata; |
| 6 | use Getopt::Long; |
| 7 | use Time::Piece; |
| 8 | use JSON::PP; |
| 9 | |
| 10 | my %board; |
| 11 | |
| 12 | sub version_to_num($) { |
| 13 | my $str = shift; |
| 14 | my $num = 0; |
| 15 | |
| 16 | if (defined($str) && $str =~ /^\d+(?:\.\d+)+$/) |
| 17 | { |
| 18 | my @n = (split(/\./, $str), 0, 0, 0, 0); |
| 19 | $num = ($n[0] << 24) | ($n[1] << 16) | ($n[2] << 8) | $n[3]; |
| 20 | } |
| 21 | |
| 22 | return $num; |
| 23 | } |
| 24 | |
| 25 | sub version_filter_list(@) { |
| 26 | my $cmpver = version_to_num(shift @_); |
| 27 | my @items; |
| 28 | |
| 29 | foreach my $item (@_) |
| 30 | { |
| 31 | if ($item =~ s/@(lt|le|gt|ge|eq|ne)(\d+(?:\.\d+)+)\b//) |
| 32 | { |
| 33 | my $op = $1; |
| 34 | my $symver = version_to_num($2); |
| 35 | |
| 36 | if ($symver > 0 && $cmpver > 0) |
| 37 | { |
| 38 | next unless (($op eq 'lt' && $cmpver < $symver) || |
| 39 | ($op eq 'le' && $cmpver <= $symver) || |
| 40 | ($op eq 'gt' && $cmpver > $symver) || |
| 41 | ($op eq 'ge' && $cmpver >= $symver) || |
| 42 | ($op eq 'eq' && $cmpver == $symver) || |
| 43 | ($op eq 'ne' && $cmpver != $symver)); |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | push @items, $item; |
| 48 | } |
| 49 | |
| 50 | return @items; |
| 51 | } |
| 52 | |
| 53 | sub gen_kconfig_overrides() { |
| 54 | my %config; |
| 55 | my %kconfig; |
| 56 | my $package; |
| 57 | my $pkginfo = shift @ARGV; |
| 58 | my $cfgfile = shift @ARGV; |
| 59 | my $patchver = shift @ARGV; |
| 60 | |
| 61 | # parameter 2: build system config |
| 62 | open FILE, "<$cfgfile" or return; |
| 63 | while (<FILE>) { |
| 64 | /^(CONFIG_.+?)=(.+)$/ and $config{$1} = 1; |
| 65 | } |
| 66 | close FILE; |
| 67 | |
| 68 | # parameter 1: package metadata |
| 69 | open FILE, "<$pkginfo" or return; |
| 70 | while (<FILE>) { |
| 71 | /^Package:\s*(.+?)\s*$/ and $package = $1; |
| 72 | /^Kernel-Config:\s*(.+?)\s*$/ and do { |
| 73 | my @config = split /\s+/, $1; |
| 74 | foreach my $config (version_filter_list($patchver, @config)) { |
| 75 | my $val = 'm'; |
| 76 | my $override; |
| 77 | if ($config =~ /^(.+?)=(.+)$/) { |
| 78 | $config = $1; |
| 79 | $override = 1; |
| 80 | $val = $2; |
| 81 | } |
| 82 | if ($config{"CONFIG_PACKAGE_$package"} and ($config ne 'n')) { |
| 83 | next if $kconfig{$config} eq 'y'; |
| 84 | $kconfig{$config} = $val; |
| 85 | } elsif (!$override) { |
| 86 | $kconfig{$config} or $kconfig{$config} = 'n'; |
| 87 | } |
| 88 | } |
| 89 | }; |
| 90 | }; |
| 91 | close FILE; |
| 92 | |
| 93 | foreach my $kconfig (sort keys %kconfig) { |
| 94 | if ($kconfig{$kconfig} eq 'n') { |
| 95 | print "# $kconfig is not set\n"; |
| 96 | } else { |
| 97 | print "$kconfig=$kconfig{$kconfig}\n"; |
| 98 | } |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | my %dep_check; |
| 103 | sub __find_package_dep($$) { |
| 104 | my $pkg = shift; |
| 105 | my $name = shift; |
| 106 | my $deps = $pkg->{depends}; |
| 107 | |
| 108 | return 0 unless defined $deps; |
| 109 | foreach my $vpkg (@{$deps}) { |
| 110 | foreach my $dep (@{$vpackage{$vpkg}}) { |
| 111 | next if $dep_check{$dep->{name}}; |
| 112 | $dep_check{$dep->{name}} = 1; |
| 113 | return 1 if $dep->{name} eq $name; |
| 114 | return 1 if (__find_package_dep($dep, $name) == 1); |
| 115 | } |
| 116 | } |
| 117 | return 0; |
| 118 | } |
| 119 | |
| 120 | # wrapper to avoid infinite recursion |
| 121 | sub find_package_dep($$) { |
| 122 | my $pkg = shift; |
| 123 | my $name = shift; |
| 124 | |
| 125 | %dep_check = (); |
| 126 | return __find_package_dep($pkg, $name); |
| 127 | } |
| 128 | |
| 129 | sub package_depends($$) { |
| 130 | my $a = shift; |
| 131 | my $b = shift; |
| 132 | my $ret; |
| 133 | |
| 134 | return 0 if ($a->{submenu} ne $b->{submenu}); |
| 135 | if (find_package_dep($a, $b->{name}) == 1) { |
| 136 | $ret = 1; |
| 137 | } elsif (find_package_dep($b, $a->{name}) == 1) { |
| 138 | $ret = -1; |
| 139 | } else { |
| 140 | return 0; |
| 141 | } |
| 142 | return $ret; |
| 143 | } |
| 144 | |
| 145 | sub mconf_depends { |
| 146 | my $pkgname = shift; |
| 147 | my $depends = shift; |
| 148 | my $only_dep = shift; |
| 149 | my $res; |
| 150 | my $dep = shift; |
| 151 | my $seen = shift; |
| 152 | my $parent_condition = shift; |
| 153 | $dep or $dep = {}; |
| 154 | $seen or $seen = {}; |
| 155 | my @t_depends; |
| 156 | |
| 157 | $depends or return; |
| 158 | my @depends = @$depends; |
| 159 | foreach my $depend (@depends) { |
| 160 | my $m = "depends on"; |
| 161 | my $flags = ""; |
| 162 | $depend =~ s/^([@\+]+)// and $flags = $1; |
| 163 | my $condition = $parent_condition; |
| 164 | |
| 165 | $depend = $2 if $depend =~ /^(.+):(.+)$/ and $dep->{$1} eq 'select'; |
| 166 | |
| 167 | next if $condition eq $depend; |
| 168 | next if $seen->{"$parent_condition:$depend"}; |
| 169 | next if $seen->{":$depend"}; |
| 170 | $seen->{"$parent_condition:$depend"} = 1; |
| 171 | if ($depend =~ /^(.+):(.+)$/) { |
| 172 | if ($1 ne "PACKAGE_$pkgname") { |
| 173 | if ($condition) { |
| 174 | $condition = "$condition && $1"; |
| 175 | } else { |
| 176 | $condition = $1; |
| 177 | } |
| 178 | } |
| 179 | $depend = $2; |
| 180 | } |
| 181 | if ($flags =~ /\+/) { |
| 182 | my $vdep = $vpackage{$depend}; |
| 183 | if ($vdep) { |
| 184 | my @vdeps; |
| 185 | |
| 186 | foreach my $v (@$vdep) { |
| 187 | next if $v->{buildonly}; |
| 188 | if ($v->{variant_default}) { |
| 189 | unshift @vdeps, $v->{name}; |
| 190 | } else { |
| 191 | push @vdeps, $v->{name}; |
| 192 | } |
| 193 | } |
| 194 | |
| 195 | $depend = shift @vdeps; |
| 196 | |
| 197 | if (@vdeps > 1) { |
| 198 | $condition = ($condition ? "$condition && " : '') . join("&&", map { "PACKAGE_$_<PACKAGE_$pkgname" } @vdeps); |
| 199 | } elsif (@vdeps > 0) { |
| 200 | $condition = ($condition ? "$condition && " : '') . "PACKAGE_${vdeps[0]}<PACKAGE_$pkgname"; |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | # Menuconfig will not treat 'select FOO' as a real dependency |
| 205 | # thus if FOO depends on other config options, these dependencies |
| 206 | # will not be checked. To fix this, we simply emit all of FOO's |
| 207 | # depends here as well. |
| 208 | $package{$depend} and push @t_depends, [ $package{$depend}->{depends}, $condition ]; |
| 209 | |
| 210 | $m = "select"; |
| 211 | next if $only_dep; |
| 212 | |
| 213 | $flags =~ /@/ or $depend = "PACKAGE_$depend"; |
| 214 | } else { |
| 215 | my $vdep = $vpackage{$depend}; |
| 216 | if ($vdep && @$vdep > 0) { |
| 217 | $depend = join("||", map { "PACKAGE_".$_->{name} } @$vdep); |
| 218 | } else { |
| 219 | $flags =~ /@/ or $depend = "PACKAGE_$depend"; |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | if ($condition) { |
| 224 | if ($m =~ /select/) { |
| 225 | next if $depend eq $condition; |
| 226 | $depend = "$depend if $condition"; |
| 227 | } else { |
| 228 | next if $dep->{"$depend if $condition"}; |
| 229 | $depend = "!($condition) || $depend" unless $dep->{$condition} eq 'select'; |
| 230 | } |
| 231 | } |
| 232 | $dep->{$depend} =~ /select/ or $dep->{$depend} = $m; |
| 233 | } |
| 234 | |
| 235 | foreach my $tdep (@t_depends) { |
| 236 | mconf_depends($pkgname, $tdep->[0], 1, $dep, $seen, $tdep->[1]); |
| 237 | } |
| 238 | |
| 239 | foreach my $depend (sort keys %$dep) { |
| 240 | my $m = $dep->{$depend}; |
| 241 | $res .= "\t\t$m $depend\n"; |
| 242 | } |
| 243 | return $res; |
| 244 | } |
| 245 | |
| 246 | sub mconf_conflicts { |
| 247 | my $pkgname = shift; |
| 248 | my $depends = shift; |
| 249 | my $res = ""; |
| 250 | |
| 251 | foreach my $depend (@$depends) { |
| 252 | next unless $package{$depend}; |
| 253 | $res .= "\t\tdepends on m || (PACKAGE_$depend != y)\n"; |
| 254 | } |
| 255 | return $res; |
| 256 | } |
| 257 | |
| 258 | sub print_package_config_category($) { |
| 259 | my $cat = shift; |
| 260 | my %menus; |
| 261 | my %menu_dep; |
| 262 | |
| 263 | return unless $category{$cat}; |
| 264 | |
| 265 | print "menu \"$cat\"\n\n"; |
| 266 | my %spkg = %{$category{$cat}}; |
| 267 | |
| 268 | foreach my $spkg (sort {uc($a) cmp uc($b)} keys %spkg) { |
| 269 | foreach my $pkg (@{$spkg{$spkg}}) { |
| 270 | next if $pkg->{buildonly}; |
| 271 | my $menu = $pkg->{submenu}; |
| 272 | if ($menu) { |
| 273 | $menu_dep{$menu} or $menu_dep{$menu} = $pkg->{submenudep}; |
| 274 | } else { |
| 275 | $menu = 'undef'; |
| 276 | } |
| 277 | $menus{$menu} or $menus{$menu} = []; |
| 278 | push @{$menus{$menu}}, $pkg; |
| 279 | } |
| 280 | } |
| 281 | my @menus = sort { |
| 282 | ($a eq 'undef' ? 1 : 0) or |
| 283 | ($b eq 'undef' ? -1 : 0) or |
| 284 | ($a cmp $b) |
| 285 | } keys %menus; |
| 286 | |
| 287 | foreach my $menu (@menus) { |
| 288 | my @pkgs = sort { |
| 289 | package_depends($a, $b) or |
| 290 | ($a->{name} cmp $b->{name}) |
| 291 | } @{$menus{$menu}}; |
| 292 | if ($menu ne 'undef') { |
| 293 | $menu_dep{$menu} and print "if $menu_dep{$menu}\n"; |
| 294 | print "menu \"$menu\"\n"; |
| 295 | } |
| 296 | foreach my $pkg (@pkgs) { |
| 297 | next if $pkg->{src}{ignore}; |
| 298 | my $title = $pkg->{name}; |
| 299 | my $c = (72 - length($pkg->{name}) - length($pkg->{title})); |
| 300 | if ($c > 0) { |
| 301 | $title .= ("." x $c). " ". $pkg->{title}; |
| 302 | } |
| 303 | $title = "\"$title\""; |
| 304 | print "\t"; |
| 305 | $pkg->{menu} and print "menu"; |
| 306 | print "config PACKAGE_".$pkg->{name}."\n"; |
| 307 | $pkg->{hidden} and $title = ""; |
| 308 | print "\t\t".($pkg->{tristate} ? 'tristate' : 'bool')." $title\n"; |
| 309 | print "\t\tdefault y if DEFAULT_".$pkg->{name}."\n"; |
| 310 | unless ($pkg->{hidden}) { |
| 311 | my @def = ("ALL"); |
| 312 | if (!exists($pkg->{repository})) { |
| 313 | push @def, "ALL_NONSHARED"; |
| 314 | } |
| 315 | if ($pkg->{name} =~ /^kmod-/) { |
| 316 | push @def, "ALL_KMODS"; |
| 317 | } |
| 318 | $pkg->{default} ||= "m if " . join("||", @def); |
| 319 | } |
| 320 | if ($pkg->{default}) { |
| 321 | foreach my $default (split /\s*,\s*/, $pkg->{default}) { |
| 322 | print "\t\tdefault $default\n"; |
| 323 | } |
| 324 | } |
| 325 | print mconf_depends($pkg->{name}, $pkg->{depends}, 0); |
| 326 | print mconf_depends($pkg->{name}, $pkg->{mdepends}, 0); |
| 327 | print mconf_conflicts($pkg->{name}, $pkg->{conflicts}); |
| 328 | print "\t\thelp\n"; |
| 329 | print $pkg->{description}; |
| 330 | print "\n"; |
| 331 | |
| 332 | $pkg->{config} and print $pkg->{config}."\n"; |
| 333 | } |
| 334 | if ($menu ne 'undef') { |
| 335 | print "endmenu\n"; |
| 336 | $menu_dep{$menu} and print "endif\n"; |
| 337 | } |
| 338 | } |
| 339 | print "endmenu\n\n"; |
| 340 | |
| 341 | undef $category{$cat}; |
| 342 | } |
| 343 | |
| 344 | sub print_package_overrides() { |
| 345 | keys %overrides > 0 or return; |
| 346 | print "\tconfig OVERRIDE_PKGS\n"; |
| 347 | print "\t\tstring\n"; |
| 348 | print "\t\tdefault \"".join(" ", sort keys %overrides)."\"\n\n"; |
| 349 | } |
| 350 | |
| 351 | sub gen_package_config() { |
| 352 | parse_package_metadata($ARGV[0]) or exit 1; |
| 353 | print "menuconfig IMAGEOPT\n\tbool \"Image configuration\"\n\tdefault n\n"; |
| 354 | print "source \"package/*/image-config.in\"\n"; |
| 355 | if (scalar glob "package/feeds/*/*/image-config.in") { |
| 356 | print "source \"package/feeds/*/*/image-config.in\"\n"; |
| 357 | } |
| 358 | print_package_config_category 'Base system'; |
| 359 | foreach my $cat (sort {uc($a) cmp uc($b)} keys %category) { |
| 360 | print_package_config_category $cat; |
| 361 | } |
| 362 | print_package_overrides(); |
| 363 | } |
| 364 | |
| 365 | sub and_condition($) { |
| 366 | my $condition = shift; |
| 367 | my @spl_and = split('\&\&', $condition); |
| 368 | if (@spl_and == 1) { |
| 369 | return "\$(CONFIG_$spl_and[0])"; |
| 370 | } |
| 371 | return "\$(and " . join (',', map("\$(CONFIG_$_)", @spl_and)) . ")"; |
| 372 | } |
| 373 | |
| 374 | sub gen_condition ($) { |
| 375 | my $condition = shift; |
| 376 | # remove '!()', just as include/package-pack.mk does |
| 377 | $condition =~ s/[()!]//g; |
| 378 | return join("", map(and_condition($_), split('\|\|', $condition))); |
| 379 | } |
| 380 | |
| 381 | sub get_conditional_dep($$) { |
| 382 | my $condition = shift; |
| 383 | my $depstr = shift; |
| 384 | if ($condition) { |
| 385 | if ($condition =~ /^!(.+)/) { |
| 386 | return "\$(if " . gen_condition($1) . ",,$depstr)"; |
| 387 | } else { |
| 388 | return "\$(if " . gen_condition($condition) . ",$depstr)"; |
| 389 | } |
| 390 | } else { |
| 391 | return $depstr; |
| 392 | } |
| 393 | } |
| 394 | |
| 395 | sub gen_package_mk() { |
| 396 | my $line; |
| 397 | |
| 398 | parse_package_metadata($ARGV[0]) or exit 1; |
| 399 | foreach my $srcname (sort {uc($a) cmp uc($b)} keys %srcpackage) { |
| 400 | my $src = $srcpackage{$srcname}; |
| 401 | my $variant_default; |
| 402 | my %deplines = ('' => {}); |
| 403 | |
| 404 | foreach my $pkg (@{$src->{packages}}) { |
| 405 | foreach my $dep (@{$pkg->{depends}}) { |
| 406 | next if ($dep =~ /@/); |
| 407 | |
| 408 | my $condition; |
| 409 | |
| 410 | $dep =~ s/\+//g; |
| 411 | if ($dep =~ /^(.+):(.+)/) { |
| 412 | $condition = $1; |
| 413 | $dep = $2; |
| 414 | } |
| 415 | |
| 416 | my $vpkg_dep = $vpackage{$dep}; |
| 417 | unless (defined $vpkg_dep) { |
| 418 | warn sprintf "WARNING: Makefile '%s' has a dependency on '%s', which does not exist\n", |
| 419 | $src->{makefile}, $dep; |
| 420 | next; |
| 421 | } |
| 422 | |
| 423 | # Filter out self-depends |
| 424 | my @vdeps = grep { $srcname ne $_->{src}{name} } @{$vpkg_dep}; |
| 425 | |
| 426 | foreach my $vdep (@vdeps) { |
| 427 | my $depstr = sprintf '$(curdir)/%s/compile', $vdep->{src}{path}; |
| 428 | if (@vdeps > 1) { |
| 429 | $depstr = sprintf '$(if $(CONFIG_PACKAGE_%s),%s)', $vdep->{name}, $depstr; |
| 430 | } |
| 431 | my $depline = get_conditional_dep($condition, $depstr); |
| 432 | if ($depline) { |
| 433 | $deplines{''}{$depline}++; |
| 434 | } |
| 435 | } |
| 436 | } |
| 437 | |
| 438 | my $config = ''; |
| 439 | $config = sprintf '$(CONFIG_PACKAGE_%s)', $pkg->{name} unless $pkg->{buildonly}; |
| 440 | |
| 441 | $pkg->{prereq} and printf "prereq-%s += %s\n", $config, $src->{path}; |
| 442 | |
| 443 | next if $pkg->{buildonly}; |
| 444 | |
| 445 | printf "package-%s += %s\n", $config, $src->{path}; |
| 446 | |
| 447 | if ($pkg->{variant}) { |
| 448 | if (!defined($variant_default) or $pkg->{variant_default}) { |
| 449 | $variant_default = $pkg->{variant}; |
| 450 | } |
| 451 | printf "\$(curdir)/%s/variants += \$(if %s,%s)\n", $src->{path}, $config, $pkg->{variant}; |
| 452 | } |
| 453 | } |
| 454 | |
| 455 | if (defined($variant_default)) { |
| 456 | printf "\$(curdir)/%s/default-variant := %s\n", $src->{path}, $variant_default; |
| 457 | } |
| 458 | |
| 459 | unless (grep {!$_->{buildonly}} @{$src->{packages}}) { |
| 460 | printf "package- += %s\n", $src->{path}; |
| 461 | } |
| 462 | |
| 463 | if (@{$src->{buildtypes}} > 0) { |
| 464 | printf "buildtypes-%s = %s\n", $src->{path}, join(' ', @{$src->{buildtypes}}); |
| 465 | } |
| 466 | |
| 467 | foreach my $type ('', @{$src->{buildtypes}}) { |
| 468 | my $suffix = ''; |
| 469 | |
| 470 | $suffix = "/$type" if $type; |
| 471 | |
| 472 | next unless $src->{"builddepends$suffix"}; |
| 473 | |
| 474 | defined $deplines{$suffix} or $deplines{$suffix} = {}; |
| 475 | |
| 476 | foreach my $dep (@{$src->{"builddepends$suffix"}}) { |
| 477 | my $depsuffix = ""; |
| 478 | my $deptype = ""; |
| 479 | my $condition; |
| 480 | |
| 481 | if ($dep =~ /^(.+):(.+)/) { |
| 482 | $condition = $1; |
| 483 | $dep = $2; |
| 484 | } |
| 485 | if ($dep =~ /^(.+)\/(.+)/) { |
| 486 | $dep = $1; |
| 487 | $deptype = $2; |
| 488 | $depsuffix = "/$2"; |
| 489 | } |
| 490 | |
| 491 | next if $srcname.$suffix eq $dep.$depsuffix; |
| 492 | |
| 493 | my $src_dep = $srcpackage{$dep}; |
| 494 | unless (defined($src_dep) && (!$deptype || grep { $_ eq $deptype } @{$src_dep->{buildtypes}})) { |
| 495 | warn sprintf "WARNING: Makefile '%s' has a build dependency on '%s', which does not exist\n", |
| 496 | $src->{makefile}, $dep.$depsuffix; |
| 497 | next; |
| 498 | } |
| 499 | |
| 500 | my $depstr = sprintf '$(curdir)/%s/compile', $src_dep->{path}.$depsuffix; |
| 501 | my $depline = get_conditional_dep($condition, $depstr); |
| 502 | if ($depline) { |
| 503 | $deplines{$suffix}{$depline}++; |
| 504 | } |
| 505 | } |
| 506 | } |
| 507 | |
| 508 | foreach my $suffix (sort keys %deplines) { |
| 509 | my $depline = join(" ", sort keys %{$deplines{$suffix}}); |
| 510 | if ($depline) { |
| 511 | $line .= sprintf "\$(curdir)/%s/compile += %s\n", $src->{path}.$suffix, $depline; |
| 512 | } |
| 513 | } |
| 514 | } |
| 515 | |
| 516 | if ($line ne "") { |
| 517 | print "\n$line"; |
| 518 | } |
| 519 | } |
| 520 | |
| 521 | sub gen_package_source() { |
| 522 | parse_package_metadata($ARGV[0]) or exit 1; |
| 523 | foreach my $name (sort {uc($a) cmp uc($b)} keys %package) { |
| 524 | my $pkg = $package{$name}; |
| 525 | if ($pkg->{name} && $pkg->{source}) { |
| 526 | print "$pkg->{name}: "; |
| 527 | print "$pkg->{source}\n"; |
| 528 | } |
| 529 | } |
| 530 | } |
| 531 | |
| 532 | sub gen_package_auxiliary() { |
| 533 | parse_package_metadata($ARGV[0]) or exit 1; |
| 534 | foreach my $name (sort {uc($a) cmp uc($b)} keys %package) { |
| 535 | my $pkg = $package{$name}; |
| 536 | if ($pkg->{name} && $pkg->{repository}) { |
| 537 | print "Package/$name/subdir = $pkg->{repository}\n"; |
| 538 | } |
| 539 | my %depends; |
| 540 | foreach my $dep (@{$pkg->{depends} || []}) { |
| 541 | if ($dep =~ m!^\+?(?:[^:]+:)?([^@]+)$!) { |
| 542 | $depends{$1}++; |
| 543 | } |
| 544 | } |
| 545 | my @depends = sort keys %depends; |
| 546 | if (@depends > 0) { |
| 547 | foreach my $n (@{$pkg->{provides}}) { |
| 548 | print "Package/$n/depends = @depends\n"; |
| 549 | } |
| 550 | } |
| 551 | } |
| 552 | } |
| 553 | |
| 554 | sub gen_package_license($) { |
| 555 | my $level = shift; |
| 556 | parse_package_metadata($ARGV[0]) or exit 1; |
| 557 | foreach my $name (sort {uc($a) cmp uc($b)} keys %package) { |
| 558 | my $pkg = $package{$name}; |
| 559 | if ($pkg->{name}) { |
| 560 | if ($pkg->{license}) { |
| 561 | print "$pkg->{name}: "; |
| 562 | print "$pkg->{license}\n"; |
| 563 | if ($pkg->{licensefiles} && $level == 0) { |
| 564 | print "\tFiles: $pkg->{licensefiles}\n"; |
| 565 | } |
| 566 | } else { |
| 567 | if ($level == 1) { |
| 568 | print "$pkg->{name}: Missing license! "; |
| 569 | print "Please fix $pkg->{src}{makefile}\n"; |
| 570 | } |
| 571 | } |
| 572 | } |
| 573 | } |
| 574 | } |
| 575 | |
| 576 | sub gen_version_filtered_list() { |
| 577 | foreach my $item (version_filter_list(@ARGV)) { |
| 578 | print "$item\n"; |
| 579 | } |
| 580 | } |
| 581 | |
| 582 | sub gen_usergroup_list() { |
| 583 | parse_package_metadata($ARGV[0]) or exit 1; |
| 584 | for my $name (keys %usernames) { |
| 585 | print "user $name $usernames{$name}{id} $usernames{$name}{makefile}\n"; |
| 586 | } |
| 587 | for my $name (keys %groupnames) { |
| 588 | print "group $name $groupnames{$name}{id} $groupnames{$name}{makefile}\n"; |
| 589 | } |
| 590 | } |
| 591 | |
| 592 | sub gen_package_manifest_json() { |
| 593 | my $json; |
| 594 | parse_package_metadata($ARGV[0]) or exit 1; |
| 595 | foreach my $name (sort {uc($a) cmp uc($b)} keys %package) { |
| 596 | my %depends; |
| 597 | my $pkg = $package{$name}; |
| 598 | foreach my $dep (@{$pkg->{depends} || []}) { |
| 599 | if ($dep =~ m!^\+?(?:[^:]+:)?([^@]+)$!) { |
| 600 | $depends{$1}++; |
| 601 | } |
| 602 | } |
| 603 | my @depends = sort keys %depends; |
| 604 | my $pkg_deps = join ' ', map { qq/"$_",/ } @depends; |
| 605 | $pkg_deps =~ s/\,$//; |
| 606 | |
| 607 | my $pkg_maintainer = join ' ', map { qq/"$_",/ } @{$pkg->{maintainer} || []}; |
| 608 | $pkg_maintainer =~ s/\,$//; |
| 609 | |
| 610 | $json = <<"END_JSON"; |
| 611 | ${json}{ |
| 612 | "name":"$name", |
| 613 | "version":"$pkg->{version}", |
| 614 | "category":"$pkg->{category}", |
| 615 | "license":"$pkg->{license}", |
| 616 | "cpe_id":"$pkg->{cpe_id}", |
| 617 | "maintainer": [$pkg_maintainer], |
| 618 | "depends":[$pkg_deps]}, |
| 619 | END_JSON |
| 620 | } |
| 621 | |
| 622 | $json =~ s/[\n\r]//g; |
| 623 | $json =~ s/\,$//; |
| 624 | print "[$json]"; |
| 625 | } |
| 626 | |
| 627 | sub image_manifest_packages($) |
| 628 | { |
| 629 | my %packages; |
| 630 | my $imgmanifest = shift; |
| 631 | |
| 632 | open FILE, "<$imgmanifest" or return; |
| 633 | while (<FILE>) { |
| 634 | /^(.+?) - (.+)$/ and $packages{$1} = $2; |
| 635 | } |
| 636 | close FILE; |
| 637 | |
| 638 | return %packages; |
| 639 | } |
| 640 | |
| 641 | sub dump_cyclonedxsbom_json { |
| 642 | my (@components) = @_; |
| 643 | |
| 644 | my $uuid = sprintf( |
| 645 | "%04x%04x-%04x-%04x-%04x-%04x%04x%04x", |
| 646 | rand(0xffff), rand(0xffff), rand(0xffff), |
| 647 | rand(0x0fff) | 0x4000, |
| 648 | rand(0x3fff) | 0x8000, |
| 649 | rand(0xffff), rand(0xffff), rand(0xffff) |
| 650 | ); |
| 651 | |
| 652 | my $cyclonedx = { |
| 653 | bomFormat => "CycloneDX", |
| 654 | specVersion => "1.4", |
| 655 | serialNumber => "urn:uuid:$uuid", |
| 656 | version => 1, |
| 657 | metadata => { |
| 658 | timestamp => gmtime->datetime . 'Z', |
| 659 | }, |
| 660 | "components" => [@components], |
| 661 | }; |
| 662 | |
| 663 | return encode_json($cyclonedx); |
| 664 | } |
| 665 | |
| 666 | sub gen_image_cyclonedxsbom() { |
| 667 | my $pkginfo = shift @ARGV; |
| 668 | my $imgmanifest = shift @ARGV; |
| 669 | my @components; |
| 670 | my %image_packages; |
| 671 | |
| 672 | %image_packages = image_manifest_packages($imgmanifest); |
| 673 | %image_packages or exit 1; |
| 674 | parse_package_metadata($pkginfo) or exit 1; |
| 675 | |
| 676 | $package{"kernel"} = { |
| 677 | license => "GPL-2.0", |
| 678 | cpe_id => "cpe:/o:linux:linux_kernel", |
| 679 | name => "kernel", |
| 680 | category => "operating-system", |
| 681 | }; |
| 682 | |
| 683 | my %abimap; |
| 684 | my @abipkgs = grep { defined $package{$_}->{abi_version} } keys %package; |
| 685 | foreach my $name (@abipkgs) { |
| 686 | my $pkg = $package{$name}; |
| 687 | my $abipkg = $name . $pkg->{abi_version}; |
| 688 | $abimap{$abipkg} = $name; |
| 689 | } |
| 690 | |
| 691 | foreach my $name (sort {uc($a) cmp uc($b)} keys %image_packages) { |
| 692 | my $pkg = $package{$name}; |
| 693 | if (!$pkg) { |
| 694 | $pkg = $package{$abimap{$name}}; |
| 695 | next if !$pkg; |
| 696 | } |
| 697 | |
| 698 | my @licenses; |
| 699 | my @license = split(/\s+/, $pkg->{license}); |
| 700 | foreach my $lic (@license) { |
| 701 | push @licenses, ( |
| 702 | { "license" => { "name" => $lic } } |
| 703 | ); |
| 704 | } |
| 705 | my $type; |
| 706 | if ($pkg->{category}) { |
| 707 | my $category = $pkg->{category}; |
| 708 | my %cat_type = ( |
| 709 | "operating-system" => "operating-system", |
| 710 | "Firmware" => "firmware", |
| 711 | "Libraries" => "library" |
| 712 | ); |
| 713 | |
| 714 | if ($cat_type{$category}) { |
| 715 | $type = $cat_type{$category}; |
| 716 | } else { |
| 717 | $type = "application"; |
| 718 | } |
| 719 | } |
| 720 | |
| 721 | my $version = $pkg->{version}; |
| 722 | if ($image_packages{$name}) { |
| 723 | $version = $image_packages{$name}; |
| 724 | } |
| 725 | $version =~ s/-r\d+$// if $version; |
| 726 | if ($name =~ /^(kernel|kmod-)/ and $version =~ /^(\d+\.\d+\.\d+)/) { |
| 727 | $version = $1; |
| 728 | } |
| 729 | |
| 730 | push @components, { |
| 731 | name => $pkg->{name}, |
| 732 | version => $version, |
| 733 | @licenses > 0 ? (licenses => [ @licenses ]) : (), |
| 734 | $pkg->{cpe_id} ? (cpe => $pkg->{cpe_id}.":".$version) : (), |
| 735 | $type ? (type => $type) : (), |
| 736 | $version ? (version => $version) : (), |
| 737 | }; |
| 738 | } |
| 739 | |
| 740 | print dump_cyclonedxsbom_json(@components); |
| 741 | } |
| 742 | |
| 743 | sub gen_package_cyclonedxsbom() { |
| 744 | my $pkgmanifest = shift @ARGV; |
| 745 | my @components; |
| 746 | my %mpkgs; |
| 747 | |
| 748 | %mpkgs = parse_package_manifest_metadata($pkgmanifest); |
| 749 | %mpkgs or exit 1; |
| 750 | |
| 751 | foreach my $name (sort {uc($a) cmp uc($b)} keys %mpkgs) { |
| 752 | my $pkg = $mpkgs{$name}; |
| 753 | |
| 754 | my @licenses; |
| 755 | my @license = split(/\s+/, $pkg->{license}); |
| 756 | foreach my $lic (@license) { |
| 757 | push @licenses, ( |
| 758 | { "license" => { "name" => $lic } } |
| 759 | ); |
| 760 | } |
| 761 | |
| 762 | my $type; |
| 763 | if ($pkg->{section}) { |
| 764 | my $section = $pkg->{section}; |
| 765 | my %section_type = ( |
| 766 | "firmware" => "firmware", |
| 767 | "libs" => "library" |
| 768 | ); |
| 769 | |
| 770 | if ($section_type{$section}) { |
| 771 | $type = $section_type{$section}; |
| 772 | } else { |
| 773 | $type = "application"; |
| 774 | } |
| 775 | } |
| 776 | |
| 777 | my $version = $pkg->{version}; |
| 778 | $version =~ s/-r\d+$// if $version; |
| 779 | if ($name =~ /^(kernel|kmod-)/ and $version =~ /^(\d+\.\d+\.\d+)/) { |
| 780 | $version = $1; |
| 781 | } |
| 782 | |
| 783 | push @components, { |
| 784 | name => $name, |
| 785 | version => $version, |
| 786 | @licenses > 0 ? (licenses => [ @licenses ]) : (), |
| 787 | $pkg->{cpe_id} ? (cpe => $pkg->{cpe_id}.":".$version) : (), |
| 788 | $type ? (type => $type) : (), |
| 789 | $version ? (version => $version) : (), |
| 790 | }; |
| 791 | } |
| 792 | |
| 793 | print dump_cyclonedxsbom_json(@components); |
| 794 | } |
| 795 | |
| 796 | sub parse_command() { |
| 797 | GetOptions("ignore=s", \@ignore); |
| 798 | my $cmd = shift @ARGV; |
| 799 | for ($cmd) { |
| 800 | /^mk$/ and return gen_package_mk(); |
| 801 | /^config$/ and return gen_package_config(); |
| 802 | /^kconfig/ and return gen_kconfig_overrides(); |
| 803 | /^source$/ and return gen_package_source(); |
| 804 | /^pkgaux$/ and return gen_package_auxiliary(); |
| 805 | /^pkgmanifestjson$/ and return gen_package_manifest_json(); |
| 806 | /^imgcyclonedxsbom$/ and return gen_image_cyclonedxsbom(); |
| 807 | /^pkgcyclonedxsbom$/ and return gen_package_cyclonedxsbom(); |
| 808 | /^license$/ and return gen_package_license(0); |
| 809 | /^licensefull$/ and return gen_package_license(1); |
| 810 | /^usergroup$/ and return gen_usergroup_list(); |
| 811 | /^version_filter$/ and return gen_version_filtered_list(); |
| 812 | } |
| 813 | die <<EOF |
| 814 | Available Commands: |
| 815 | $0 mk [file] Package metadata in makefile format |
| 816 | $0 config [file] Package metadata in Kconfig format |
| 817 | $0 kconfig [file] [config] [patchver] Kernel config overrides |
| 818 | $0 source [file] Package source file information |
| 819 | $0 pkgaux [file] Package auxiliary variables in makefile format |
| 820 | $0 pkgmanifestjson [file] Package manifests in JSON format |
| 821 | $0 imgcyclonedxsbom <file> [manifest] Image package manifest in CycloneDX SBOM JSON format |
| 822 | $0 pkgcyclonedxsbom <file> Package manifest in CycloneDX SBOM JSON format |
| 823 | $0 license [file] Package license information |
| 824 | $0 licensefull [file] Package license information (full list) |
| 825 | $0 usergroup [file] Package usergroup allocation list |
| 826 | $0 version_filter [patchver] [list...] Filter list of version tagged strings |
| 827 | |
| 828 | Options: |
| 829 | --ignore <name> Ignore the source package <name> |
| 830 | EOF |
| 831 | } |
| 832 | |
| 833 | parse_command(); |