| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- #!/usr/bin/python3
- # --- BEGIN COPYRIGHT BLOCK ---
- # Copyright (C) 2021 Red Hat, Inc.
- # All rights reserved.
- #
- # License: GPL (version 3 or any later version).
- # See LICENSE for details.
- # --- END COPYRIGHT BLOCK ---
- #
- # PYTHON_ARGCOMPLETE_OK
- import os
- import sys
- import time
- import signal
- import argparse
- import argcomplete
- import shutil
- import toml
- from lib389.cli_base import setup_script_logger
- from rust2rpm import licensing
- SPECFILE_COMMENT_LINE = 'Bundled cargo crates list'
- START_LINE = f"##### {SPECFILE_COMMENT_LINE} - START #####\n"
- END_LINE = f"##### {SPECFILE_COMMENT_LINE} - END #####\n"
- parser = argparse.ArgumentParser(
- formatter_class=argparse.RawDescriptionHelpFormatter,
- description="""Add 'Provides: bundled(crate(foo)) = version' to a Fedora based specfile.
- Additionally, add a helper comment with a comulated License metainfo which is based on Cargo.lock file content.""")
- parser.add_argument('-v', '--verbose',
- help="Display verbose operation tracing during command execution",
- action='store_true', default=False)
- parser.add_argument('cargo_lock_file',
- help="The path to Cargo.lock file.")
- parser.add_argument('spec_file',
- help="The path to spec file that will be modified.")
- parser.add_argument('vendor_dir',
- help="The path to the vendor directory file that will be modified.")
- parser.add_argument('--backup-specfile',
- help="Make a backup of the downstream specfile.",
- action='store_true', default=False)
- # handle a control-c gracefully
- def signal_handler(signal, frame):
- print('\n\nExiting...')
- sys.exit(0)
- def get_license_list(vendor_dir):
- license_list = list()
- for root, _, files in os.walk(vendor_dir):
- for file in files:
- name = os.path.join(root, file)
- if os.path.isfile(name) and "Cargo.toml" in name:
- with open(name, "r") as file:
- contents = file.read()
- data = toml.loads(contents)
- license, warning = licensing.translate_license_fedora(data["package"]["license"])
- # Normalise
- license = license.replace("/", " or ").replace(" / ", " or ")
- license = license.replace("Apache-2.0", "ASL 2.0")
- license = license.replace("WITH LLVM-exception", "with exceptions")
- if "or" in license or "and" in license:
- license = f"({license})"
- if license == "(MIT or ASL 2.0)":
- license = "(ASL 2.0 or MIT)"
- if license not in license_list:
- if warning is not None:
- # Ignore known warnings
- if not warning.endswith("LLVM-exception!") and \
- not warning.endswith("MIT/Apache-2.0!"):
- print(f"{license}: {warning}")
- license_list.append(license)
- return " and ".join(license_list)
- def backup_specfile(spec_file):
- time_now = time.strftime("%Y%m%d_%H%M%S")
- log.info(f"Backing up file {spec_file} to {spec_file}.{time_now}")
- shutil.copy2(spec_file, f"{spec_file}.{time_now}")
- def replace_license(spec_file, license_string):
- result = []
- with open(spec_file, "r") as file:
- contents = file.readlines()
- for line in contents:
- if line.startswith("License: "):
- result.append("# IMPORTANT - Check if it looks right. Additionally, "
- "compare with the original line. Then, remove this comment and # FIX ME - part.\n")
- result.append(f"# FIX ME - License: GPLv3+ and {license_string}\n")
- else:
- result.append(line)
- with open(spec_file, "w") as file:
- file.writelines(result)
- log.info(f"Licenses are successfully updated - {spec_file}")
- def clean_specfile(spec_file):
- result = []
- remove_lines = False
- cleaned = False
- with open(spec_file, "r") as file:
- contents = file.readlines()
- log.info(f"Remove '{SPECFILE_COMMENT_LINE}' content from {spec_file}")
- for line in contents:
- if line == START_LINE:
- remove_lines = True
- log.debug(f"Remove '{START_LINE}' from {spec_file}")
- elif line == END_LINE:
- remove_lines = False
- cleaned = True
- log.debug(f"Remove '{END_LINE}' from {spec_file}")
- elif not remove_lines:
- result.append(line)
- else:
- log.debug(f"Remove '{line}' from {spec_file}")
- with open(spec_file, "w") as file:
- file.writelines(result)
- return cleaned
- def write_provides_bundled_crate(cargo_lock_file, spec_file, cleaned):
- # Generate 'Provides' out of cargo_lock_file
- with open(cargo_lock_file, "r") as file:
- contents = file.read()
- data = toml.loads(contents)
- provides_lines = []
- for package in data["package"]:
- provides_lines.append(f"Provides: bundled(crate({package['name']})) = {package['version'].replace('-', '_')}\n")
- # Find a line index where 'Provides' ends
- with open(spec_file, "r") as file:
- spec_file_lines = file.readlines()
- last_provides = -1
- for i in range(0, len(spec_file_lines)):
- if spec_file_lines[i].startswith("%description"):
- break
- if spec_file_lines[i].startswith("Provides:"):
- last_provides = i
- # Insert the generated 'Provides' to the specfile
- log.info(f"Add the fresh '{SPECFILE_COMMENT_LINE}' content to {spec_file}")
- i = last_provides + 2
- spec_file_lines.insert(i, START_LINE)
- for line in sorted(provides_lines):
- i = i + 1
- log.debug(f"Adding '{line[:-1]}' as a line {i} to buffer")
- spec_file_lines.insert(i, line)
- i = i + 1
- spec_file_lines.insert(i, END_LINE)
- # Insert an empty line if we haven't cleaned the old content
- # (as the old content already has an extra empty line that wasn't removed)
- if not cleaned:
- i = i + 1
- spec_file_lines.insert(i, "\n")
- log.debug(f"Commit the buffer to {spec_file}")
- with open(spec_file, "w") as file:
- file.writelines(spec_file_lines)
- if __name__ == '__main__':
- args = parser.parse_args()
- log = setup_script_logger('bundle-rust-downstream', args.verbose)
- log.debug("389-ds-base Rust Crates to Bundled Downstream Specfile tool")
- log.debug(f"Called with: {args}")
- if not os.path.exists(args.spec_file):
- log.info(f"File doesn't exists: {args.spec_file}")
- sys.exit(1)
- if not os.path.exists(args.cargo_lock_file):
- log.info(f"File doesn't exists: {args.cargo_lock_file}")
- sys.exit(1)
- if args.backup_specfile:
- backup_specfile(args.spec_file)
- cleaned = clean_specfile(args.spec_file)
- write_provides_bundled_crate(args.cargo_lock_file, args.spec_file, cleaned)
- license_string = get_license_list(args.vendor_dir)
- replace_license(args.spec_file, license_string)
- log.info(f"Specfile {args.spec_file} is successfully modified! Please:\n"
- "1. Open the specfile with your editor of choice\n"
- "2. Make sure that Provides with bundled crates are correct\n"
- "3. Follow the instructions for 'License:' field and remove the helper comments")
|