#!/bin/sh # Load shlib and modules _root="$(dirname "${0}")"; . "${_root}/lib/env.sh" want log root ######## # # Chroot handling # ######## # Prepare a chroot for work chprepare() { # Verify environment sanity [ -d "${chroot_dir}" ] && omg "${chroot_dir}: directory exists; purging" && chdestroy mount | grep -q "${chroot_dir}" && wtf "Stuff is mounted under ${chroot_dir}; cannot continue" [ -d "${seed_dir}" -a -f "${seed_dir}/COPYRIGHT" ] || wtf "Use 'makeworld' to create a root build first" [ -f "/usr/ports/UPDATING" ] || wtf "Need ports tree in /usr/ports to build" [ -f "/usr/src/sys/conf/newvers.sh" ] || omg "No base sourcetree in /usr/src" meh "Setting up chroot tree" # Just in case we're aborted partway through the prepare trap "chdestroy" exit hup int term kill # Make sure config dir exists for later save [ -d "${conf_dir}" ] || mkdir -p "${conf_dir}" # Populate initial seed mkdir -p "$(dirname "${chroot_dir}")" || wtf "chroot path create failed" cp -pR "${seed_dir}" "${chroot_dir}" || wtf "chroot seeding failed" # Create distfile cache mkdir -p "${dist_dir}" || wtf "mkdir ${dist_dir} failed" # Create mountpoint directories mkdir -p "${chroot_dir}/dev" || wtf "mkdir /dev failed" mkdir -p "${chroot_dir}/usr/src" || wtf "mkdir /usr/src failed" mkdir -p "${chroot_dir}/usr/obj" || wtf "mkdir /usr/obj failed" mkdir -p "${chroot_dir}/usr/ports" || wtf "mkdir /usr/ports failed" mkdir -p "${chroot_dir}/var/ports/distfiles" || wtf "mkdir /var/ports/distfiles failed" # Create pkg directories mkdir -p "${pkg_dir}" || wtf "mkdir ${chroot_pkg_dir} failed" mkdir -p "${bdeps_dir}" || wtf "mkdir ${chroot_bdeps_dir} failed" # Copy in cached configuration, if necessary [ -f "${conf_dir}/make.conf" ] && cp -p "${conf_dir}/make.conf" "${chroot_dir}/etc/make.conf" # If you have WITHOUT_INFO set in src.conf during buildworld/installworld, ports that need # makeinfo and install-info will fail horribly; stub them [ -f "${chroot_dir}/usr/bin/makeinfo" ] || ln -sf true "${chroot_dir}/usr/bin/makeinfo" [ -f "${chroot_dir}/usr/bin/install-info" ] || ln -sf true "${chroot_dir}/usr/bin/install-info" # Tweak make.conf to support read-only ports tree cat <> "${chroot_dir}/etc/make.conf" # Read-only ports tree DISTDIR=/var/ports/distfiles PACKAGES=/var/ports/packages WRKDIRPREFIX=/usr/obj EOF trap "" exit hup int term kill meh "Chroot tree set up in ${chroot_dir}" return 0 } # Set up chroot mounts and runtime configuration chstartup() { [ -d "${chroot_dir}" -a -f "${chroot_dir}/COPYRIGHT" -a -d "${chroot_dir}/dev" ] || wtf "Chroot not prepared" chup? && return 0 # Rollback if a problem occurs during startup trap "chshutdown" exit hup int term kill meh "Starting up chroot" # Necessary mountpoints mount -t devfs devfs "${chroot_dir}/dev" || wtf "mount /dev failed" mount -t nullfs -r /usr/src "${chroot_dir}/usr/src" || wtf "mount /usr/src failed" mount -t nullfs -r /usr/ports "${chroot_dir}/usr/ports" || wtf "mount /usr/ports failed" mount -t nullfs -w "${dist_dir}" "${chroot_dir}/var/ports/distfiles" || wtf "mount /var/ports/distfiles failed" # Chroot configuration cp -f /etc/resolv.conf "${chroot_dir}/etc/resolv.conf" || wtf "seeding /etc/resolv.conf failed" # Load port configuration, now that chroot is running port_load_config # Update ld.so.hints cheval "/etc/rc.d/ldconfig start" trap "" exit hup int term kill meh "Chroot up and running in ${chroot_dir}" return 0 } # Check if the chroot is probably ready for use chup?() { [ -d "${chroot_dir}" -a -f "${chroot_dir}/COPYRIGHT" -a -c "${chroot_dir}/dev/null" ] return $? } # Unmount all chroot directories chshutdown() { # Short-circuit if nothing is mounted mount | grep -q "${chroot_dir}" || return 0 meh "Shutting down chroot" umount "${chroot_dir}/var/ports/distfiles" umount "${chroot_dir}/usr/ports" umount "${chroot_dir}/usr/src" umount "${chroot_dir}/dev" return 0 } # Remove all chroot contents chdestroy() { chshutdown meh "Destroying chroot" chflags -R noschg "${chroot_dir}" || wtf "noschg failed" rm -Rf "${chroot_dir}" || wtf "chroot removal failed" return 0 } # Evaluate a command line within the chroot cheval() { chup? || wtf "Chroot not ready" chroot "${chroot_dir}" env -i ${chroot_env} /bin/sh -c "cd ${chroot_pkg_dir}; ${*}" return $? } # Run chrooted make chmake() { local port="/usr/ports/${1##/usr/ports/}" shift cheval "make -C ${port} ${*}" return $? } ######## # # Port Dependency Tracking # ######## # Translate port origin to package name port2pkg() { while [ "${1}" ] do chmake "${1}" -V PKGNAME shift done } # Build dependencies (shallow, recursive, package) port_bdeps() { while [ "${1}" ] do chmake "${1}" build-depends-list | sed -e 's#^/usr/ports/##' shift done } port_all_bdeps() { # Clear cache if first value isn't '-r' [ "${1}" = '-r' ] && shift || { : > "${_port_all_bdeps_cache}"; : > "${_port_all_rdeps_cache}"; } # rdeps for rdeps are rdeps, bdeps for rdeps are bdeps, rdeps for bdeps are bdeps; thus: ( port_bdeps "${@}" | while read port do [ "${VERBOSE_CACHE}" ] && logf "**** bdep cache %s: '%s'\n" "$(grep -q "${port}" \ "${_port_all_bdeps_cache}" && echo "hit" || echo "miss")" "${port}" >&2 if ! grep -q "${port}" "${_port_all_bdeps_cache}" then echo "${port}" >> "${_port_all_bdeps_cache}" echo "${port}" port_all_bdeps -r "${port}" port_all_rdeps -r "${port}" fi done port_all_rdeps -r "${@}" | while read port do [ "${VERBOSE_CACHE}" ] && logf "**** bdep cache %s: '%s'\n" "$(grep -q "${port}" \ "${_port_all_bdeps_cache}" && echo "hit" || echo "miss")" "${port}" >&2 if ! grep -q "${port}" "${_port_all_bdeps_cache}" then echo "${port}" >> "${_port_all_bdeps_cache}" port_all_bdeps -r "${port}" fi done ) | sort | uniq } pkg_bdeps() { port2pkg $(port_all_bdeps "${@}") } # Runtime dependencies (shallow, recursive, package) port_rdeps() { while [ "${1}" ] do chmake "${1}" run-depends-list | sed -e 's#^/usr/ports/##' shift done } port_all_rdeps() { # Clear cache if first value isn't '-r' [ "${1}" = '-r' ] && shift || { : > "${_port_all_rdeps_cache}"; } port_rdeps "${@}" | while read port do [ "${VERBOSE_CACHE}" ] && logf "**** rdep cache %s: '%s'\n" "$(grep -q "${port}" \ "${_port_all_rdeps_cache}" && echo "hit" || echo "miss")" "${port}" >&2 if ! grep -q "${port}" "${_port_all_rdeps_cache}" then echo "${port}" >> "${_port_all_rdeps_cache}" echo "${port}" port_all_rdeps -r "${port}" fi done | sort | uniq } pkg_rdeps() { port2pkg $(port_all_rdeps "${@}") } # All dependencies (shallow, recursive, package) port_deps() { while [ "${1}" ] do chmake "${1}" all-depends-list | sed -e 's#^/usr/ports/##' shift done } port_all_deps() { port_deps "${@}" | while read port do echo "${port}" port_deps "${port}" done | sort | uniq } pkg_deps() { port2pkg $(port_all_deps "${@}") } # Dump a list of leaf ports from a working system # Leaf ports are ports that have nothing depending upon them # These are generally the top-level ports; everything else should # be pulled in by them; on a system that has had updates applied # this may pick up orphaned packages as well, so try cutleaves? leaf_ports() { ( cd /var/db/pkg; ls -1 ) | while read pkg do pkg_info -Rq "${pkg}" | grep -q '' || pkg_info -oq "${pkg}" done | sort } # Display a tree of all build dependencies (and # any runtime dependencies those build dependencies # depend upon). Individual ports may appear more than # once, as it is a tree and not a list. port_bdep_tree() { port="${1##/usr/ports/}" printf "%${2}s%s\n" "" "${port}" ( chmake "${port}" build-depends-list [ "${2:-0}" -gt 0 ] && chmake "${port}" run-depends-list ) | sort -u | while read port do port_bdep_tree "${port}" $(( ${2:-0} + 1 )) done } # Display a tree of all runtime dependencies. Individual # ports may appear more than once, as it is a tree and # not a list. port_rdep_tree() { port="${1##/usr/ports/}" printf "%${2}s%s\n" "" "${port}" chmake "${port}" run-depends-list | sort -u | while read port do port_rdep_tree "${port}" $(( ${2:-0} + 1 )) done } ######## # # Port Configuration Handling # ######## # Run make config on a list of ports port_config() { while [ "${1}" ] do meh "port config ${1}" chmake "${1}" config < /dev/tty shift done } # Make config-conditional for a list of ports, and all dependencies port_config_recursive() { # Clear cache if first value isn't '-r' [ "${1}" = '-r' ] && shift || _port_config_recursive_cache="" while [ "${1}" ] do # Do not use config-recursive because it computes the depchain first; # instead, manually compute the depchain after each config to ensure the # proper depchain is followed. Use config-conditional to avoid dialog # if the config is already complete. Also use a cache to avoid re-config # and re-recurse on previously handled port branches. if echo "${_port_config_recursive_cache}" | grep -qv " ${1} " then meh "port config-recursive ${1}" chmake "${1}" config-conditional < /dev/tty port_config_recursive -r $(port_deps "${1}") _port_config_recursive_cache="${_port_config_recursive_cache} ${1} " fi shift done } # Config cache unset _port_config_recursive_cache # Remove saved config for a list of ports port_rmconfig() { while [ "${1}" ] do meh "port rmconfig ${1}" chmake "${1}" rmconfig shift done } # Remove saved config for a list of ports and all dependencies port_rmconfig_recursive() { meh "port rmconfig-recursive ${*}" port_rmconfig $(echo "${@}" $(port_all_deps "${@}") | sort | uniq) } # Obliterate saved configuration for all ports port_rmconfig_all() { meh "port rmconfig-all" cheval "cd /var/db/ports; find . -name 'options' -delete; find . -type d | xargs rmdir 2>/dev/null" } # Restore port build options from directory port_load_config() { [ -d "${conf_dir}/port.options" ] || return 0 meh "port load-config" ( cd "${conf_dir}/port.options"; find . | cpio -oHnewc ) | cheval "cd /var/db/ports; cpio -i" || wtf "port load-config failed" } # Dump port build options to directory port_save_config() { meh "port save-config" mkdir -p "${conf_dir}/port.options.tmp" cheval "cd /var/db/ports; find . -type d -o -type f -name options | cpio -oHnewc" | ( cd "${conf_dir}/port.options.tmp"; cpio -i ) || wtf "port safe-config failed" rm -Rf "${conf_dir}/port.options" && mv -f "${conf_dir}/port.options.tmp" "${conf_dir}/port.options" || wtf "port save-config atomic commit failed" } ######## # # Port distfile handling # ######## # Recursively retrieve distfiles port_fetch_recursive() { while [ "${1}" ] do meh "fetch-recursive ${1}" chmake "${1}" fetch-recursive shift done } ######## # # Port building and packaging # ######## # Copy in and install dependency packages port_load_deps() { local port="${1}" for pkg in $(port2pkg $(port_all_deps "${port}")) do cp -f "${final_bdeps_dir}/${pkg}.tbz" "${bdeps_dir}" 2>/dev/null && meh "Loading dependent ${pkg}" done if ls "${bdeps_dir}"/*.tbz >/dev/null 2>&1 then meh "Installing dependencies" cheval "cd ${chroot_bdeps_dir}; pkg_add -F *" || wtf "port_load_deps ${port} failed" fi } # Build and install a port port_build() { local port="${1}" meh "Building ${port}" chmake "${port}" clean build install clean || wtf "port_build ${port} failed" } # Package a port port_package() { local port="${1}" meh "Creating rdep package tree for ${port}" cheval "pkg_create -Rvb $(port2pkg "${port}")" || wtf "port_package ${port} failed" } # Package port build dependencies, unless they're already run deps port_stash_bdeps() { meh "Stashing unsaved bdeps" # This doesn't work well, because there can be bdeps that aren't listed as bdeps (bison) #for pkg in $(pkg_bdeps "${1}") #do # [ ! -f "${pkg_dir}/${pkg}.tbz" ] && cheval "cd ${chroot_bdeps_dir}; pkg_create -vb ${pkg}" #done # # Instead, just save everything that's not already in rdeps as bdeps for pkg in $(cheval pkg_info | awk '{print $1}') do if [ ! -f "${pkg_dir}/${pkg}.tbz" -a ! -f "${bdeps_dir}/${pkg}.tbz" ] then cheval "cd ${chroot_bdeps_dir}; pkg_create -vb ${pkg}" || wtf "port_stash_bdeps failed" fi done } # Copy generated packages out of tree pkg_final() { meh "Moving created packages to repo" mkdir -p "${final_pkg_dir}" ls "${pkg_dir}"/*.tbz >/dev/null 2>&1 && mv -f "${pkg_dir}"/*.tbz "${final_pkg_dir}" mkdir -p "${final_bdeps_dir}" ls "${bdeps_dir}"/*.tbz >/dev/null 2>&1 && mv -f "${bdeps_dir}"/*.tbz "${final_bdeps_dir}" # link everything into ${bdeps_dir} so we can find it easily later ( cd "${final_pkg_dir}"; find . -type f | cpio -plu --quiet "${final_bdeps_dir}" ) } # Delete all installed packages (hope you saved them first) pkg_delete_all() { meh "Clearing out installed packages" cheval "pkg_delete -f \*" || wtf "pkg_delete_all failed" } ######## # # All of the above? # ######## # Execute a complete port build, using prebuilt packages to fulfill dependencies when available # Be sure to chsetup and populate your config before running! chport() { while [ "${1}" ] do local port="${1}" meh "config-recursive" port_config_recursive "${port}" meh "fetch-recursive" port_fetch_recursive "${port}" meh "load-deps" port_load_deps "${port}" meh "build" port_build "${port}" meh "package" port_package "${port}" meh "stash-deps" port_stash_bdeps meh "final" pkg_final meh "delete-all" pkg_delete_all shift done } ######## # # Configuration variable setup # ######## ARCH="${ARCH:-$(uname -m)}" CONF="${CONF:-GENERIC}" # Root directory of makepkg ROOT="$(realpath "$(dirname "${0}")/..")" # Location of targets TARGETS="${ROOT}/targets" # Base directory for selected target base_dir="${TARGETS}/${ARCH}/${CONF}" # Link to appropriate world world_dir="${base_dir}/world" # Verify that world points to a proper world if [ ! -d "${world_dir}" ] then omg "World link is not appropriate; defaulting to ${ARCH}/GENERIC" world_dir="${ROOT}/worlds/${ARCH}/GENERIC" fi # Directory holding configuration conf_dir="${base_dir}/config" # Root tree for chroot seeding seed_dir="${world_dir}/root" # Directory where distfiles will be stored between builds (common to all targets) dist_dir="${ROOT}/seed/distfiles" # Final directory for built packages (Outside chroot) final_pkg_dir="${base_dir}/pkg" final_bdeps_dir="${base_dir}/bdeps" # Chroot directory chroot_dir="${base_dir}/chroot" # Package directories (must be under ${chroot_dir}) pkg_dir="${chroot_dir}/pkg" bdeps_dir="${pkg_dir}/bdeps" # Compute in-chroot pkg and bdeps dirs chroot_pkg_dir="${pkg_dir##${chroot_dir}}" chroot_bdeps_dir="${bdeps_dir##${chroot_dir}}" # Cache files to speed up recursive bdep/rdep scanning _port_all_bdeps_cache="${chroot_dir}/tmp/_port_all_bdeps_cache" _port_all_rdeps_cache="${chroot_dir}/tmp/_port_all_rdeps_cache" # Chroot environment chroot_env=" USER=root HOME=/root LOGNAME=root PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin SHELL=/bin/sh TERM=${TERM} " help() { cat < EOF } # Blind passthru for testing [ "${#}" ] && "${@}" # Todo: Clean up entrypoint to support proper options # Todo: Add methods to autoprocess a ports.lst file to produce packages # Todo: Unify chroot handling with makeworld