-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #11 from hartwork/extend-gentoo-packages-command
Extend command "gentoo-packages"
- Loading branch information
Showing
1 changed file
with
76 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
@@ -18,6 +20,7 @@ | |
class BinaryPackage: | ||
full_name: str | ||
build_id: int | ||
build_time: int | ||
cpv: str | ||
path: str | ||
|
||
|
@@ -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) | ||
|
@@ -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): | ||
|
@@ -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:]) | ||
|
||
|
@@ -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__': | ||
|