#!/usr/bin/env python3
## @file
# Copyright (c) 2023, The OCE Build Authors. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
##
"""CLI entrypoint for the build command."""
from os import getcwd, makedirs
from typing import List, Tuple, Union
import click
from ocebuild.filesystem import copy, glob, remove
from ocebuild.filesystem.cache import clear_cache, UNPACK_DIR
from ocebuild.parsers.dict import merge_dict, nested_del, nested_get
from ocebuild.pipeline.build import *
from ocebuild.pipeline.packages import *
from ocebuild.pipeline.packages import _iterate_extract_packages
#NOTE: This import was remapped from 'ocebuild_cli' to 'ocebuild.cli'.
from ocebuild.cli._lib import cli_command
from ocebuild.cli.interactive import Progress, progress_bar
from ocebuild.cli.logging import *
#NOTE: This import was remapped from 'third_party' to 'ocebuild.third_party'.
from ocebuild.third_party.cpython.pathlib import Path
[docs]def get_build_file(cwd: Union[str, Path]
) -> Tuple[dict, dict, List[str], Path, Path]:
"""Reads the build file configuration.
Args:
cwd: The current working directory.
Returns:
A tuple containing:
- The build configuration.
- The build variables.
- The build flags.
- The build file path.
- The project directory.
"""
BUILD_FILE = glob(cwd, '**/build.yml', include='**/build.yaml', first=True)
try:
if BUILD_FILE:
info(f"Found build configuration at '{BUILD_FILE.relative(cwd)}'.")
build_config, build_vars, flags = read_build_file(filepath=BUILD_FILE)
else:
error("Could not find 'build.{yml,yaml}'",
"Try running `ocebuild init` first.")
except Exception as e: #pylint: disable=broad-exception-caught
abort(f"Encountered an error while reading '{BUILD_FILE.name}': {e}",
'Check the build configuration for errors.')
else:
PROJECT_DIR = Path(BUILD_FILE.parent)
debug(f"Using '{PROJECT_DIR.relative('.')}' as the project root.")
return build_config, build_vars, flags, BUILD_FILE, PROJECT_DIR
[docs]def unpack_packages(resolvers: List[dict], project_dir: Path) -> dict:
"""Unpacks packages to a temporary directory."""
debug(f"Unpacking packages to {UNPACK_DIR}")
with Progress() as progress:
bar = progress_bar('Unpacking packages', wrap=progress)
unpacked_entries = unpack_build_entries(resolvers,
project_dir=project_dir,
# Interactive arguments
__wrapper=bar)
num_unpacked = len([k for e in unpacked_entries.values() for k in e.keys()])
if num_unpacked:
success(f'Unpacked {num_unpacked} packages from lockfile.')
return unpacked_entries
@cli_command(name='build')
@click.option("-c", "--cwd",
type=click.Path(exists=True,
file_okay=False,
readable=True,
writable=True,
path_type=Path),
help="Use the specified directory as the working directory.")
@click.option("-o", "--out",
type=click.Path(path_type=Path),
help="Use the specified directory as the output directory.")
@click.option("--clean",
is_flag=True,
help="Clean the output directory before building.")
@click.option("--update",
is_flag=True,
help="Update outdated lockfile entries before building.")
@click.option("--force",
is_flag=True,
help="Force the build even if the lockfile is up to date.")
[docs]def cli(env, cwd, out, clean, update, force):
"""Builds the project's OpenCore EFI directory."""
if not cwd: cwd = getcwd()
else: debug(f"(--cwd) Using '{cwd}' as the working directory.")
if not out: out = 'dist'
else: debug(f"(--out) Using '{out}' as the build directory.")
# Prepare the build directory
BUILD_DIR = Path(cwd, out)
debug(f"Using '{BUILD_DIR}' as the build directory.")
if clean:
debug('(--clean) Cleaning the output directory...')
try:
remove(BUILD_DIR)
except Exception: #pylint: disable=broad-exception-caught
abort(f"Failed to clean the output directory ('{BUILD_DIR}')",
'Check the output directory permissions.')
else:
makedirs(BUILD_DIR, exist_ok=True)
if not any(BUILD_DIR.iterdir()):
success(f"Cleaned the output directory at '{BUILD_DIR}'.")
# Read the build configuration
build_config, build_vars, flags, *_, PROJECT_DIR = get_build_file(cwd)
# Read the lockfile
from .lock import resolve_lockfile #pylint: disable=import-outside-toplevel
lockfile, resolvers = resolve_lockfile(cwd,
update=update,
force=force,
build_config=build_config,
project_dir=PROJECT_DIR)
# Prepend build directory to resolver paths
for e in resolvers:
e['__filepath'] = BUILD_DIR.joinpath(e['__filepath']).resolve()
# Handle skipping builds if the lockfile and build dir is up to date
has_pending_build = any(e['specifier'] != '*' and not e['__filepath'].exists()
for e in resolvers)
if not (has_pending_build or BUILD_DIR.exists()):
echo('\n[white]Nothing to build.[/white]',
'\nTry running with --update or --force to regenerate a build.',
log=False)
exit(0)
# Extract all build entries to a temporary directory
packages = unpack_packages(resolvers, project_dir=PROJECT_DIR)
opencore_pkg, extracted = extract_packages(build_vars, lockfile, resolvers,
packages=packages,
build_dir=BUILD_DIR)
# Move build entries to the build directory
extract_build_directory(opencore_pkg, extracted, build_dir=BUILD_DIR)
if extracted:
num_extracted = len([k for e in extracted.values() for k in e.keys()])
success(f"Extracted {num_extracted} build entries to '{BUILD_DIR}'.")
#TODO: Call the patch command to apply config.plist patches
__all__ = [
# Functions (5)
"get_build_file",
"unpack_packages",
"extract_packages",
"extract_build_directory",
"cli"
]