Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
package_release.py 10.39 KiB
#!/usr/bin/env python3
import argparse
import glob
import json
import os
import sys
import shutil
import hashlib
import tempfile
import alphaplan_fwr
from datetime import datetime

from convert_md2html import convertmd2html


def md5(fname):
    hash_md5 = hashlib.md5()
    with open(fname, "rb", encoding=None) as f:
        for chunk in iter(lambda: f.read(4096), b""):
            hash_md5.update(chunk)
    return hash_md5.hexdigest()


def copy_files(files, input_dir, subdir, output_dir, outlocal_dir):
    md5sums = {}
    if output_dir is not None:
        os.makedirs(os.path.join(output_dir, subdir), exist_ok=True)
    if outlocal_dir is not None:
        os.makedirs(os.path.join(outlocal_dir, subdir), exist_ok=True)

    for f in files:
        source_file = os.path.join(input_dir, f)
        if os.path.exists(source_file):

            if output_dir is not None:
                target_file = os.path.join(
                    output_dir, subdir, os.path.basename(source_file)
                )
                print("Copy: %s -> %s" % (source_file, target_file))
                shutil.copyfile(source_file, target_file, follow_symlinks=True)

            if outlocal_dir is not None:
                target_file = os.path.join(
                    outlocal_dir, subdir, os.path.basename(source_file)
                )
                print("Copy: %s -> %s" % (source_file, target_file))
                shutil.copyfile(source_file, target_file, follow_symlinks=True)

            md5sums[os.path.basename(source_file)] = md5(source_file)
        else:
            print("Missing: " + source_file)

    # Write md5sums file:
    with tempfile.TemporaryDirectory() as tmp:
        source_file = os.path.join(tmp, subdir, "md5sums.txt")
        os.makedirs(os.path.dirname(source_file), exist_ok=True)

        with open(source_file, "w", encoding="utf-8") as f_md5:
            for f, h in md5sums.items():
                f_md5.write("{}  {}\n".format(h, f))

        if output_dir is not None:
            target_file = os.path.join(
                output_dir, subdir, os.path.basename(source_file)
            )
            print("Copy: %s -> %s" % (source_file, target_file))
            shutil.copyfile(source_file, target_file, follow_symlinks=True)

        if outlocal_dir is not None:
            target_file = os.path.join(
                outlocal_dir, subdir, os.path.basename(source_file)
            )
            print("Copy: %s -> %s" % (source_file, target_file))
            shutil.copyfile(source_file, target_file, follow_symlinks=True)
    return md5sums


def generate_metadata(
    machine,
    version,
    artifacts_image,
    sdk,
    output_dir,
    outlocal_dir,
):
    """Generates a metainfo.json for the release"""

    install_script = None
    licenses = None
    image_general = None
    image_wic = None

    # Join filepath for metadata

    if output_dir is not None:
        filepath = os.path.join(output_dir, machine, "metainfo.json")
    elif outlocal_dir is not None:
        filepath = os.path.join(outlocal_dir, machine, "metainfo.json")
    else:
        print("Error: Filepath is empty")
        return -1

    # Collect metadata and write to metainfo.json

    for artifact in artifacts_image:
        if artifact == "fng-install.sh":
            install_script = artifact
        elif artifact == "license.manifest":
            licenses = artifact
        elif artifact.endswith(machine + ".tar.gz"):
            image_general = artifact
        elif artifact.endswith(machine + ".wic"):
            image_wic = artifact

    metadata = dict()

    metadata["files"] = []
    metadata["version"] = version
    metadata["machine"] = machine
    metadata["date"] = datetime.today().strftime("%Y-%m-%d")

    if install_script is not None:
        new_file = dict()
        new_file["name"] = "Install Script"
        new_file["path"] = install_script
        metadata["files"].append(new_file)

    if image_general is not None:
        new_file = dict()
        new_file["name"] = "Image"
        new_file["path"] = image_general
        metadata["files"].append(new_file)

    if image_wic is not None:
        new_file = dict()
        new_file["name"] = "SD-Card Image (WIC)"
        new_file["path"] = image_wic
        metadata["files"].append(new_file)

    if sdk is not None:
        new_file = dict()
        new_file["name"] = "SDK"
        new_file["path"] = "sdk/" + sdk + ".sh"
        metadata["files"].append(new_file)

    if licenses is not None:
        new_file = dict()
        new_file["name"] = "Licenses"
        new_file["path"] = licenses
        metadata["files"].append(new_file)

    with open(filepath, "w", encoding="utf-8") as file:
        file.write(json.dumps(metadata))


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--images-dir",
        help="""Yocto images directory""",
        dest="images_dir",
    )
    parser.add_argument(
        "--licenses-dir",
        help="""Yocto licenses directory""",
        dest="licenses_dir",
    )
    parser.add_argument(
        "--outputdir-upload",
        help="""Base directory name for uploaded artifacts""",
        dest="outputdir_upload",
    )
    parser.add_argument(
        "--outputdir-local",
        help="""Base directory for locally deployed artifacts, should contain absolut path.""",
        dest="outputdir_local",
    )
    parser.add_argument(
        "--sdk-dir",
        help="""Yocto sdk directory""",
        dest="sdk_dir",
    )
    parser.add_argument(
        "--doc-dir",
        help="""Documentation directory""",
        dest="doc_dir",
    )
    parser.add_argument(
        "--generate-fwr-articles",
        help="""Enable AlphaPlan FWR generation""",
        dest="generate_fwr_articles",
        action="store_true",
    )
    parser.set_defaults(generate_fwr_articles=False)
    args, _ = parser.parse_known_args()

    if args.outputdir_upload is None and args.outputdir_local is None:
        sys.exit(
            "ERROR: Either outputdir-local and/or outputdir-upload needs to be specified."
        )

    # Get bitbake variables from testdata.json file
    testdata_files = []
    if args.images_dir is not None:
        testdata_files += glob.glob(os.path.join(args.images_dir, "*.testdata.json"))
    if args.sdk_dir is not None:
        testdata_files += glob.glob(os.path.join(args.sdk_dir, "*.testdata.json"))

    # Debug stuff
    if not testdata_files:
        if args.images_dir is not None:
            print(args.images_dir)
            for f in glob.glob(args.images_dir + "/*"):
                print("-- ", f)
        if args.sdk_dir is not None:
            print(args.sdk_dir)
            for f in glob.glob(args.sdk_dir + "/*"):
                print("-- ", f)
        sys.exit("ERROR: no *.testdata.json file found in image or sdk dir.")

    with open(testdata_files[0], "r", encoding="utf-8") as f:
        buildvars = json.load(f)

    machine = buildvars["MACHINE"]
    version = buildvars["DISTRO_VERSION"]
    sdkname = buildvars["TOOLCHAIN_OUTPUTNAME"]
    artifacts_image = buildvars["DISTRO_IMAGES"].split()
    artifacts_all = buildvars["DISTRO_RELEASE_ARTEFACTS"].split()
    artifacts_all.append("BUILD_SRCREVS.log")

    if version.startswith("fngsystem"):
        release_name_local = version.replace("fngsystem", "FNGSystem")
        release_name = release_name_local
        # outlocal_base = "/artifacts-fngsystem"
    else:
        release_name = "GUF-Yocto-%s" % version
        release_name_local = "Yocto-%s" % version
        # outlocal_base = "/artifacts-yocto"

    # Create output directories
    if args.outputdir_upload is not None:
        output_dir = os.path.join(args.outputdir_upload, release_name)
        os.makedirs(output_dir, exist_ok=True)
    else:
        output_dir = None
    if args.outputdir_local is not None:
        outlocal_dir = os.path.join(args.outputdir_local, release_name_local)
        os.makedirs(outlocal_dir, exist_ok=True)
    else:
        outlocal_dir = None

    if args.doc_dir is not None:
        doc_files = glob.glob(os.path.join(args.doc_dir, "*.md"))
        html_files = []
        for f in doc_files:
            fout = os.path.splitext(f)[0] + ".html"
            convertmd2html(f, fout)
            html_files.append(fout)

        copy_files(doc_files + html_files, "", "", output_dir, outlocal_dir)

    if args.images_dir is not None:
        # Add some additional files to the artifacts
        for artifact in artifacts_image:
            artifacts_all.append(artifact.split(".")[0] + ".manifest")
            artifacts_all.append(artifact.split(".")[0] + ".testdata.json")

        md5sums = copy_files(
            artifacts_all, args.images_dir, machine, output_dir, outlocal_dir
        )

        # If the path for the licenses is set, we check for the list with all
        # licenses. If the list is found, we copy it to the output directory
        # and also add it to the artifacts dictionary.
        if args.licenses_dir is not None and os.path.isdir(args.licenses_dir):
            manifest = glob.glob(
                os.path.join(args.licenses_dir, "**", "license.manifest")
            )

            if manifest:
                md5sums.update(
                    copy_files(
                        ["license.manifest"],
                        os.path.dirname(manifest[0]),
                        machine,
                        output_dir,
                        outlocal_dir,
                    )
                )

                artifacts_all.append("license.manifest")

    # Generate alphaplan FWR articles
    if args.generate_fwr_articles:
        alphaplan_fwr.generate_fwr_articles(
            output_dir,
            outlocal_dir,
            machine,
            release_name_local,
            artifacts_all,
            md5sums,
        )

    # Generate metadata
    generate_metadata(
        machine,
        version,
        artifacts_all,
        sdkname,
        output_dir,
        outlocal_dir,
    )

    # Handle SDK if available
    if args.sdk_dir is not None:
        sdkfiles = glob.glob(os.path.join(args.sdk_dir, sdkname + "*"))
        copy_files(sdkfiles, "", os.path.join(machine, "sdk"), None, outlocal_dir)

    # Store pathes and other stuff in environment variable file
    with open("package.env", "w", encoding="utf-8") as env_file:
        env_file.write("VERSION={}\n".format(version))
        env_file.write("MACHINE={}\n".format(machine))
        env_file.write("LOCALDIR={}\n".format(outlocal_dir))


if __name__ == "__main__":
    main()