#!/bin/sh # Load shlib and modules _root="$(dirname "${0}")"; . "${_root}/lib/sh/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 chup && port_save_config || meh "Not saving port config" 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 || { kvs_unset_all 'port_all_bdeps'; kvs_unset_all 'port_all_rdeps'; } # 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" "$(kvs_has_key 'port_all_bdeps' "${port}" && echo "hit" || echo "miss")" "${port}" >&2 if ! kvs_has_key 'port_all_bdeps' "${port}" then kvs_set 'port_all_bdeps' "${port}" "" 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" "$(kvs_has_key 'port_all_bdeps' "${port}" && echo "hit" || echo "miss")" "${port}" >&2 if ! kvs_has_key 'port_all_bdeps' "${port}" then kvs_set 'port_all_bdeps' "${port}" "" 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 || kvs_unset_all 'port_all_rdeps' port_rdeps "${@}" | while read port do [ "${VERBOSE_CACHE}" ] && logf "**** rdep cache %s: '%s'\n" "$(kvs_has_key 'port_all_rdeps' "${port}" && echo "hit" || echo "miss")" "${port}" >&2 if ! kvs_has_key 'port_all_rdeps' "${port}" then kvs_set 'port_all_rdeps' "${port}" 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() { # Clear cache if first value isn't '-r' [ "${1}" = '-r' ] && shift || { [ ${VERBOSE_CACHE} ] && logf "**** bdep_tree cache cleared\n" kvs_unset_all 'port_bdep_tree' } port="${1##/usr/ports/}" printf "%${2}s%s\n" "" "${port}" [ "${VERBOSE_CACHE}" ] && logf "**** bdep_tree cache %s: '%s'\n" "$(kvs_has_key 'port_bdep_tree' "${port}" && echo "hit" || echo "miss")" "${port}" >&2 if ! kvs_has_key 'port_bdep_tree' "${port}" then ( chmake "${port}" build-depends-list chmake "${port}" run-depends-list ) | sort -u | while read port do port_bdep_tree -r "${port}" $(( ${2:-0} + 1 )) kvs_set 'port_bdep_tree' "${port}" done else printf "%${2}s %s\n" "" "..." fi } # Display a tree of all runtime dependencies. Individual # ports may appear more than once, as it is a tree and # not a list. # Cache already-visted branches into ... to eliminate recursion delays # (x11-drivers/xorg-drivers computes forever!) port_rdep_tree() { # Clear cache if first value isn't '-r' [ "${1}" = '-r' ] && shift || { [ ${VERBOSE_CACHE} ] && logf "**** rdep_tree cache cleared\n" kvs_unset_all 'port_rdep_tree' } port="${1##/usr/ports/}" printf "%${2}s%s\n" "" "${port}" [ "${VERBOSE_CACHE}" ] && logf "**** rdep_tree cache %s: '%s'\n" "$(kvs_has_key 'port_rdep_tree' "${port}" && echo "hit" || echo "miss")" "${port}" >&2 if ! kvs_has_key 'port_rdep_tree' "${port}" then chmake "${port}" run-depends-list | sort -u | while read port do port_rdep_tree -r "${port}" $(( ${2:-0} + 1 )) kvs_set 'port_rdep_tree' "${port}" done else printf "%${2}s %s\n" "" "..." fi } ######## # # 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 || kvs_unset_all 'port_config_recursive' 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 ! kvs_has_key 'port_config_recursive' "${1}" then meh "port config-recursive ${1}" chmake "${1}" config-conditional < /dev/tty port_config_recursive -r $(port_deps "${1}") kvs_set 'port_config_recursive' "${1}" fi shift done } # 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" rm -Rf "${conf_dir}/port.options.tmp" "${conf_dir}/port.options.old" mkdir -p "${conf_dir}/port.options" "${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" mv -f "${conf_dir}/port.options" "${conf_dir}/port.options.old" && \ mv -f "${conf_dir}/port.options.tmp" "${conf_dir}/port.options" && \ rm -Rf "${conf_dir}/port.options.old" || \ 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 # ######## # Recursively compute all dependencies to load; avoid loading packages whose # depgraph is incomplete, since that can cause strange behaviour at build time port_available_deps_recursive() { local port="${1}" local dependency local good=good if kvs_has_key 'port_available_deps' "${port}" then good="$(kvs_get 'port_available_deps' "${port}")" #echo "S==> Skipping ${port} (${good:-not good})" >&2 else #echo "D==> Port ${port} deps: $(port_deps "${port}" | sort | tr '\n' ' ')" >&2 for dependency in $(port_deps "${port}" | sort) do #echo "C==> Computing ${dependency}" >&2 # Recurse into dependency port_available_deps_recursive "${dependency}" || unset good done local pkgfile="${final_bdeps_dir}/$(port2pkg "${port}").tbz" [ "${good}" -a -f "${pkgfile}" ] && echo "${port}" || unset good kvs_set 'port_available_deps' "${port}" "${good}" #echo "R==> ${port} is ${good:-not good}" fi [ "${good}" ] } port_available_deps() { local port="${1}" kvs_unset_all 'port_available_deps' port_available_deps_recursive "${port}" | grep -v "^${port}$" | sort } # Copy in and install dependency packages # Missing dependencies are not fatal, since a port build will rebuild them anyways port_load_deps() { local port="${1}" # Install portmaster: it's needed to clean up dependencies after an update build portmaster_port="ports-mgmt/portmaster" portmaster=$(port2pkg "${portmaster_port}") if [ -f "${final_bdeps_dir}/${portmaster}.tbz" ] then cp -f "${final_bdeps_dir}/${portmaster}.tbz" "${bdeps_dir}" 2>/dev/null && meh "Loading ${portmaster}" else meh "No portmaster package exists; building one" chmake "${portmaster_port}" BATCH=yes clean build install clean || wtf "port_build ${portmaster_port} failed" fi # And now for the dependencies for pkg in $(port2pkg $(port_available_deps "${port}" | sort -u)) do cp -f "${final_bdeps_dir}/${pkg}.tbz" "${bdeps_dir}" 2>/dev/null && meh "Loading dependent ${pkg}" done # Install all selected packages, ignoring already-installed packages and missing dependencies if ls "${bdeps_dir}"/*.tbz >/dev/null 2>&1 then meh "Installing dependencies" cheval "cd ${chroot_bdeps_dir}; pkg_add -Ff *" || wtf "port_load_deps ${port} failed" rm -f "${bdeps_dir}"/*.tbz fi } # Build and install a port port_build() { local port="${1}" meh "Building ${port}" chmake "${port}" clean build deinstall install clean || wtf "port_build ${port} failed" } # Package a port port_package() { local port="${1}" meh "Cleaning up dependency tree" cheval "portmaster --check-depends" 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) # Actually, that was due to a bug in port_all_bdeps; but I like the other solution better anyways #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" } # Fix up dependency information for already-built packages pkg_fixdeps() { while [ "${1}" ] do local port="${1}" meh "Fixing up dependency information for ${port}" # Load dependencies port_load_deps "${port}" # Load package pkg="$(port2pkg "${port}")" cp -f "${final_bdeps_dir}/${pkg}.tbz" "${bdeps_dir}" 2>/dev/null && meh "Loading package ${pkg}" cheval "cd ${chroot_bdeps_dir}; pkg_add -Ff ${pkg}.tbz" || wtf "Installing ${port} failed" # Fixup and stash package port_package "${port}" # Stash fixed bdeps port_stash_bdeps # Save them all pkg_final # Delete them all pkg_delete_all shift done } ######## # # 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() { [ "${#}" -gt 0 ] || set -- $(cat ${conf_dir}/port.lst) # Stash the current list of ports kvs_unset_all 'chport' kvs_set 'chport' 'port.lst' "${*}" # Complete all config and fetch steps first for step in port_config_recursive port_fetch_recursive do set -- $(kvs_get 'chport' 'port.lst') while [ "${1}" ] do local port="${1}" meh "=> Step ${step}" ${step} ${port} shift done done # Now process the remaining steps set -- $(kvs_get 'chport' 'port.lst') while [ "${1}" ] do local port="${1}" meh "load-deps" port_load_deps "${port}" meh "build" # Avoid building ports-mgmt/portmaster because port_load_deps already has [ "${port}" = "ports-mgmt/portmaster" ] || 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}}" # 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} " # Configure and load kvs kvs="${chroot_dir}/.makepkg.kvs" mkdir -p "$(dirname "${kvs}")" want kvs 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