#!/bin/sh # -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=tcl:et:sw=4:ts=4:sts=4 # \ if test -x @prefix@/bin/port-tclsh; then exec @prefix@/bin/port-tclsh "$0" "$@"; else exec /usr/bin/tclsh "$0" "$@"; fi # $Id$ set VERSION 0.1.4 set prefix "@prefix@" # - Procedures --------------------------------------------------------------- proc composite_version {port} { return [version $port]_[revision $port][variants $port] } proc cut_leaves {{processed_leaves {}}} { global exit_status option if {[catch {set leaves [leaves]} result]} { puts stderr "Error: $result" return 1 } set to_process {} foreach leaf $leaves { set full_name [full_name $leaf] if {[lsearch -exact $processed_leaves $full_name] != -1} { continue } lappend processed_leaves $full_name lappend to_process $leaf } set total_leaves [llength $to_process] if {$total_leaves < 1} { puts "There are no new leaves to process." return $exit_status } set current_leaf 1 set flush_now 0 set to_uninstall {} foreach leaf $to_process { set composite_version [composite_version $leaf] set full_name [full_name $leaf] set name [name $leaf] set status active if {![is_active $leaf]} { set status inactive } while {1} { puts "\[Leaf $current_leaf of $total_leaves] $full_name ($status):" puts -nonewline " \[keep] / (u)ninstall / (f)lush / (a)bort: " flush stdout gets stdin action switch -glob $action { a* { puts "\nAborting port_cutleaves..." return $exit_status } f* { puts "\nFlushing any uninstallation operations...\n" set flush_now 1 } u* { puts "** $full_name will be uninstalled.\n" lappend to_uninstall $leaf } k* - "" { puts "** $full_name will be kept.\n" } default { puts "** '$action' is an invalid action.\n" continue } } break } if {$flush_now == 1} { break } incr current_leaf } if {[llength $to_uninstall] < 1} { puts "No leaves were marked for uninstallation." return $exit_status } set uninstalled [uninstall $to_uninstall] if {[llength $uninstalled] < 1} { puts "\nNo leaves were uninstalled." return 1 } puts "\nThe following ports were uninstalled:" foreach port $uninstalled { puts " $port" } puts "\nSearch for new leaves?" puts -nonewline " \[no] / (y)es: " flush stdout gets stdin choice if {[regexp {^y} $choice]} { puts {} return [cut_leaves $processed_leaves] } return $exit_status } proc exclusions {file} { global cached_exclusions if {![info exists cached_exclusions]} { if {![file exists $file]} { return -code error "'$file' does not exist." } elseif {[catch {set exclusions_file [open $file]} result]} { return -code error $result } set cached_exclusions {} foreach line [split [read -nonewline $exclusions_file] \n] { switch -regexp $line { {^\s*#} - {^$} {} default { lappend cached_exclusions $line } } } close $exclusions_file } return $cached_exclusions } proc full_name {port} { return "[name $port] @[composite_version $port]" } proc is_active {port} { return [lindex $port 4] } proc collect_build_deps {installedvar} { upvar $installedvar installed global build_deps array unset build_deps foreach i $installed { set iname [name $i] if {[catch {set res [mportlookup $iname]} result]} { puts stderr "lookup of portname $iname failed: $result" exit 1 } if {[llength $res] < 2} { continue } else { array unset portinfo array set portinfo [lindex $res 1] } foreach type {depends_fetch depends_extract depends_build} { if {[info exists portinfo($type)]} { foreach d $portinfo($type) { set build_deps([lindex [split $d :] end]) 1 } } } } } proc leaves {} { global option build_deps registry::open_dep_map if {[catch {set installed [registry::installed]} result]} { return -code error $result } elseif {[catch {set exclusions [exclusions $option(F)]} result]} { if {![regexp {does not exist} $result]} { return -code error $result } set exclusions {} } if {$option(b)} { collect_build_deps installed } set leaves {} foreach port $installed { if {${macports::registry.format} eq "receipt_sqlite"} { set regref [registry::open_entry [name $port] [version $port] [revision $port] [variants $port] [epoch $port]] } else { set regref [registry::open_entry [name $port] [version $port] [revision $port] [variants $port]] } if {![registry::property_retrieve $regref requested] && [registry::list_dependents [name $port]] eq "" && ![should_be_excluded $port $exclusions] && (!$option(b) || ![info exists build_deps([name $port])])} { lappend leaves $port } } return $leaves } proc list_leaves {} { if {[catch {set leaves [leaves]} result]} { puts stderr "Error: $result" return 1 } foreach leaf $leaves { puts [full_name $leaf] } return 0 } proc epoch {port} { return [lindex $port 5] } proc name {port} { return [lindex $port 0] } proc revision {port} { return [lindex $port 2] } proc should_be_excluded {port exclusions} { foreach exclusion $exclusions { set full_name [full_name $port] if {[string equal -nocase $exclusion $full_name] || [regexp -nocase $exclusion $full_name]} { return 1 } } return 0 } proc uninstall {ports} { global exit_status set uninstalled {} foreach port $ports { if {${macports::registry.format} eq "receipt_sqlite"} { set regref [registry::open_entry [name $port] [version $port] [revision $port] [variants $port] [epoch $port]] if {[registry::run_target $regref uninstall {}]} { lappend uninstalled [full_name $port] continue } } if {[llength [info commands "registry_uninstall::uninstall"]] == 1} { if {[llength [info commands "registry_uninstall::uninstall_composite"]] == 1} { if {[catch {registry_uninstall::uninstall [name $port] [version $port] [revision $port] [variants $port] {}} \ result]} { set exit_status 1 puts stderr "Error: $result" continue } } else { if {[catch {registry_uninstall::uninstall [name $port] [composite_version $port] {}} \ result]} { set exit_status 1 puts stderr "Error: $result" continue } } } elseif {[catch {portuninstall::uninstall [name $port] [composite_version $port] {}} \ result]} { set exit_status 1 puts stderr "Error: $result" continue } lappend uninstalled [full_name $port] } return $uninstalled } proc variants {port} { return [lindex $port 3] } proc version {port} { return [lindex $port 1] } # - Main --------------------------------------------------------------------- package require cmdline set options { { b "Don't count ports as leaves when they are only needed at build time" } { F.arg ~/.port_leaves.exclude \ "Specify a different file to read exclusions from" } { l "List leaves and exit"} { V "Display version information and exit" } } set usage "\[-b] \[-F value] \[-l] \[-t value] \[-V] \[-help] \[-?]\n\nOptions:" if {[catch {array set option [::cmdline::getoptions argv $options]}]} { puts [::cmdline::usage $options $usage] exit 1 } if {![file exists ${prefix}/bin/port-tclsh]} { if {[catch {source ${prefix}/share/macports/Tcl/macports1.0/macports_fastload.tcl} result]} { puts stderr "Error: $result" exit 1 } } package require macports if {[catch {mportinit} result]} { puts stderr "Error: $result" exit 1 } set exit_status 0 if {$option(V)} { exit [puts port_cutleaves-$VERSION] } elseif {$option(l)} { exit [list_leaves] } else { exit [cut_leaves] }