Skip to content

Commit

Permalink
Merge pull request #11 from hartwork/extend-gentoo-packages-command
Browse files Browse the repository at this point in the history
Extend command "gentoo-packages"
  • Loading branch information
hartwork committed Jul 23, 2021
2 parents 544b1ea + a21eee7 commit b8ecc50
Showing 1 changed file with 76 additions and 22 deletions.
98 changes: 76 additions & 22 deletions binary_gentoo/internal/cli/packages.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# Copyright (C) 2021 Sebastian Pipping <[email protected]>
# Licensed under GNU Affero GPL version 3 or later

import datetime
import os
import re
import sys
import time
from argparse import ArgumentParser
from contextlib import suppress
from dataclasses import dataclass
Expand All @@ -18,6 +20,7 @@
class BinaryPackage:
full_name: str
build_id: int
build_time: int
cpv: str
path: str

Expand All @@ -36,33 +39,63 @@ def parse_package_block(package_block: str) -> BinaryPackage:
return BinaryPackage(
full_name=f'{d["CPV"]}-{d["BUILD_ID"]}',
build_id=d['BUILD_ID'],
build_time=int(d['BUILD_TIME']),
cpv=d['CPV'],
path=d.get('PATH', f'{d["CPV"]}.tbz2'), # for FEATURES=-binpkg-multi-instance
)


def adjust_index_file_header(old_header: str, new_package_count: int,
new_modification_timestamp: int) -> str:
timestamp_pattern = 'TIMESTAMP: ([0-9]+)\n'
old_modification_timestamp = int(re.search(timestamp_pattern, old_header).group(1))

# Make sure we're always monotonically increasing the timestamp
if new_modification_timestamp <= old_modification_timestamp:
new_modification_timestamp = old_modification_timestamp + 1

new_header = re.sub('PACKAGES: [0-9]+\n',
f'PACKAGES: {new_package_count}\n',
old_header,
flags=re.MULTILINE)
new_header = re.sub(timestamp_pattern,
f'TIMESTAMP: {new_modification_timestamp}\n',
new_header,
flags=re.MULTILINE)

return new_header


def has_safe_package_path(package):
return (not package.path.startswith('/') and '..' not in package.path
and 2 <= len(package.path.split('/')) <= 3)


def run(config):
def read_packages_index_file(config):
packages_index_filename = os.path.join(config.host_pkgdir, 'Packages')

with open(packages_index_filename) as f:
content = f.read()
blocks = content.split('\n\n')
header, *packages_blocks = blocks
return header, packages_blocks, packages_index_filename


def run_delete(config):
header, packages_blocks, packages_index_filename = read_packages_index_file(config)

matcher = (re.compile(config.metadata, flags=re.MULTILINE) if config.metadata else Mock(
search=Mock(return_value=True)))
packages_to_keep = []
packages_to_delete = []

empty_block_count = 0
for package_block in packages_blocks:
target = packages_to_delete if (package_block
and matcher.search(package_block)) else packages_to_keep
if not package_block:
empty_block_count += 1
continue
target = packages_to_delete if matcher.search(package_block) else packages_to_keep
target.append(package_block)
actual_packages_count = len(packages_blocks) - empty_block_count

for package_block in packages_to_delete:
package = parse_package_block(package_block)
Expand All @@ -80,12 +113,31 @@ def run(config):
else:
print(f'Dropping entry {package.full_name!r} BUT SKIPPING file {package.path!r}...')

now_seconds_since_epoch = int(time.time())
header = adjust_index_file_header(header,
new_package_count=len(packages_to_keep),
new_modification_timestamp=now_seconds_since_epoch)

if not config.pretend:
with open(packages_index_filename, 'w') as f:
content = '\n\n'.join([header] + packages_to_keep)
f.write(content)

print(f'{len(packages_to_delete)} of {len(packages_blocks)} package(s) dropped')
print(f'{len(packages_to_delete)} of {actual_packages_count} package(s) dropped')


def run_list(config):
_header, packages_blocks, _packages_index_filename = read_packages_index_file(config)

packages = []
for package_block in packages_blocks:
if not package_block:
continue
packages.append(parse_package_block(package_block))

for package in sorted(packages, key=lambda p: (p.build_time, p.full_name)):
build_datetime = datetime.datetime.fromtimestamp(package.build_time)
print(f'[{build_datetime}] {package.full_name}')


def parse_command_line(argv):
Expand All @@ -96,23 +148,25 @@ def parse_command_line(argv):
add_version_argument_to(parser)
add_pkgdir_argument_to(parser)

parser.add_argument('--metadata',
metavar='REGEX',
help='limit operation to all packages '
'where any metadata line matches '
'pattern REGEX (e.g. "CPV: virtual/.+")')
parser.add_argument('--pretend',
default=False,
action='store_true',
help='only display what would be cleaned '
'(default: delete files)')

commands_group = parser.add_argument_group('commands')
commands_group.add_argument('--delete',
subcommands = parser.add_subparsers(title='subcommands')

delete_command = subcommands.add_parser('delete',
help='drop package entries and '
'delete their respective .xpak/.tbz2 files')
delete_command.add_argument('--metadata',
metavar='REGEX',
help='limit operation to all packages '
'where any metadata line matches '
'pattern REGEX (e.g. "CPV: virtual/.+")')
delete_command.add_argument('--pretend',
default=False,
action='store_true',
required='True',
help='drop package entries and '
'delete their respective .xpak/.tbz2 files')
help='only display what would be cleaned '
'(default: delete files)')
delete_command.set_defaults(command_func=run_delete)

list_command = subcommands.add_parser('list', help='list packages in chronological order')
list_command.set_defaults(command_func=run_list)

return parser.parse_args(argv[1:])

Expand All @@ -125,7 +179,7 @@ def main():
with exception_reporting():
config = parse_command_line(sys.argv)
enrich_config(config)
run(config)
config.command_func(config)


if __name__ == '__main__':
Expand Down

0 comments on commit b8ecc50

Please sign in to comment.