mirror of
https://github.com/truenas/scale-build.git
synced 2025-12-20 02:49:28 +00:00
168 lines
6.2 KiB
Python
168 lines
6.2 KiB
Python
import logging
|
|
import os
|
|
import queue
|
|
import shutil
|
|
import threading
|
|
|
|
from toposort import toposort
|
|
|
|
from .bootstrap.bootstrapdir import PackageBootstrapDirectory
|
|
from .clean import clean_bootstrap_logs
|
|
from .config import PARALLEL_BUILD, PKG_DEBUG
|
|
from .packages.order import get_initialized_packages, get_to_build_packages
|
|
from .utils.logger import get_logger
|
|
from .utils.paths import LOG_DIR, PKG_DIR, PKG_LOG_DIR
|
|
from .utils.run import interactive_run, run
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
APT_LOCK = threading.Lock()
|
|
PACKAGE_BUILD_LOCK = threading.Lock()
|
|
|
|
|
|
def update_queue(package_queue, to_build_orig, failed, in_progress, built):
|
|
if failed:
|
|
# If we have failure(s), there is no point in continuing
|
|
return
|
|
|
|
to_build = {k: v for k, v in to_build_orig.items()}
|
|
to_remove = set()
|
|
for pkg_name, package in in_progress.items():
|
|
for child in package.children:
|
|
to_remove.add(child)
|
|
|
|
for rm in to_remove:
|
|
to_build.pop(rm, None)
|
|
|
|
deps_mapping = {
|
|
p.name: {d for d in p.build_time_dependencies() if d not in built} for p in list(to_build.values())
|
|
}
|
|
sorted_ordering = [list(deps) for deps in toposort(deps_mapping)]
|
|
|
|
for item in filter(
|
|
lambda i: i in to_build and i not in in_progress and i not in package_queue.queue and i not in built,
|
|
sorted(sorted_ordering[0], key=lambda k: to_build_orig[k].batch_priority) if sorted_ordering else []
|
|
):
|
|
package_queue.put(to_build_orig.pop(item))
|
|
|
|
|
|
def build_package(package_queue, to_build, failed, in_progress, built):
|
|
while True:
|
|
if not failed and (to_build or package_queue.queue):
|
|
try:
|
|
package = package_queue.get(timeout=5)
|
|
except queue.Empty:
|
|
package = None
|
|
else:
|
|
in_progress[package.name] = package
|
|
else:
|
|
if PKG_DEBUG:
|
|
logger.debug('Thread exiting')
|
|
break
|
|
|
|
if package:
|
|
try:
|
|
logger.debug('Building %r package', package.name)
|
|
package.delete_overlayfs()
|
|
package.setup_chroot_basedir()
|
|
package.make_overlayfs()
|
|
with APT_LOCK:
|
|
package.clean_previous_packages()
|
|
shutil.copytree(PKG_DIR, package.dpkg_overlay_packages_path)
|
|
package._build_impl()
|
|
except Exception as e:
|
|
logger.error('Failed to build %r package', package.name)
|
|
failed[package.name] = {'package': package, 'exception': e}
|
|
break
|
|
else:
|
|
with APT_LOCK:
|
|
package.logger.debug('Building local APT repo Packages.gz...')
|
|
run(
|
|
f'cd {PKG_DIR} && dpkg-scanpackages . /dev/null | gzip -9c > Packages.gz',
|
|
shell=True, logger=package.logger
|
|
)
|
|
in_progress.pop(package.name)
|
|
built[package.name] = package
|
|
logger.info(
|
|
'Successfully built %r package (Remaining %d packages)', package.name,
|
|
len(to_build) + package_queue.qsize() + len(in_progress)
|
|
)
|
|
|
|
with PACKAGE_BUILD_LOCK:
|
|
if not package:
|
|
update_queue(package_queue, to_build, failed, in_progress, built)
|
|
|
|
|
|
def build_packages():
|
|
clean_bootstrap_logs()
|
|
_build_packages_impl()
|
|
|
|
|
|
def _build_packages_impl():
|
|
package_logger = get_logger('build_packages', 'build_packages.log', 'w')
|
|
logger.info('Building packages (%s/build_packages.log)', LOG_DIR)
|
|
logger.debug('Setting up bootstrap directory')
|
|
|
|
with PackageBootstrapDirectory(package_logger) as p:
|
|
p.setup()
|
|
|
|
logger.debug('Successfully setup bootstrap directory')
|
|
|
|
shutil.rmtree(PKG_LOG_DIR, ignore_errors=True)
|
|
os.makedirs(PKG_LOG_DIR)
|
|
|
|
all_packages = get_initialized_packages()
|
|
to_build = get_to_build_packages(all_packages)
|
|
package_queue = queue.Queue()
|
|
in_progress = {}
|
|
failed = {}
|
|
built = {p: all_packages[p] for p in set(all_packages) - set(to_build)}
|
|
if built:
|
|
logger.debug('%d package(s) do not need to be rebuilt (%s)', len(built), ','.join(built))
|
|
logger.debug('Going to build %d package(s): %s', len(to_build), ','.join(to_build))
|
|
no_of_tasks = PARALLEL_BUILD if len(to_build) >= PARALLEL_BUILD else len(to_build)
|
|
update_queue(package_queue, to_build, failed, in_progress, built)
|
|
logger.debug('Creating %d parallel task(s)', no_of_tasks)
|
|
threads = [
|
|
threading.Thread(
|
|
name=f'build_packages_thread_{i + 1}', target=build_package,
|
|
args=(package_queue, to_build, failed, in_progress, built)
|
|
) for i in range(no_of_tasks)
|
|
]
|
|
for thread in threads:
|
|
thread.start()
|
|
for thread in threads:
|
|
thread.join()
|
|
|
|
if failed:
|
|
logger.error('Failed to build %r package(s)', ', '.join(failed))
|
|
try:
|
|
if PKG_DEBUG:
|
|
logger.debug(
|
|
'Please specify name or index of package to debug ( shell access would be provided to failed '
|
|
'package\'s environment ) from following.'
|
|
)
|
|
while True:
|
|
data = input(
|
|
'\n'.join(
|
|
[f'{i+1}) {k}' for i, k in enumerate(failed)]
|
|
) + '\n\nPlease type "exit" when done.\n'
|
|
)
|
|
if data in ('exit', 'e'):
|
|
logger.debug('Exiting debug session')
|
|
break
|
|
elif data.isdigit() and not (1 <= int(data) <= len(failed)):
|
|
logger.debug('Please provide valid index value')
|
|
elif not data.isdigit() and data not in failed:
|
|
logger.debug('Please provide valid package name')
|
|
else:
|
|
package = failed[data]['package'] if data in failed else list(failed.values())[int(data) - 1]
|
|
interactive_run(package.debug_command)
|
|
finally:
|
|
for p in failed.values():
|
|
p['package'].delete_overlayfs()
|
|
|
|
else:
|
|
logger.info('Success! Done building packages')
|