b.liu | e958203 | 2025-04-17 19:18:16 +0800 | [diff] [blame] | 1 | package metadata; |
| 2 | use base 'Exporter'; |
| 3 | use strict; |
| 4 | use warnings; |
| 5 | our @EXPORT = qw(%package %vpackage %srcpackage %category %overrides clear_packages parse_package_metadata parse_package_manifest_metadata parse_target_metadata get_multiline @ignore %usernames %groupnames); |
| 6 | |
| 7 | our %package; |
| 8 | our %vpackage; |
| 9 | our %srcpackage; |
| 10 | our %category; |
| 11 | our %overrides; |
| 12 | our @ignore; |
| 13 | |
| 14 | our %usernames; |
| 15 | our %groupnames; |
| 16 | our %userids; |
| 17 | our %groupids; |
| 18 | |
| 19 | sub get_multiline { |
| 20 | my $fh = shift; |
| 21 | my $prefix = shift; |
| 22 | my $str; |
| 23 | while (<$fh>) { |
| 24 | last if /^@@/; |
| 25 | $str .= (($_ and $prefix) ? $prefix . $_ : $_); |
| 26 | } |
| 27 | |
| 28 | return $str ? $str : ""; |
| 29 | } |
| 30 | |
| 31 | sub confstr($) { |
| 32 | my $conf = shift; |
| 33 | $conf =~ tr#/\.\-/#___#; |
| 34 | return $conf; |
| 35 | } |
| 36 | |
| 37 | sub parse_package_metadata_usergroup($$$$$) { |
| 38 | my $makefile = shift; |
| 39 | my $typename = shift; |
| 40 | my $names = shift; |
| 41 | my $ids = shift; |
| 42 | my $spec = shift; |
| 43 | my $name; |
| 44 | my $id; |
| 45 | |
| 46 | # the regex for name is taken from is_valid_name() of package shadow |
| 47 | if ($spec =~ /^([a-z_][a-z0-9_-]*\$?)$/) { |
| 48 | $name = $spec; |
| 49 | $id = -1; |
| 50 | } elsif ($spec =~ /^([a-z_][a-z0-9_-]*\$?)=(\d+)$/) { |
| 51 | $name = $1; |
| 52 | $id = $2; |
| 53 | } else { |
| 54 | warn "$makefile: invalid $typename spec $spec\n"; |
| 55 | return 0; |
| 56 | } |
| 57 | |
| 58 | if ($id =~ /^[1-9]\d*$/) { |
| 59 | if ($id >= 65536) { |
| 60 | warn "$makefile: $typename $name id $id >= 65536"; |
| 61 | return 0; |
| 62 | } |
| 63 | if (not exists $ids->{$id}) { |
| 64 | $ids->{$id} = { |
| 65 | name => $name, |
| 66 | makefile => $makefile, |
| 67 | }; |
| 68 | } elsif ($ids->{$id}{name} ne $name) { |
| 69 | warn "$makefile: $typename $name id $id is already taken by $ids->{$id}{makefile}\n"; |
| 70 | return 0; |
| 71 | } |
| 72 | } elsif ($id != -1) { |
| 73 | warn "$makefile: $typename $name has invalid id $id\n"; |
| 74 | return 0; |
| 75 | } |
| 76 | |
| 77 | if (not exists $names->{$name}) { |
| 78 | $names->{$name} = { |
| 79 | id => $id, |
| 80 | makefile => $makefile, |
| 81 | }; |
| 82 | } elsif ($names->{$name}{id} != $id) { |
| 83 | warn "$makefile: id of $typename $name collides with that defined defined in $names->{$name}{makefile}\n"; |
| 84 | return 0; |
| 85 | } |
| 86 | return 1; |
| 87 | } |
| 88 | |
| 89 | sub parse_target_metadata($) { |
| 90 | my $file = shift; |
| 91 | my ($target, @target, $profile); |
| 92 | my %target; |
| 93 | my $makefile; |
| 94 | |
| 95 | open FILE, "<$file" or do { |
| 96 | warn "Can't open file '$file': $!\n"; |
| 97 | return; |
| 98 | }; |
| 99 | while (<FILE>) { |
| 100 | chomp; |
| 101 | /^Source-Makefile: \s*((.+\/)([^\/]+)\/Makefile)\s*$/ and $makefile = $1; |
| 102 | /^Target:\s*(.+)\s*$/ and do { |
| 103 | my $name = $1; |
| 104 | $target = { |
| 105 | id => $name, |
| 106 | board => $name, |
| 107 | makefile => $makefile, |
| 108 | boardconf => confstr($name), |
| 109 | conf => confstr($name), |
| 110 | profiles => [], |
| 111 | features => [], |
| 112 | depends => [], |
| 113 | subtargets => [] |
| 114 | }; |
| 115 | push @target, $target; |
| 116 | $target{$name} = $target; |
| 117 | if ($name =~ /([^\/]+)\/([^\/]+)/) { |
| 118 | push @{$target{$1}->{subtargets}}, $2; |
| 119 | $target->{board} = $1; |
| 120 | $target->{boardconf} = confstr($1); |
| 121 | $target->{subtarget} = 1; |
| 122 | $target->{parent} = $target{$1}; |
| 123 | } |
| 124 | }; |
| 125 | /^Target-Name:\s*(.+)\s*$/ and $target->{name} = $1; |
| 126 | /^Target-Arch:\s*(.+)\s*$/ and $target->{arch} = $1; |
| 127 | /^Target-Arch-Packages:\s*(.+)\s*$/ and $target->{arch_packages} = $1; |
| 128 | /^Target-Features:\s*(.+)\s*$/ and $target->{features} = [ split(/\s+/, $1) ]; |
| 129 | /^Target-Depends:\s*(.+)\s*$/ and $target->{depends} = [ split(/\s+/, $1) ]; |
| 130 | /^Target-Description:/ and $target->{desc} = get_multiline(*FILE); |
| 131 | /^Target-Optimization:\s*(.+)\s*$/ and $target->{cflags} = $1; |
| 132 | /^CPU-Type:\s*(.+)\s*$/ and $target->{cputype} = $1; |
| 133 | /^Linux-Version:\s*(.+)\s*$/ and $target->{version} = $1; |
| 134 | /^Linux-Testing-Version:\s*(.+)\s*$/ and $target->{testing_version} = $1; |
| 135 | /^Linux-Release:\s*(.+)\s*$/ and $target->{release} = $1; |
| 136 | /^Linux-Kernel-Arch:\s*(.+)\s*$/ and $target->{karch} = $1; |
| 137 | /^Default-Subtarget:\s*(.+)\s*$/ and $target->{def_subtarget} = $1; |
| 138 | /^Default-Packages:\s*(.+)\s*$/ and $target->{packages} = [ split(/\s+/, $1) ]; |
| 139 | /^Target-Profile:\s*(.+)\s*$/ and do { |
| 140 | $profile = { |
| 141 | id => $1, |
| 142 | name => $1, |
| 143 | has_image_metadata => 0, |
| 144 | supported_devices => [], |
| 145 | priority => 999, |
| 146 | packages => [], |
| 147 | default => "y if TARGET_ALL_PROFILES" |
| 148 | }; |
| 149 | $1 =~ /^DEVICE_/ and $target->{has_devices} = 1; |
| 150 | push @{$target->{profiles}}, $profile; |
| 151 | }; |
| 152 | /^Target-Profile-Name:\s*(.+)\s*$/ and $profile->{name} = $1; |
| 153 | /^Target-Profile-hasImageMetadata:\s*(\d+)\s*$/ and $profile->{has_image_metadata} = $1; |
| 154 | /^Target-Profile-SupportedDevices:\s*(.+)\s*$/ and $profile->{supported_devices} = [ split(/\s+/, $1) ]; |
| 155 | /^Target-Profile-Priority:\s*(\d+)\s*$/ and do { |
| 156 | $profile->{priority} = $1; |
| 157 | $target->{sort} = 1; |
| 158 | }; |
| 159 | /^Target-Profile-Packages:\s*(.*)\s*$/ and $profile->{packages} = [ split(/\s+/, $1) ]; |
| 160 | /^Target-Profile-Description:\s*(.*)\s*/ and $profile->{desc} = get_multiline(*FILE); |
| 161 | /^Target-Profile-Broken:\s*(.+)\s*$/ and do { |
| 162 | $profile->{broken} = 1; |
| 163 | $profile->{default} = "n"; |
| 164 | }; |
| 165 | /^Target-Profile-Default:\s*(.+)\s*$/ and $profile->{default} = $1; |
| 166 | } |
| 167 | close FILE; |
| 168 | foreach my $target (@target) { |
| 169 | if (@{$target->{subtargets}} > 0) { |
| 170 | $target->{profiles} = []; |
| 171 | next; |
| 172 | } |
| 173 | @{$target->{profiles}} > 0 or $target->{profiles} = [ |
| 174 | { |
| 175 | id => 'Default', |
| 176 | name => 'Default', |
| 177 | packages => [] |
| 178 | } |
| 179 | ]; |
| 180 | |
| 181 | $target->{sort} and @{$target->{profiles}} = sort { |
| 182 | $a->{priority} <=> $b->{priority} or |
| 183 | $a->{name} cmp $b->{name}; |
| 184 | } @{$target->{profiles}}; |
| 185 | } |
| 186 | return @target; |
| 187 | } |
| 188 | |
| 189 | sub clear_packages() { |
| 190 | %package = (); |
| 191 | %vpackage = (); |
| 192 | %srcpackage = (); |
| 193 | %category = (); |
| 194 | %overrides = (); |
| 195 | %usernames = (); |
| 196 | %groupnames = (); |
| 197 | } |
| 198 | |
| 199 | sub parse_package_metadata($) { |
| 200 | my $file = shift; |
| 201 | my $pkg; |
| 202 | my $src; |
| 203 | my $override; |
| 204 | my %ignore = map { $_ => 1 } @ignore; |
| 205 | |
| 206 | open FILE, "<$file" or do { |
| 207 | warn "Cannot open '$file': $!\n"; |
| 208 | return undef; |
| 209 | }; |
| 210 | while (<FILE>) { |
| 211 | chomp; |
| 212 | /^Source-Makefile: \s*((?:package\/)?((?:.+\/)?([^\/]+))\/Makefile)\s*$/ and do { |
| 213 | $src = { |
| 214 | makefile => $1, |
| 215 | path => $2, |
| 216 | name => $3, |
| 217 | ignore => $ignore{$3}, |
| 218 | packages => [], |
| 219 | buildtypes => [], |
| 220 | builddepends => [], |
| 221 | }; |
| 222 | $srcpackage{$3} = $src; |
| 223 | $override = ""; |
| 224 | undef $pkg; |
| 225 | }; |
| 226 | /^Override: \s*(.+?)\s*$/ and do { |
| 227 | $override = $1; |
| 228 | $overrides{$src->{name}} = 1; |
| 229 | }; |
| 230 | next unless $src; |
| 231 | /^Package:\s*(.+?)\s*$/ and do { |
| 232 | $pkg = {}; |
| 233 | $pkg->{src} = $src; |
| 234 | $pkg->{name} = $1; |
| 235 | $pkg->{title} = ""; |
| 236 | $pkg->{depends} = []; |
| 237 | $pkg->{mdepends} = []; |
| 238 | $pkg->{provides} = [$1]; |
| 239 | $pkg->{tristate} = 1; |
| 240 | $pkg->{override} = $override; |
| 241 | $package{$1} = $pkg; |
| 242 | push @{$src->{packages}}, $pkg; |
| 243 | |
| 244 | $vpackage{$1} or $vpackage{$1} = []; |
| 245 | unshift @{$vpackage{$1}}, $pkg; |
| 246 | }; |
| 247 | /^Build-Depends: \s*(.+)\s*$/ and $src->{builddepends} = [ split /\s+/, $1 ]; |
| 248 | /^Build-Depends\/(\w+): \s*(.+)\s*$/ and $src->{"builddepends/$1"} = [ split /\s+/, $2 ]; |
| 249 | /^Build-Types:\s*(.+)\s*$/ and $src->{buildtypes} = [ split /\s+/, $1 ]; |
| 250 | next unless $pkg; |
| 251 | /^Version: \s*(.+)\s*$/ and $pkg->{version} = $1; |
| 252 | /^Title: \s*(.+)\s*$/ and $pkg->{title} = $1; |
| 253 | /^Menu: \s*(.+)\s*$/ and $pkg->{menu} = $1; |
| 254 | /^Submenu: \s*(.+)\s*$/ and $pkg->{submenu} = $1; |
| 255 | /^Submenu-Depends: \s*(.+)\s*$/ and $pkg->{submenudep} = $1; |
| 256 | /^Source: \s*(.+)\s*$/ and $pkg->{source} = $1; |
| 257 | /^License: \s*(.+)\s*$/ and $pkg->{license} = $1; |
| 258 | /^LicenseFiles: \s*(.+)\s*$/ and $pkg->{licensefiles} = $1; |
| 259 | /^CPE-ID: \s*(.+)\s*$/ and $pkg->{cpe_id} = $1; |
| 260 | /^URL: \s*(.+)\s*$/ and $pkg->{url} = $1; |
| 261 | /^ABI-Version: \s*(.+)\s*$/ and $pkg->{abi_version} = $1; |
| 262 | /^Default: \s*(.+)\s*$/ and $pkg->{default} = $1; |
| 263 | /^Provides: \s*(.+)\s*$/ and do { |
| 264 | my @vpkg = split /\s+/, $1; |
| 265 | @{$pkg->{provides}} = ($pkg->{name}, @vpkg); |
| 266 | foreach my $vpkg (@vpkg) { |
| 267 | next if ($vpkg eq $pkg->{name}); |
| 268 | $vpackage{$vpkg} or $vpackage{$vpkg} = []; |
| 269 | push @{$vpackage{$vpkg}}, $pkg; |
| 270 | } |
| 271 | }; |
| 272 | /^Menu-Depends: \s*(.+)\s*$/ and $pkg->{mdepends} = [ split /\s+/, $1 ]; |
| 273 | /^Depends: \s*(.+)\s*$/ and $pkg->{depends} = [ split /\s+/, $1 ]; |
| 274 | /^Conflicts: \s*(.+)\s*$/ and $pkg->{conflicts} = [ split /\s+/, $1 ]; |
| 275 | /^Hidden: \s*(.+)\s*$/ and $pkg->{hidden} = 1; |
| 276 | /^Build-Variant: \s*([\w\-]+)\s*/ and $pkg->{variant} = $1; |
| 277 | /^Default-Variant: .*/ and $pkg->{variant_default} = 1; |
| 278 | /^Build-Only: \s*(.+)\s*$/ and $pkg->{buildonly} = 1; |
| 279 | /^Repository:\s*(.+?)\s*$/ and $pkg->{repository} = $1; |
| 280 | /^Category: \s*(.+)\s*$/ and do { |
| 281 | $pkg->{category} = $1; |
| 282 | defined $category{$1} or $category{$1} = {}; |
| 283 | defined $category{$1}{$src->{name}} or $category{$1}{$src->{name}} = []; |
| 284 | push @{$category{$1}{$src->{name}}}, $pkg; |
| 285 | }; |
| 286 | /^Description: \s*(.*)\s*$/ and $pkg->{description} = "\t\t $1\n". get_multiline(*FILE, "\t\t "); |
| 287 | /^Type: \s*(.+)\s*$/ and do { |
| 288 | $pkg->{type} = [ split /\s+/, $1 ]; |
| 289 | undef $pkg->{tristate}; |
| 290 | foreach my $type (@{$pkg->{type}}) { |
| 291 | $type =~ /ipkg/ and $pkg->{tristate} = 1; |
| 292 | } |
| 293 | }; |
| 294 | /^Config:\s*(.*)\s*$/ and $pkg->{config} = "$1\n".get_multiline(*FILE, "\t"); |
| 295 | /^Prereq-Check:/ and $pkg->{prereq} = 1; |
| 296 | /^Maintainer: \s*(.+)\s*$/ and $pkg->{maintainer} = [ split /, /, $1 ]; |
| 297 | /^Require-User:\s*(.*?)\s*$/ and do { |
| 298 | my @ugspecs = split /\s+/, $1; |
| 299 | |
| 300 | for my $ugspec (@ugspecs) { |
| 301 | my @ugspec = split /:/, $ugspec, 3; |
| 302 | if ($ugspec[0]) { |
| 303 | parse_package_metadata_usergroup($src->{makefile}, "user", \%usernames, \%userids, $ugspec[0]) or return 0; |
| 304 | } |
| 305 | if ($ugspec[1]) { |
| 306 | parse_package_metadata_usergroup($src->{makefile}, "group", \%groupnames, \%groupids, $ugspec[1]) or return 0; |
| 307 | } |
| 308 | if ($ugspec[2]) { |
| 309 | my @addngroups = split /,/, $ugspec[2]; |
| 310 | for my $addngroup (@addngroups) { |
| 311 | parse_package_metadata_usergroup($src->{makefile}, "group", \%groupnames, \%groupids, $addngroup) or return 0; |
| 312 | } |
| 313 | } |
| 314 | } |
| 315 | }; |
| 316 | } |
| 317 | close FILE; |
| 318 | return 1; |
| 319 | } |
| 320 | |
| 321 | sub parse_package_manifest_metadata($) { |
| 322 | my $file = shift; |
| 323 | my $pkg; |
| 324 | my %pkgs; |
| 325 | |
| 326 | open FILE, "<$file" or do { |
| 327 | warn "Cannot open '$file': $!\n"; |
| 328 | return undef; |
| 329 | }; |
| 330 | |
| 331 | while (<FILE>) { |
| 332 | chomp; |
| 333 | /^Package:\s*(.+?)\s*$/ and do { |
| 334 | $pkg = {}; |
| 335 | $pkg->{name} = $1; |
| 336 | $pkg->{depends} = []; |
| 337 | $pkgs{$1} = $pkg; |
| 338 | }; |
| 339 | /^Version:\s*(.+)\s*$/ and $pkg->{version} = $1; |
| 340 | /^Depends:\s*(.+)\s*$/ and $pkg->{depends} = [ split /\s+/, $1 ]; |
| 341 | /^Source:\s*(.+)\s*$/ and $pkg->{source} = $1; |
| 342 | /^SourceName:\s*(.+)\s*$/ and $pkg->{sourcename} = $1; |
| 343 | /^License:\s*(.+)\s*$/ and $pkg->{license} = $1; |
| 344 | /^LicenseFiles:\s*(.+)\s*$/ and $pkg->{licensefiles} = $1; |
| 345 | /^Section:\s*(.+)\s*$/ and $pkg->{section} = $1; |
| 346 | /^SourceDateEpoch: \s*(.+)\s*$/ and $pkg->{sourcedateepoch} = $1; |
| 347 | /^CPE-ID:\s*(.+)\s*$/ and $pkg->{cpe_id} = $1; |
| 348 | /^URL:\s*(.+)\s*$/ and $pkg->{url} = $1; |
| 349 | /^Architecture:\s*(.+)\s*$/ and $pkg->{architecture} = $1; |
| 350 | /^Installed-Size:\s*(.+)\s*$/ and $pkg->{installedsize} = $1; |
| 351 | /^Filename:\s*(.+)\s*$/ and $pkg->{filename} = $1; |
| 352 | /^Size:\s*(\d+)\s*$/ and $pkg->{size} = $1; |
| 353 | /^SHA256sum:\s*(.*)\s*$/ and $pkg->{sha256sum} = $1; |
| 354 | } |
| 355 | |
| 356 | close FILE; |
| 357 | return %pkgs; |
| 358 | } |
| 359 | |
| 360 | 1; |