source: trunk/base/src/macports1.0/macports.tcl @ 146724

Last change on this file since 146724 was 146724, checked in by petr@…, 4 years ago

base: fix more passing of signals in macports.tcl

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 209.7 KB
Line 
1# -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:filetype=tcl:et:sw=4:ts=4:sts=4
2# macports.tcl
3# $Id: macports.tcl 146724 2016-03-15 22:07:02Z petr@macports.org $
4#
5# Copyright (c) 2002 - 2003 Apple Inc.
6# Copyright (c) 2004 - 2005 Paul Guyot, <pguyot@kallisys.net>.
7# Copyright (c) 2004 - 2006 Ole Guldberg Jensen <olegb@opendarwin.org>.
8# Copyright (c) 2004 - 2005 Robert Shaw <rshaw@opendarwin.org>
9# Copyright (c) 2004 - 2013 The MacPorts Project
10# All rights reserved.
11#
12# Redistribution and use in source and binary forms, with or without
13# modification, are permitted provided that the following conditions
14# are met:
15# 1. Redistributions of source code must retain the above copyright
16#    notice, this list of conditions and the following disclaimer.
17# 2. Redistributions in binary form must reproduce the above copyright
18#    notice, this list of conditions and the following disclaimer in the
19#    documentation and/or other materials provided with the distribution.
20# 3. Neither the name of Apple Inc. nor the names of its contributors
21#    may be used to endorse or promote products derived from this software
22#    without specific prior written permission.
23#
24# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
28# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34# POSSIBILITY OF SUCH DAMAGE.
35#
36package provide macports 1.0
37package require macports_dlist 1.0
38package require macports_util 1.0
39package require diagnose 1.0
40package require reclaim 1.0
41package require Tclx
42
43namespace eval macports {
44    namespace export bootstrap_options user_options portinterp_options open_mports ui_priorities
45    variable bootstrap_options "\
46        portdbpath binpath auto_path extra_env sources_conf prefix portdbformat \
47        portarchivetype portautoclean \
48        porttrace portverbose keeplogs destroot_umask variants_conf rsync_server rsync_options \
49        rsync_dir startupitem_type startupitem_install place_worksymlink xcodeversion xcodebuildcmd \
50        configureccache ccache_dir ccache_size configuredistcc configurepipe buildnicevalue buildmakejobs \
51        applications_dir frameworks_dir developer_dir universal_archs build_arch macosx_sdk_version macosx_deployment_target \
52        macportsuser proxy_override_env proxy_http proxy_https proxy_ftp proxy_rsync proxy_skip \
53        master_site_local patch_site_local archive_site_local buildfromsource \
54        revupgrade_autorun revupgrade_mode revupgrade_check_id_loadcmds \
55        host_blacklist preferred_hosts sandbox_enable delete_la_files cxx_stdlib \
56        packagemaker_path default_compilers pkg_post_unarchive_deletions ui_interactive"
57    variable user_options {}
58    variable portinterp_options "\
59        portdbpath porturl portpath portbuildpath auto_path prefix prefix_frozen portsharepath \
60        registry.path registry.format user_home user_path \
61        portarchivetype archivefetch_pubkeys portautoclean porttrace keeplogs portverbose destroot_umask \
62        rsync_server rsync_options rsync_dir startupitem_type startupitem_install place_worksymlink macportsuser \
63        configureccache ccache_dir ccache_size configuredistcc configurepipe buildnicevalue buildmakejobs \
64        applications_dir current_phase frameworks_dir developer_dir universal_archs build_arch \
65        os_arch os_endian os_version os_major os_minor os_platform macosx_version macosx_sdk_version macosx_deployment_target \
66        packagemaker_path default_compilers sandbox_enable delete_la_files cxx_stdlib \
67        pkg_post_unarchive_deletions $user_options"
68
69    # deferred options are only computed when needed.
70    # they are not exported to the trace thread.
71    # they are not exported to the interpreter in system_options array.
72    variable portinterp_deferred_options "xcodeversion xcodebuildcmd developer_dir"
73
74    variable open_mports {}
75
76    variable ui_priorities "error warn msg notice info debug any"
77    variable current_phase main
78
79    variable ui_prefix "---> "
80}
81
82##
83# Return the version of MacPorts you are running
84#
85# This proc never fails and always returns the current version in the format
86# major.minor.patch. Note that the value of patch will not be meaningful for
87# trunk releases, but we guarantee that it will compare to be greater than any
88# released versions from the same major.minor.x series. You should use the
89# MacPorts-provided Tcl extension "vercmp" to do version number comparisons on
90# the return value of this function.
91proc macports::version {} {
92    return ${macports::autoconf::macports_version}
93}
94
95# Provided UI instantiations
96# For standard messages, the following priorities are defined
97#     debug, info, msg, warn, error
98# Clients of the library are expected to provide ui_prefix and ui_channels with
99# the following prototypes.
100#     proc ui_prefix {priority}
101#     proc ui_channels {priority}
102# ui_prefix returns the prefix for the messages, if any.
103# ui_channels returns a list of channels to output the message to, empty for
104#     no message.
105# if these functions are not provided, defaults are used.
106# Clients of the library may optionally provide ui_init with the following
107# prototype.
108#     proc ui_init {priority prefix channels message}
109# ui_init needs to correctly define the proc ::ui_$priority {message} or throw
110# an error.
111# if this function is not provided or throws an error, default procedures for
112# ui_$priority are defined.
113
114# ui_options accessor
115proc macports::ui_isset {val} {
116    if {[info exists macports::ui_options($val)]} {
117        return [string is true -strict $macports::ui_options($val)]
118    }
119    return 0
120}
121
122
123# global_options accessor
124proc macports::global_option_isset {val} {
125    if {[info exists macports::global_options($val)]} {
126        return [string is true -strict $macports::global_options($val)]
127    }
128    return 0
129}
130
131proc macports::init_logging {mport} {
132    global macports::channels macports::portdbpath
133
134    if {[getuid] == 0 && [geteuid] != 0} {
135        seteuid 0; setegid 0
136    }
137    if {[catch {macports::ch_logging $mport} err]} {
138        ui_debug "Logging disabled, error opening log file: $err"
139        return 1
140    }
141    return 0
142}
143proc macports::ch_logging {mport} {
144    global ::debuglog ::debuglogname
145
146    set portname [_mportkey $mport subport]
147    set portpath [_mportkey $mport portpath]
148
149    ui_debug "Starting logging for $portname"
150
151    set logname [macports::getportlogpath $portpath $portname]
152    file mkdir $logname
153    set logname [file join $logname main.log]
154
155    set ::debuglogname $logname
156
157    # Truncate the file if already exists
158    set ::debuglog [open $::debuglogname w]
159    puts $::debuglog version:1
160}
161proc macports::push_log {mport} {
162    global ::logstack ::logenabled ::debuglog ::debuglogname
163    if {![info exists ::logenabled]} {
164        if {[macports::init_logging $mport] == 0} {
165            set ::logenabled yes
166            set ::logstack [list [list $::debuglog $::debuglogname]]
167            return
168        } else {
169            set ::logenabled no
170        }
171    }
172    if {$::logenabled} {
173        if {[getuid] == 0 && [geteuid] != 0} {
174            seteuid 0; setegid 0
175        }
176        if {[catch {macports::ch_logging $mport} err]} {
177            ui_debug "Logging disabled, error opening log file: $err"
178            return
179        }
180        lappend ::logstack [list $::debuglog $::debuglogname]
181    }
182}
183
184proc macports::pop_log {} {
185    global ::logenabled ::logstack ::debuglog ::debuglogname
186    if {![info exists ::logenabled]} {
187        return -code error "pop_log called before push_log"
188    }
189    if {$::logenabled && [llength $::logstack] > 0} {
190        close $::debuglog
191        set ::logstack [lreplace $::logstack end end]
192        if {[llength $::logstack] > 0} {
193            set top [lindex $::logstack end]
194            set ::debuglog [lindex $top 0]
195            set ::debuglogname [lindex $top 1]
196        } else {
197            unset ::debuglog
198            unset ::debuglogname
199        }
200    }
201}
202
203proc set_phase {phase} {
204    global macports::current_phase
205    set macports::current_phase $phase
206    if {$phase ne "main"} {
207        set cur_time [clock format [clock seconds] -format  {%+}]
208        ui_debug "$phase phase started at $cur_time"
209    }
210}
211
212proc ui_message {priority prefix args} {
213    global macports::channels ::debuglog macports::current_phase
214
215    #
216    # validate $args
217    #
218    switch [llength $args] {
219       0 - 1 {}
220       2 {
221           if {[lindex $args 0] ne "-nonewline"} {
222               set hint "error: when 4 arguments are given, 3rd must be \"-nonewline\""
223               error "$hint\nusage: ui_message priority prefix ?-nonewline? string"
224           }
225       }
226       default {
227           set hint "error: too many arguments specified"
228           error "$hint\nusage: ui_message priority prefix ?-nonewline? string"
229       }
230    } 
231
232    foreach chan $macports::channels($priority) {
233        if {[lindex $args 0] eq "-nonewline"} {
234            puts -nonewline $chan $prefix[lindex $args 1]
235        } else {
236            puts $chan $prefix[lindex $args 0]
237        }
238    }
239
240    if {[info exists ::debuglog]} {
241        set chan $::debuglog
242        if {[info exists macports::current_phase]} {
243            set phase $macports::current_phase
244        }
245        set strprefix ":${priority}:$phase "
246        if {[lindex $args 0] eq "-nonewline"} {
247            puts -nonewline $chan $strprefix[lindex $args 1]
248        } else {
249            foreach str [split [lindex $args 0] "\n"] {
250                puts $chan $strprefix$str
251            }
252        }
253    }
254}
255
256proc macports::ui_init {priority args} {
257    global macports::channels ::debuglog
258    set default_channel [macports::ui_channels_default $priority]
259    # Get the list of channels.
260    if {[llength [info commands ui_channels]] > 0} {
261        set channels($priority) [ui_channels $priority]
262    } else {
263        set channels($priority) $default_channel
264    }
265
266    # Simplify ui_$priority.
267    try {
268        set prefix [ui_prefix $priority]
269    } catch * {
270        set prefix [ui_prefix_default $priority]
271    }
272    try {
273        ::ui_init $priority $prefix $channels($priority) {*}$args
274    } catch * {
275        interp alias {} ui_$priority {} ui_message $priority $prefix
276    }
277}
278
279# Default implementation of ui_prefix
280proc macports::ui_prefix_default {priority} {
281    switch -- $priority {
282        debug {
283            return "DEBUG: "
284        }
285        error {
286            return "Error: "
287        }
288        warn {
289            return "Warning: "
290        }
291        default {
292            return {}
293        }
294    }
295}
296
297# Default implementation of ui_channels:
298# ui_options(ports_debug) - If set, output debugging messages
299# ui_options(ports_verbose) - If set, output info messages (ui_info)
300# ui_options(ports_quiet) - If set, don't output "standard messages"
301proc macports::ui_channels_default {priority} {
302    switch -- $priority {
303        debug {
304            if {[ui_isset ports_debug]} {
305                return stderr
306            } else {
307                return {}
308            }
309        }
310        info {
311            if {[ui_isset ports_verbose]} {
312                return stdout
313            } else {
314                return {}
315            }
316        }
317        notice {
318            if {[ui_isset ports_quiet]} {
319                return {}
320            } else {
321                return stdout
322            }
323        }
324        msg {
325            return stdout
326        }
327        warn -
328        error {
329            return stderr
330        }
331        default {
332            return stdout
333        }
334    }
335}
336
337proc ui_warn_once {id msg} {
338    variable macports::warning_done
339    if {![info exists macports::warning_done($id)]} {
340        ui_warn $msg
341        set macports::warning_done($id) 1
342    }
343}
344
345# Replace puts to catch errors (typically broken pipes when being piped to head)
346rename puts tcl::puts
347proc puts {args} {
348    catch "tcl::puts $args"
349}
350
351# find a binary either in a path defined at MacPorts' configuration time
352# or in the PATH environment variable through macports::binaryInPath (fallback)
353proc macports::findBinary {prog {autoconf_hint {}}} {
354    if {$autoconf_hint ne "" && [file executable $autoconf_hint]} {
355        return $autoconf_hint
356    } else {
357        try -pass_signal {
358            set cmd_path [macports::binaryInPath $prog]
359            return $cmd_path
360        } catch {{*} eCode eMessage} {
361            error "$eMessage or at its MacPorts configuration time location, did you move it?"
362        }
363    }
364}
365
366# check for a binary in the path
367# returns an error code if it cannot be found
368proc macports::binaryInPath {prog} {
369    global env
370    foreach dir [split $env(PATH) :] {
371        if {[file executable [file join $dir $prog]]} {
372            return [file join $dir $prog]
373        }
374    }
375    return -code error [format [msgcat::mc "Failed to locate '%s' in path: '%s'"] $prog $env(PATH)];
376}
377
378# deferred option processing
379proc macports::getoption {name} {
380    global macports::$name
381    return [set $name]
382}
383
384# deferred and on-need extraction of xcodeversion and xcodebuildcmd.
385proc macports::setxcodeinfo {name1 name2 op} {
386    global macports::xcodeversion macports::xcodebuildcmd
387
388    trace remove variable macports::xcodeversion read macports::setxcodeinfo
389    trace remove variable macports::xcodebuildcmd read macports::setxcodeinfo
390
391    try -pass_signal {
392        set xcodebuild [findBinary xcodebuild $macports::autoconf::xcodebuild_path]
393        if {![info exists xcodeversion]} {
394            # Determine xcode version
395            set macports::xcodeversion 2.0orlower
396            try -pass_signal {
397                set xcodebuildversion [exec -- $xcodebuild -version 2> /dev/null]
398                if {[regexp {Xcode ([0-9.]+)} $xcodebuildversion - xcode_v] == 1} {
399                    set macports::xcodeversion $xcode_v
400                } elseif {[regexp {DevToolsCore-(.*);} $xcodebuildversion - devtoolscore_v] == 1} {
401                    if {$devtoolscore_v >= 1809.0} {
402                        set macports::xcodeversion 3.2.6
403                    } elseif {$devtoolscore_v >= 1204.0} {
404                        set macports::xcodeversion 3.1.4
405                    } elseif {$devtoolscore_v >= 1100.0} {
406                        set macports::xcodeversion 3.1
407                    } elseif {$devtoolscore_v >= 921.0} {
408                        set macports::xcodeversion 3.0
409                    } elseif {$devtoolscore_v >= 798.0} {
410                        set macports::xcodeversion 2.5
411                    } elseif {$devtoolscore_v >= 762.0} {
412                        set macports::xcodeversion 2.4.1
413                    } elseif {$devtoolscore_v >= 757.0} {
414                        set macports::xcodeversion 2.4
415                    } elseif {$devtoolscore_v > 650.0} {
416                        # XXX find actual version corresponding to 2.3
417                        set macports::xcodeversion 2.3
418                    } elseif {$devtoolscore_v >= 650.0} {
419                        set macports::xcodeversion 2.2.1
420                    } elseif {$devtoolscore_v > 620.0} {
421                        # XXX find actual version corresponding to 2.2
422                        set macports::xcodeversion 2.2
423                    } elseif {$devtoolscore_v >= 620.0} {
424                        set macports::xcodeversion 2.1
425                    }
426                }
427            } catch {*} {
428                ui_warn "xcodebuild exists but failed to execute"
429                set macports::xcodeversion none
430            }
431        }
432        if {![info exists xcodebuildcmd]} {
433            set macports::xcodebuildcmd $xcodebuild
434        }
435    } catch {*} {
436        if {![info exists xcodeversion]} {
437            set macports::xcodeversion none
438        }
439        if {![info exists xcodebuildcmd]} {
440            set macports::xcodebuildcmd none
441        }
442    }
443}
444
445# deferred calculation of developer_dir
446proc macports::set_developer_dir {name1 name2 op} {
447    global macports::developer_dir macports::os_major macports::xcodeversion
448
449    trace remove variable macports::developer_dir read macports::set_developer_dir
450
451    # Look for xcodeselect, and make sure it has a valid value
452    try -pass_signal {
453        set xcodeselect [findBinary xcode-select $macports::autoconf::xcode_select_path]
454
455        # We have xcode-select: ask it where xcode is and check if it's valid.
456        # If no xcode is selected, xcode-select will fail, so catch that
457        try -pass_signal {
458            set devdir [exec $xcodeselect -print-path 2> /dev/null]
459            if {[_is_valid_developer_dir $devdir]} {
460                set macports::developer_dir $devdir
461                return
462            }
463        } catch {*} {}
464
465        # The directory from xcode-select isn't correct.
466
467        # Ask mdfind where Xcode is and make some suggestions for the user,
468        # searching by bundle identifier for various Xcode versions (3.x and 4.x)
469        set installed_xcodes {}
470
471        try -pass_signal {
472            set mdfind [findBinary mdfind $macports::autoconf::mdfind_path]
473            set installed_xcodes [exec $mdfind "kMDItemCFBundleIdentifier == 'com.apple.Xcode' || kMDItemCFBundleIdentifier == 'com.apple.dt.Xcode'"]
474        } catch {*} {}
475
476        # In case mdfind metadata wasn't complete, also look in two well-known locations for Xcode.app
477        foreach app {/Applications/Xcode.app /Developer/Applications/Xcode.app} {
478            if {[file isdirectory $app]} {
479                lappend installed_xcodes $app
480            }
481        }
482
483        # Form a list of unique xcode installations
484        set installed_xcodes [lsort -unique $installed_xcodes]
485
486        # Present instructions to the user
487        ui_error
488        try -pass_signal {
489            if {[llength $installed_xcodes] == 0} {
490                error "No Xcode installation was found."
491            }
492
493            set mdls [findBinary mdls $macports::autoconf::mdls_path]
494
495            # One, or more than one, Xcode installations found
496            ui_error "No valid Xcode installation is properly selected."
497            ui_error "Please use xcode-select to select an Xcode installation:"
498            foreach xcode $installed_xcodes {
499                set vers [exec $mdls -raw -name kMDItemVersion $xcode]
500                if {$vers eq {(null)}} {set vers unknown}
501                if {[_is_valid_developer_dir ${xcode}/Contents/Developer]} {
502                    # Though xcode-select shipped with xcode 4.3 supports and encourages
503                    # direct use of the app path, older xcode-select does not.
504                    # Specify the Contents/Developer directory if it exists
505                    ui_error "    sudo xcode-select -switch ${xcode}/Contents/Developer # version $vers"
506                } elseif {[vercmp $vers 4.3] >= 0} {
507                    # Future proofing: fall back to the app-path only for xcode >= 4.3, since Contents/Developer doesn't exist
508                    ui_error "    sudo xcode-select -switch $xcode # version $vers"
509                } elseif {[_is_valid_developer_dir ${xcode}/../..]} {
510                    # Older xcode (< 4.3) is below the developer directory
511                    ui_error "    sudo xcode-select -switch [file normalize ${xcode}/../..] # version $vers"
512                } else {
513                    ui_error "    # malformed Xcode at ${xcode}, version $vers"
514                }
515            }
516        } catch {*} {
517            ui_error "No Xcode installation was found."
518            ui_error "Please install Xcode and/or run xcode-select to specify its location."
519        }
520        ui_error
521    } catch {*} {}
522
523    # Try the default
524    if {$os_major >= 11 && [vercmp $xcodeversion 4.3] >= 0} {
525        set devdir /Applications/Xcode.app/Contents/Developer
526    } else {
527        set devdir /Developer
528    }
529
530    set macports::developer_dir $devdir
531}
532
533proc macports::_is_valid_developer_dir {dir} {
534    # Check whether specified directory looks valid for an Xcode installation
535
536    # Verify that the directory exists
537    if {![file isdirectory $dir]} {
538        return 0
539    }
540
541    # Verify that the directory has some key subdirectories
542    foreach subdir {Library usr} {
543        if {![file isdirectory ${dir}/$subdir]} {
544            return 0
545        }
546    }
547
548    # The specified directory seems valid for Xcode
549    return 1
550}
551
552
553proc mportinit {{up_ui_options {}} {up_options {}} {up_variations {}}} {
554    if {$up_ui_options eq {}} {
555        array set macports::ui_options {}
556    } else {
557        upvar $up_ui_options temp_ui_options
558        array set macports::ui_options [array get temp_ui_options]
559    }
560    if {$up_options eq {}} {
561        array set macports::global_options {}
562    } else {
563        upvar $up_options temp_options
564        array set macports::global_options [array get temp_options]
565    }
566    if {$up_variations eq {}} {
567        array set variations {}
568    } else {
569        upvar $up_variations variations
570    }
571
572    # Initialize ui_*
573    foreach priority $macports::ui_priorities {
574        macports::ui_init $priority
575    }
576
577    package require Pextlib 1.0
578    package require registry 1.0
579    package require registry2 2.0
580    package require machista 1.0
581
582    global auto_path env tcl_platform \
583        macports::autoconf::macports_conf_path \
584        macports::macports_user_dir \
585        macports::bootstrap_options \
586        macports::user_options \
587        macports::portconf \
588        macports::portsharepath \
589        macports::registry.format \
590        macports::registry.path \
591        macports::sources \
592        macports::sources_default \
593        macports::destroot_umask \
594        macports::prefix \
595        macports::macportsuser \
596        macports::prefix_frozen \
597        macports::xcodebuildcmd \
598        macports::xcodeversion \
599        macports::configureccache \
600        macports::ccache_dir \
601        macports::ccache_size \
602        macports::configuredistcc \
603        macports::configurepipe \
604        macports::buildnicevalue \
605        macports::buildmakejobs \
606        macports::universal_archs \
607        macports::build_arch \
608        macports::os_arch \
609        macports::os_endian \
610        macports::os_version \
611        macports::os_major \
612        macports::os_minor \
613        macports::os_platform \
614        macports::macosx_version \
615        macports::macosx_sdk_version \
616        macports::macosx_deployment_target \
617        macports::archivefetch_pubkeys \
618        macports::ping_cache \
619        macports::host_blacklisted \
620        macports::host_preferred \
621        macports::delete_la_files \
622        macports::cxx_stdlib
623
624    # Set the system encoding to utf-8
625    encoding system utf-8
626
627    # Set up signal handling for SIGTERM and SIGINT
628    # Specifying error here will case the program to abort where it is with
629    # a Tcl error, which can be caught, if necessary.
630    signal -restart error {TERM INT}
631
632    # set up platform info variables
633    set os_arch $tcl_platform(machine)
634    if {$os_arch eq "Power Macintosh"} {set os_arch "powerpc"}
635    if {$os_arch eq "i586" || $os_arch eq "i686" || $os_arch eq "x86_64"} {set os_arch "i386"}
636    set os_version $tcl_platform(osVersion)
637    set os_major [lindex [split $os_version .] 0]
638    set os_minor [lindex [split $os_version .] 1]
639    set os_platform [string tolower $tcl_platform(os)]
640    # Remove trailing "Endian"
641    set os_endian [string range $tcl_platform(byteOrder) 0 end-6]
642    set macosx_version {}
643    if {$os_platform eq "darwin" && [file executable /usr/bin/sw_vers]} {
644
645        try -pass_signal {
646            set macosx_version [exec /usr/bin/sw_vers -productVersion | cut -f1,2 -d.]
647        } catch {*} {
648            ui_debug "sw_vers exists but running it failed: $result"
649        }
650    }
651
652    # Check that the current platform is the one we were configured for, otherwise need to do migration
653    if {($os_platform ne $macports::autoconf::os_platform) || ($os_major != $macports::autoconf::os_major)} {
654        ui_error "Current platform \"$os_platform $os_major\" does not match expected platform \"$macports::autoconf::os_platform $macports::autoconf::os_major\""
655        ui_error "If you upgraded your OS, please follow the migration instructions: https://trac.macports.org/wiki/Migration"
656        return -code error "OS platform mismatch"
657    }
658
659    # Ensure that the macports user directory (i.e. ~/.macports) exists if HOME is defined.
660    # Also save $HOME for later use before replacing it with our own.
661    if {[info exists env(HOME)]} {
662        set macports::user_home $env(HOME)
663        set macports::macports_user_dir [file normalize $macports::autoconf::macports_user_dir]
664    } elseif {[info exists env(SUDO_USER)] && $os_platform eq "darwin"} {
665        set macports::user_home [exec dscl -q . -read /Users/$env(SUDO_USER) NFSHomeDirectory | cut -d ' ' -f 2]
666        set macports::macports_user_dir [file join $macports::user_home [string range $macports::autoconf::macports_user_dir 2 end]]
667    } elseif {[exec id -u] != 0 && $os_platform eq "darwin"} {
668        set macports::user_home [exec dscl -q . -read /Users/[exec id -un] NFSHomeDirectory | cut -d ' ' -f 2]
669        set macports::macports_user_dir [file join $macports::user_home [string range $macports::autoconf::macports_user_dir 2 end]]
670    } else {
671        # Otherwise define the user directory as a directory that will never exist
672        set macports::macports_user_dir /dev/null/NO_HOME_DIR
673        set macports::user_home /dev/null/NO_HOME_DIR
674    }
675
676    # Save the path for future processing
677    set macports::user_path $env(PATH)
678
679    # Configure the search path for configuration files
680    set conf_files {}
681    lappend conf_files ${macports_conf_path}/macports.conf
682    if {[file isdirectory $macports_user_dir]} {
683        lappend conf_files ${macports_user_dir}/macports.conf
684    }
685    if {[info exists env(PORTSRC)]} {
686        set PORTSRC $env(PORTSRC)
687        lappend conf_files $PORTSRC
688    }
689
690    # Process all configuration files we find on conf_files list
691    foreach file $conf_files {
692        if {[file exists $file]} {
693            set portconf $file
694            set fd [open $file r]
695            while {[gets $fd line] >= 0} {
696                if {[regexp {^(\w+)([ \t]+(.*))?$} $line match option ignore val] == 1} {
697                    if {$option in $bootstrap_options} {
698                        set macports::$option [string trim $val]
699                        global macports::$option
700                    }
701                }
702            }
703            close $fd
704        }
705    }
706
707    # Process per-user only settings
708    set per_user ${macports_user_dir}/user.conf
709    if {[file exists $per_user]} {
710        set fd [open $per_user r]
711        while {[gets $fd line] >= 0} {
712            if {[regexp {^(\w+)([ \t]+(.*))?$} $line match option ignore val] == 1} {
713                if {$option in $user_options} {
714                    set macports::$option $val
715                    global macports::$option
716                }
717            }
718        }
719        close $fd
720    }
721
722    if {![info exists sources_conf]} {
723        return -code error "sources_conf must be set in ${macports_conf_path}/macports.conf or in your ${macports_user_dir}/macports.conf file"
724    }
725    set fd [open $sources_conf r]
726    while {[gets $fd line] >= 0} {
727        set line [string trimright $line]
728        if {![regexp {^\s*#|^$} $line]} {
729            if {[regexp {^([\w-]+://\S+)(?:\s+\[(\w+(?:,\w+)*)\])?$} $line _ url flags]} {
730                set flags [split $flags ,]
731                foreach flag $flags {
732                    if {$flag ni [list nosync default]} {
733                        ui_warn "$sources_conf source '$line' specifies invalid flag '$flag'"
734                    }
735                    if {$flag eq "default"} {
736                        if {[info exists sources_default]} {
737                            ui_warn "More than one default port source is defined."
738                        }
739                        set sources_default [concat [list $url] $flags]
740                    }
741                }
742                lappend sources [concat [list $url] $flags]
743            } else {
744                ui_warn "$sources_conf specifies invalid source '$line', ignored."
745            }
746        }
747    }
748    close $fd
749    # Make sure the default port source is defined. Otherwise
750    # [macports::getportresourcepath] fails when the first source doesn't
751    # contain _resources.
752    if {![info exists sources_default]} {
753        ui_warn "No default port source specified in ${sources_conf}, using last source as default"
754        set sources_default [lindex $sources end]
755    }
756
757    if {![info exists sources]} {
758        if {[file isdirectory ports]} {
759            set sources file://[pwd]/ports
760        } else {
761            return -code error "No sources defined in $sources_conf"
762        }
763    }
764
765    if {[info exists variants_conf]} {
766        if {[file exists $variants_conf]} {
767            set fd [open $variants_conf r]
768            while {[gets $fd line] >= 0} {
769                set line [string trimright $line]
770                if {![regexp {^[\ \t]*#.*$|^$} $line]} {
771                    foreach arg [split $line " \t"] {
772                        if {[regexp {^([-+])([-A-Za-z0-9_+\.]+)$} $arg match sign opt] == 1} {
773                            if {![info exists variations($opt)]} {
774                                set variations($opt) $sign
775                            }
776                        } else {
777                            ui_warn "$variants_conf specifies invalid variant syntax '$arg', ignored."
778                        }
779                    }
780                }
781            }
782            close $fd
783        } else {
784            ui_debug "$variants_conf does not exist, variants_conf setting ignored."
785        }
786    }
787    global macports::global_variations
788    array set macports::global_variations [array get variations]
789
790    # pubkeys.conf
791    set macports::archivefetch_pubkeys {}
792    if {[file isfile [file join $macports_conf_path pubkeys.conf]]} {
793        set fd [open [file join $macports_conf_path pubkeys.conf] r]
794        while {[gets $fd line] >= 0} {
795            set line [string trim $line]
796            if {![regexp {^[\ \t]*#.*$|^$} $line]} {
797                lappend macports::archivefetch_pubkeys $line
798            }
799        }
800        close $fd
801    } else {
802        ui_debug "pubkeys.conf does not exist."
803    }
804
805    if {![info exists portdbpath]} {
806        return -code error "portdbpath must be set in ${macports_conf_path}/macports.conf or in your ${macports_user_dir}/macports.conf"
807    }
808    if {![file isdirectory $portdbpath]} {
809        if {![file exists $portdbpath]} {
810            if {[catch {file mkdir $portdbpath} result]} {
811                return -code error "portdbpath $portdbpath does not exist and could not be created: $result"
812            }
813        } else {
814            return -code error "$portdbpath is not a directory. Please create the directory $portdbpath and try again"
815        }
816    }
817
818    set env(HOME) [file join $portdbpath home]
819    set registry.path $portdbpath
820
821    # Format for receipts; currently only "sqlite" is allowed
822    # could previously be "flat", so we switch that to sqlite
823    if {![info exists portdbformat] || $portdbformat eq "flat" || $portdbformat eq "sqlite"} {
824        set registry.format receipt_sqlite
825    } else {
826        return -code error "unknown registry format '$portdbformat' set in macports.conf"
827    }
828
829    # Autoclean mode, whether to automatically call clean after "install"
830    if {![info exists portautoclean]} {
831        set macports::portautoclean yes
832        global macports::portautoclean
833    }
834    # whether to keep logs after successful builds
835    if {![info exists keeplogs]} {
836        set macports::keeplogs no
837        global macports::keeplogs
838    }
839
840    # Check command line override for autoclean
841    if {[info exists macports::global_options(ports_autoclean)]} {
842        if {$macports::global_options(ports_autoclean) ne $portautoclean} {
843            set macports::portautoclean $macports::global_options(ports_autoclean)
844        }
845    }
846    # Trace mode, whether to use darwintrace to debug ports.
847    if {![info exists porttrace]} {
848        set macports::porttrace no
849        global macports::porttrace
850    }
851    # Check command line override for trace
852    if {[info exists macports::global_options(ports_trace)]} {
853        if {$macports::global_options(ports_trace) ne $porttrace} {
854            set macports::porttrace $macports::global_options(ports_trace)
855        }
856    }
857    # Check command line override for source/binary only mode
858    if {![info exists macports::global_options(ports_binary_only)]
859        && ![info exists macports::global_options(ports_source_only)]
860        && [info exists macports::buildfromsource]} {
861        if {$macports::buildfromsource eq "never"} {
862            set macports::global_options(ports_binary_only) yes
863            set temp_options(ports_binary_only) yes
864        } elseif {$macports::buildfromsource eq "always"} {
865            set macports::global_options(ports_source_only) yes
866            set temp_options(ports_source_only) yes
867        } elseif {$macports::buildfromsource ne "ifneeded"} {
868            ui_warn "'buildfromsource' set to unknown value '$macports::buildfromsource', using 'ifneeded' instead"
869        }
870    }
871
872    # Duplicate prefix into prefix_frozen, so that port actions
873    # can always get to the original prefix, even if a portfile overrides prefix
874    set macports::prefix_frozen $prefix
875
876    if {![info exists macports::applications_dir]} {
877        set macports::applications_dir /Applications/MacPorts
878    }
879
880    # Export verbosity.
881    if {![info exists portverbose]} {
882        set macports::portverbose no
883        global macports::portverbose
884    }
885    if {[info exists macports::ui_options(ports_verbose)]} {
886        if {$macports::ui_options(ports_verbose) ne $portverbose} {
887            set macports::portverbose $macports::ui_options(ports_verbose)
888        }
889    }
890
891    # Set noninteractive mode if specified in config
892    if {[info exists ui_interactive] && !$ui_interactive} {
893        set macports::ui_options(ports_noninteractive) yes
894        unset -nocomplain macports::ui_options(questions_yesno) macports::ui_options(questions_singlechoice) macports::ui_options(questions_multichoice)
895    }
896
897    # Archive type, what type of binary archive to use (CPIO, gzipped
898    # CPIO, XAR, etc.)
899    global macports::portarchivetype
900    if {![info exists portarchivetype]} {
901        set macports::portarchivetype tbz2
902    } else {
903        set macports::portarchivetype [lindex $portarchivetype 0]
904    }
905
906    # Set rync options
907    if {![info exists rsync_server]} {
908        global macports::rsync_server
909        set macports::rsync_server rsync.macports.org
910    }
911    if {![info exists rsync_dir]} {
912        global macports::rsync_dir
913        set macports::rsync_dir release/tarballs/base.tar
914    }
915    if {![info exists rsync_options]} {
916        global macports::rsync_options
917        set rsync_options "-rtzv --delete-after"
918    }
919
920    set portsharepath ${prefix}/share/macports
921    if {![file isdirectory $portsharepath]} {
922        return -code error "Data files directory '$portsharepath' must exist"
923    }
924
925    if {![info exists binpath]} {
926        set env(PATH) ${prefix}/bin:${prefix}/sbin:/bin:/sbin:/usr/bin:/usr/sbin
927    } else {
928        set env(PATH) $binpath
929    }
930
931    # Set startupitem default type (can be overridden by portfile)
932    if {![info exists macports::startupitem_type]} {
933        set macports::startupitem_type default
934    }
935
936    # Set whether startupitems are symlinked into system directories
937    if {![info exists macports::startupitem_install]} {
938        set macports::startupitem_install yes
939    }
940
941    # Default place_worksymlink
942    if {![info exists macports::place_worksymlink]} {
943        set macports::place_worksymlink yes
944    }
945
946    # Default mp configure options
947    if {![info exists macports::configureccache]} {
948        set macports::configureccache no
949    }
950    if {![info exists macports::ccache_dir]} {
951        set macports::ccache_dir [file join $portdbpath build .ccache]
952    }
953    if {![info exists macports::ccache_size]} {
954        set macports::ccache_size 2G
955    }
956    if {![info exists macports::configuredistcc]} {
957        set macports::configuredistcc no
958    }
959    if {![info exists macports::configurepipe]} {
960        set macports::configurepipe yes
961    }
962
963    # Default mp build options
964    if {![info exists macports::buildnicevalue]} {
965        set macports::buildnicevalue 0
966    }
967    if {![info exists macports::buildmakejobs]} {
968        set macports::buildmakejobs 0
969    }
970
971    # default user to run as when privileges can be dropped
972    if {![info exists macports::macportsuser]} {
973        set macports::macportsuser $macports::autoconf::macportsuser
974    }
975
976    # Default mp universal options
977    if {![info exists macports::universal_archs]} {
978        if {$os_major >= 10} {
979            set macports::universal_archs {x86_64 i386}
980        } else {
981            set macports::universal_archs {i386 ppc}
982        }
983    } elseif {[llength $macports::universal_archs] < 2} {
984        ui_warn "invalid universal_archs configured (should contain at least 2 archs)"
985    }
986
987    # Default arch to build for
988    if {![info exists macports::build_arch]} {
989        if {$os_platform eq "darwin"} {
990            if {$os_major >= 10} {
991                if {[sysctl hw.cpu64bit_capable] == 1} {
992                    set macports::build_arch x86_64
993                } else {
994                    set macports::build_arch i386
995                }
996            } else {
997                if {$os_arch eq "powerpc"} {
998                    set macports::build_arch ppc
999                } else {
1000                    set macports::build_arch i386
1001                }
1002            }
1003        } else {
1004            set macports::build_arch {}
1005        }
1006    } else {
1007        set macports::build_arch [lindex $macports::build_arch 0]
1008    }
1009
1010    if {![info exists macports::macosx_deployment_target]} {
1011        set macports::macosx_deployment_target $macosx_version
1012    }
1013    if {![info exists macports::macosx_sdk_version]} {
1014        set macports::macosx_sdk_version $macosx_version
1015    }
1016
1017    if {![info exists macports::revupgrade_autorun]} {
1018        set macports::revupgrade_autorun yes
1019    }
1020    if {![info exists macports::revupgrade_mode]} {
1021        set macports::revupgrade_mode rebuild
1022    }
1023    if {![info exists macports::delete_la_files]} {
1024        if {$os_platform eq "darwin" && $os_major >= 13} {
1025            set macports::delete_la_files yes
1026        } else {
1027            set macports::delete_la_files no
1028        }
1029    }
1030    if {![info exists macports::cxx_stdlib]} {
1031        if {$os_platform eq "darwin" && $os_major >= 13} {
1032            set macports::cxx_stdlib libc++
1033        } elseif {$os_platform eq "darwin"} {
1034            set macports::cxx_stdlib libstdc++
1035        } else {
1036            set macports::cxx_stdlib {}
1037        }
1038    }
1039    if {![info exists macports::global_options(ports_rev-upgrade_id-loadcmd-check)]
1040         && [info exists macports::revupgrade_check_id_loadcmds]} {
1041        set macports::global_options(ports_rev-upgrade_id-loadcmd-check) $macports::revupgrade_check_id_loadcmds
1042        set temp_options(ports_rev-upgrade_id-loadcmd-check) $macports::revupgrade_check_id_loadcmds
1043    }
1044
1045    if {![info exists macports::sandbox_enable]} {
1046        set macports::sandbox_enable yes
1047    }
1048
1049    # make tools we run operate in UTF-8 mode
1050    set env(LANG) en_US.UTF-8
1051
1052    # ENV cleanup.
1053    set keepenvkeys {
1054        DISPLAY DYLD_FALLBACK_FRAMEWORK_PATH
1055        DYLD_FALLBACK_LIBRARY_PATH DYLD_FRAMEWORK_PATH
1056        DYLD_LIBRARY_PATH DYLD_INSERT_LIBRARIES
1057        HOME JAVA_HOME MASTER_SITE_LOCAL ARCHIVE_SITE_LOCAL
1058        PATCH_SITE_LOCAL PATH PORTSRC RSYNC_PROXY
1059        USER GROUP LANG
1060        http_proxy HTTPS_PROXY FTP_PROXY ALL_PROXY NO_PROXY
1061        COLUMNS LINES
1062    }
1063    if {[info exists extra_env]} {
1064        set keepenvkeys [concat $keepenvkeys $extra_env]
1065    }
1066
1067    # set the hidden flag on $portdbpath to avoid spotlight indexing, which
1068    # might slow builds down considerably. You can avoid this by touching
1069    # $portdbpath/.nohide.
1070    if {$os_platform eq "darwin" && [vercmp [info tclversion] 8.5] >= 0 && ![file exists [file join $portdbpath .nohide]] && [file writable $portdbpath] && [file attributes $portdbpath -hidden] == 0} {
1071        try -pass_signal {
1072            file attributes $portdbpath -hidden yes
1073        } catch {{*} eCode eMessage} {
1074            ui_debug "error setting hidden flag for $portdbpath: $eMessage"
1075        }
1076    }
1077
1078    # don't keep unusable TMPDIR/TMP values
1079    foreach var {TMP TMPDIR} {
1080        if {[info exists env($var)] && [file writable $env($var)] &&
1081            ([getuid] != 0 || $macportsuser eq "root" ||
1082             [file attributes $env($var) -owner] eq $macportsuser)} {
1083            lappend keepenvkeys $var
1084        }
1085    }
1086
1087    set env_names [array names env]
1088    foreach envkey $env_names {
1089        if {$envkey ni $keepenvkeys} {
1090            unset env($envkey)
1091        }
1092    }
1093
1094    if {![info exists xcodeversion] || ![info exists xcodebuildcmd]} {
1095        # We'll resolve these later (if needed)
1096        trace add variable macports::xcodeversion read macports::setxcodeinfo
1097        trace add variable macports::xcodebuildcmd read macports::setxcodeinfo
1098    }
1099
1100    if {![info exists developer_dir]} {
1101        if {$os_platform eq "darwin"} {
1102            trace add variable macports::developer_dir read macports::set_developer_dir
1103        } else {
1104            set macports::developer_dir {}
1105        }
1106    } else {
1107        if {$os_platform eq "darwin" && ![file isdirectory $developer_dir]} {
1108            ui_warn "Your developer_dir setting in macports.conf points to a non-existing directory.\
1109                Since this is known to cause problems, please correct the setting or comment it and let\
1110                macports auto-discover the correct path."
1111        }
1112    }
1113
1114    if {[getuid] == 0 && $os_major >= 11 && $os_platform eq "darwin" &&
1115            [file isfile "${macports::user_home}/Library/Preferences/com.apple.dt.Xcode.plist"]} {
1116        macports::copy_xcode_plist $env(HOME)
1117    }
1118
1119    # Set the default umask
1120    if {![info exists destroot_umask]} {
1121        set destroot_umask 022
1122    }
1123
1124    if {[info exists master_site_local] && ![info exists env(MASTER_SITE_LOCAL)]} {
1125        set env(MASTER_SITE_LOCAL) $master_site_local
1126    }
1127    if {[info exists patch_site_local] && ![info exists env(PATCH_SITE_LOCAL)]} {
1128        set env(PATCH_SITE_LOCAL) $patch_site_local
1129    }
1130    if {[info exists archive_site_local] && ![info exists env(ARCHIVE_SITE_LOCAL)]} {
1131        set env(ARCHIVE_SITE_LOCAL) $archive_site_local
1132    }
1133
1134    # Proxy handling (done this late since Pextlib is needed)
1135    if {![info exists proxy_override_env] || ![string is true -strict $proxy_override_env]} {
1136        set proxy_override_env no
1137    }
1138    if {[catch {array set sysConfProxies [get_systemconfiguration_proxies]} result]} {
1139        return -code error "Unable to get proxy configuration from system: $result"
1140    }
1141    if {![info exists env(http_proxy)] || $proxy_override_env} {
1142        if {[info exists proxy_http]} {
1143            set env(http_proxy) $proxy_http
1144        } elseif {[info exists sysConfProxies(proxy_http)]} {
1145            set env(http_proxy) $sysConfProxies(proxy_http)
1146        }
1147    }
1148    if {![info exists env(HTTPS_PROXY)] || $proxy_override_env} {
1149        if {[info exists proxy_https]} {
1150            set env(HTTPS_PROXY) $proxy_https
1151        } elseif {[info exists sysConfProxies(proxy_https)]} {
1152            set env(HTTPS_PROXY) $sysConfProxies(proxy_https)
1153        }
1154    }
1155    if {![info exists env(FTP_PROXY)] || $proxy_override_env} {
1156        if {[info exists proxy_ftp]} {
1157            set env(FTP_PROXY) $proxy_ftp
1158        } elseif {[info exists sysConfProxies(proxy_ftp)]} {
1159            set env(FTP_PROXY) $sysConfProxies(proxy_ftp)
1160        }
1161    }
1162    if {![info exists env(RSYNC_PROXY)] || $proxy_override_env} {
1163        if {[info exists proxy_rsync]} {
1164            set env(RSYNC_PROXY) $proxy_rsync
1165        }
1166    }
1167    if {![info exists env(NO_PROXY)] || $proxy_override_env} {
1168        if {[info exists proxy_skip]} {
1169            set env(NO_PROXY) $proxy_skip
1170        } elseif {[info exists sysConfProxies(proxy_skip)]} {
1171            set env(NO_PROXY) $sysConfProxies(proxy_skip)
1172        }
1173    }
1174
1175    # add ccache to environment
1176    set env(CCACHE_DIR) $macports::ccache_dir
1177
1178    # load cached ping times
1179    try -pass_signal {
1180        set pingfile -1
1181        set pingfile [open ${macports::portdbpath}/pingtimes r]
1182        array set macports::ping_cache [gets $pingfile]
1183    } catch {*} {
1184        array set macports::ping_cache {}
1185    } finally {
1186        if {$pingfile != -1} {
1187            close $pingfile
1188        }
1189    }
1190    # set up arrays of blacklisted and preferred hosts
1191    if {[info exists macports::host_blacklist]} {
1192        foreach host $macports::host_blacklist {
1193            set macports::host_blacklisted($host) 1
1194        }
1195    }
1196    if {[info exists macports::preferred_hosts]} {
1197        foreach host $macports::preferred_hosts {
1198            set macports::host_preferred($host) 1
1199        }
1200    }
1201
1202    # load the quick index
1203    _mports_load_quickindex
1204
1205    if {![info exists macports::ui_options(ports_no_old_index_warning)]} {
1206        set default_source_url [lindex $sources_default 0]
1207        if {[macports::getprotocol $default_source_url] eq "file" || [macports::getprotocol $default_source_url] eq "rsync"} {
1208            set default_portindex [macports::getindex $default_source_url]
1209            if {[file exists $default_portindex] && [clock seconds] - [file mtime $default_portindex] > 1209600} {
1210                ui_warn "port definitions are more than two weeks old, consider updating them by running 'port selfupdate'."
1211            }
1212        }
1213    }
1214
1215    # init registry
1216    set db_path [file join ${registry.path} registry registry.db]
1217    set db_exists [file exists $db_path]
1218    registry::open $db_path
1219    # for the benefit of the portimage code that is called from multiple interpreters
1220    global registry_open
1221    set registry_open yes
1222    # convert any flat receipts if we just created a new db
1223    if {$db_exists == 0 && [file exists ${registry.path}/receipts] && [file writable $db_path]} {
1224        ui_warn "Converting your registry to sqlite format, this might take a while..."
1225        # XXX: catch, leave unfixed, code should go away.
1226        if {[catch {registry::convert_to_sqlite}]} {
1227            ui_debug $::errorInfo
1228            file delete -force $db_path
1229            error "Failed to convert your registry to sqlite!"
1230        } else {
1231            ui_warn "Successfully converted your registry to sqlite!"
1232        }
1233    }
1234}
1235
1236# call this just before you exit
1237proc mportshutdown {} {
1238    # save ping times
1239    global macports::ping_cache macports::portdbpath
1240    if {[file writable $macports::portdbpath]} {
1241        catch {
1242            foreach host [array names ping_cache] {
1243                # don't save expired entries
1244                if {[clock seconds] - [lindex $ping_cache($host) 1] < 86400} {
1245                    lappend pinglist_fresh $host $ping_cache($host)
1246                }
1247            }
1248            set pingfile [open ${macports::portdbpath}/pingtimes w]
1249            puts $pingfile $pinglist_fresh
1250            close $pingfile
1251        }
1252    }
1253    # Check the last time 'reclaim' was run and run it
1254    if {![macports::ui_isset ports_quiet]} {
1255        reclaim::check_last_run
1256    }
1257
1258    # close it down so the cleanup stuff is called, e.g. vacuuming the db
1259    registry::close
1260}
1261
1262# link plist for xcode 4.3's benefit
1263proc macports::copy_xcode_plist {target_homedir} {
1264    global macports::user_home macports::macportsuser
1265    set user_plist "${user_home}/Library/Preferences/com.apple.dt.Xcode.plist"
1266    set target_dir "${target_homedir}/Library/Preferences"
1267    file delete -force "${target_dir}/com.apple.dt.Xcode.plist"
1268    if {[file isfile $user_plist]} {
1269        if {![file isdirectory $target_dir]} {
1270            try -pass_signal {
1271                file mkdir $target_dir
1272            } catch {{*} eCode eMessage} {
1273                ui_warn "Failed to create Library/Preferences in ${target_homedir}: $eMessage"
1274                return
1275            }
1276        }
1277        try -pass_signal {
1278            if {![file writable $target_dir]} {
1279                error "${target_dir} is not writable"
1280            }
1281            ui_debug "Copying $user_plist to $target_dir"
1282            file copy -force $user_plist $target_dir
1283            file attributes ${target_dir}/com.apple.dt.Xcode.plist -owner $macportsuser -permissions 0644
1284        } catch {{*} eCode eMessage} {
1285            ui_warn "Failed to copy com.apple.dt.Xcode.plist to ${target_dir}: $eMessage"
1286        }
1287    }
1288}
1289
1290proc macports::worker_init {workername portpath porturl portbuildpath options variations} {
1291    global macports::portinterp_options macports::portinterp_deferred_options
1292
1293    # Hide any Tcl commands that should be inaccessible to port1.0 and Portfiles
1294    # exit: It should not be possible to exit the interpreter
1295    interp hide $workername exit
1296
1297    # cd: This is necessary for some code in port1.0, but should be hidden
1298    interp eval $workername "rename cd _cd"
1299
1300    # Tell the sub interpreter about all the Tcl packages we already
1301    # know about so it won't glob for packages.
1302    foreach pkgName [package names] {
1303        foreach pkgVers [package versions $pkgName] {
1304            set pkgLoadScript [package ifneeded $pkgName $pkgVers]
1305            $workername eval "package ifneeded $pkgName $pkgVers {$pkgLoadScript}"
1306        }
1307    }
1308
1309    # Create package require abstraction procedure
1310    $workername eval "proc PortSystem \{version\} \{ \n\
1311            package require port \$version \}"
1312
1313    # Clearly separate slave interpreters and the master interpreter.
1314    $workername alias mport_exec mportexec
1315    $workername alias mport_open mportopen
1316    $workername alias mport_close mportclose
1317    $workername alias mport_lookup mportlookup
1318    $workername alias mport_info mportinfo
1319    $workername alias set_phase set_phase
1320
1321    # instantiate the UI call-backs
1322    foreach priority $macports::ui_priorities {
1323        $workername alias ui_$priority ui_$priority
1324    }
1325    # add the UI progress call-back
1326    if {[info exists macports::ui_options(progress_download)]} {
1327        $workername alias ui_progress_download $macports::ui_options(progress_download)
1328    }
1329
1330    # notifications callback
1331    if {[info exists macports::ui_options(notifications_append)]} {
1332        $workername alias ui_notifications_append $macports::ui_options(notifications_append)
1333    } else {
1334        # provide a no-op if notifications_append wasn't set. See http://wiki.tcl.tk/3044
1335        $workername alias ui_notifications_append return -level 0
1336    }
1337
1338    $workername alias ui_prefix ui_prefix
1339    $workername alias ui_channels ui_channels
1340
1341    $workername alias ui_warn_once ui_warn_once
1342
1343    # Export some utility functions defined here.
1344    $workername alias macports_version macports::version
1345    $workername alias macports_create_thread macports::create_thread
1346    $workername alias getportworkpath_from_buildpath macports::getportworkpath_from_buildpath
1347    $workername alias getportresourcepath macports::getportresourcepath
1348    $workername alias getportlogpath macports::getportlogpath
1349    $workername alias getdefaultportresourcepath macports::getdefaultportresourcepath
1350    $workername alias getprotocol macports::getprotocol
1351    $workername alias getportdir macports::getportdir
1352    $workername alias findBinary macports::findBinary
1353    $workername alias binaryInPath macports::binaryInPath
1354    $workername alias sysctl sysctl
1355    $workername alias realpath realpath
1356    $workername alias _mportsearchpath _mportsearchpath
1357    $workername alias _portnameactive _portnameactive
1358
1359    # New Registry/Receipts stuff
1360    $workername alias registry_new registry::new_entry
1361    $workername alias registry_open registry::open_entry
1362    $workername alias registry_write registry::write_entry
1363    $workername alias registry_prop_store registry::property_store
1364    $workername alias registry_prop_retr registry::property_retrieve
1365    $workername alias registry_exists registry::entry_exists
1366    $workername alias registry_exists_for_name registry::entry_exists_for_name
1367    $workername alias registry_activate portimage::activate
1368    $workername alias registry_deactivate portimage::deactivate
1369    $workername alias registry_deactivate_composite portimage::deactivate_composite
1370    $workername alias registry_uninstall registry_uninstall::uninstall
1371    $workername alias registry_register_deps registry::register_dependencies
1372    $workername alias registry_fileinfo_for_index registry::fileinfo_for_index
1373    $workername alias registry_fileinfo_for_file registry::fileinfo_for_file
1374    $workername alias registry_bulk_register_files registry::register_bulk_files
1375    $workername alias registry_active registry::active
1376    $workername alias registry_file_registered registry::file_registered
1377    $workername alias registry_port_registered registry::port_registered
1378    $workername alias registry_list_depends registry::list_depends
1379
1380    # deferred options processing.
1381    $workername alias getoption macports::getoption
1382
1383    # ping cache
1384    $workername alias get_pingtime macports::get_pingtime
1385    $workername alias set_pingtime macports::set_pingtime
1386
1387    # archive_sites.conf handling
1388    $workername alias get_archive_sites_conf_values macports::get_archive_sites_conf_values
1389
1390    foreach opt $portinterp_options {
1391        if {![info exists $opt]} {
1392            global macports::$opt
1393        }
1394        if {[info exists $opt]} {
1395            $workername eval set system_options($opt) \{[set $opt]\}
1396            $workername eval set $opt \{[set $opt]\}
1397        }
1398    }
1399
1400    foreach opt $portinterp_deferred_options {
1401        global macports::$opt
1402        # define the trace hook.
1403        $workername eval \
1404            "proc trace_$opt {name1 name2 op} { \n\
1405                trace remove variable ::$opt read ::trace_$opt \n\
1406                global $opt \n\
1407                set $opt \[getoption $opt\] \n\
1408            }"
1409        # next access will actually define the variable.
1410        $workername eval "trace add variable ::$opt read ::trace_$opt"
1411        # define some value now
1412        $workername eval set $opt ?
1413    }
1414
1415    foreach {opt val} $options {
1416        $workername eval set user_options($opt) $val
1417        $workername eval set $opt $val
1418    }
1419
1420    foreach {var val} $variations {
1421        $workername eval set variations($var) $val
1422    }
1423}
1424
1425# Create a thread with most configuration options set.
1426# The newly created thread is sent portinterp_options vars and knows where to
1427# find all packages we know.
1428proc macports::create_thread {} {
1429    package require Thread
1430
1431    global macports::portinterp_options
1432
1433    # Create the thread.
1434    set result [thread::create -preserved {thread::wait}]
1435
1436    # Tell the thread about all the Tcl packages we already
1437    # know about so it won't glob for packages.
1438    foreach pkgName [package names] {
1439        foreach pkgVers [package versions $pkgName] {
1440            set pkgLoadScript [package ifneeded $pkgName $pkgVers]
1441            thread::send -async $result "package ifneeded $pkgName $pkgVers {$pkgLoadScript}"
1442        }
1443    }
1444
1445    # inherit configuration variables.
1446    thread::send -async $result "namespace eval macports {}"
1447    foreach opt $portinterp_options {
1448        if {![info exists $opt]} {
1449            global macports::$opt
1450        }
1451        if {[info exists $opt]} {
1452            thread::send -async $result "global macports::$opt"
1453            set val [set macports::$opt]
1454            thread::send -async $result "set macports::$opt \"$val\""
1455        }
1456    }
1457
1458    return $result
1459}
1460
1461proc macports::get_tar_flags {suffix} {
1462    switch -- $suffix {
1463        .tbz -
1464        .tbz2 {
1465            return -j
1466        }
1467        .tgz {
1468            return -z
1469        }
1470        .txz {
1471            return "--use-compress-program [findBinary xz {}] -"
1472        }
1473        .tlz {
1474            return "--use-compress-program [findBinary lzma {}] -"
1475        }
1476        default {
1477            return -
1478        }
1479    }
1480}
1481
1482##
1483# Extracts a Portfile from a tarball pointed to by the given \a url to a path
1484# in \c $portdbpath and returns its path.
1485#
1486# @param url URL pointing to a tarball containing either a file named \c
1487#            Portfile at the root level -- in which case the tarball is
1488#            extracted completely, --  or a file named \c +CONTENTS at the root
1489#            level (i.e., the archive is a valid MacPorts binary archive), in
1490#            which case the Portfile is extracted from the file \c +PORTFILE
1491#            and put in a separate directory.
1492# @param local one, if the URL is local, zero otherwise
1493# @return a path to a directory containing the Portfile, or an error code
1494proc macports::fetch_port {url {local 0}} {
1495    global macports::portdbpath macports::ui_prefix macports::portverbose macports::ui_options
1496
1497    set fetchdir [file join $portdbpath portdirs]
1498    file mkdir $fetchdir
1499    if {![file writable $fetchdir]} {
1500        return -code error "Port remote fetch failed: You do not have permission to write to $fetchdir"
1501    }
1502
1503    if {$local} {
1504        set filepath $url
1505    } else {
1506        ui_msg "$macports::ui_prefix Fetching port $url"
1507        set fetchfile [file tail $url]
1508        set progressflag {}
1509        if {$macports::portverbose} {
1510            set progressflag "--progress builtin"
1511        } elseif {[info exists macports::ui_options(progress_download)]} {
1512            set progressflag "--progress ${macports::ui_options(progress_download)}"
1513        }
1514        set filepath [file join $fetchdir $fetchfile]
1515        if {[catch {curl fetch {*}$progressflag $url $filepath} result]} {
1516            return -code error "Port remote fetch failed: $result"
1517        }
1518    }
1519
1520    set oldpwd [pwd]
1521    cd $fetchdir
1522
1523    # check if this is a binary archive or just the port dir by checking
1524    # whether the file "+CONTENTS" exists.
1525    set tarcmd [findBinary tar $macports::autoconf::tar_path]
1526    set tarflags [get_tar_flags [file extension $filepath]]
1527    set qflag $macports::autoconf::tar_q
1528    set cmdline [list $tarcmd ${tarflags}${qflag}xOf $filepath +CONTENTS]
1529    ui_debug $cmdline
1530    if {![catch {set contents [exec {*}$cmdline]}]} {
1531        # the file is probably a valid binary archive
1532        set binary 1
1533        ui_debug "getting port name from binary archive"
1534        # get the portname from the contents file
1535        foreach line [split $contents \n] {
1536            if {[lindex $line 0] eq {@name}} {
1537                # actually ${name}-${version}_$revision
1538                set portname [lindex $line 1]
1539            }
1540        }
1541        ui_debug "port name is '$portname'"
1542
1543        # create a correctly-named directory and put the Portfile there
1544        file mkdir $portname
1545        cd $portname
1546    } else {
1547        # the file is not a valid binary archive, assume it's an archive just
1548        # containing Portfile and the files directory
1549        set binary 0
1550        set portname [file rootname [file tail $filepath]]
1551    }
1552
1553    # extract the portfile (and possibly files dir if not a binary archive)
1554    ui_debug "extracting port archive to [pwd]"
1555    if {$binary} {
1556        set cmdline [list $tarcmd ${tarflags}${qflag}xOf $filepath +PORTFILE > Portfile]
1557    } else {
1558        set cmdline [list $tarcmd ${tarflags}${qflag}xf $filepath]
1559    }
1560    ui_debug $cmdline
1561    if {[catch {exec {*}$cmdline} result]} {
1562        if {!$local} {
1563            # clean up the archive, we don't need it anymore
1564            file delete [file join $fetchdir $fetchfile]
1565        }
1566
1567        cd $oldpwd
1568        return -code error "Port extract failed: $result"
1569    }
1570
1571    if {!$local} {
1572        # clean up the archive, we don't need it anymore
1573        file delete [file join $fetchdir $fetchfile]
1574    }
1575
1576    cd $oldpwd
1577    return [file join $fetchdir $portname]
1578}
1579
1580proc macports::getprotocol {url} {
1581    if {[regexp {(?x)([^:]+)://.+} $url match protocol] == 1} {
1582        return $protocol
1583    } else {
1584        return -code error "Can't parse url $url"
1585    }
1586}
1587
1588##
1589# Return the directory where the port identified by the given \a url is
1590# located. Can be called with either local paths (starting with \c file://), or
1591# local or remote URLs pointing to a tarball that will be extracted.
1592#
1593# @param url URL identifying the port to be installed
1594# @return normalized path to the port's directory, or error when called with an
1595#         unsupported protocol, or if the tarball pointed to by \a url didn't
1596#         contain a Portfile.
1597proc macports::getportdir {url} {
1598    global macports::extracted_portdirs
1599
1600    set protocol [macports::getprotocol $url]
1601    switch -- $protocol {
1602        file {
1603            set path [file normalize [string range $url [expr {[string length $protocol] + 3}] end]]
1604            if {![file isfile $path]} {
1605                # the URL points to a local directory
1606                return $path
1607            } else {
1608                # the URL points to a local tarball that (hopefully) contains a Portfile
1609                # create a local dir for the extracted port, but only once
1610                if {![info exists macports::extracted_portdirs($url)]} {
1611                    set macports::extracted_portdirs($url) [macports::fetch_port $path 1]
1612                }
1613                return $macports::extracted_portdirs($url)
1614            }
1615        }
1616        https -
1617        http -
1618        ftp {
1619            # the URL points to a remote tarball that (hopefully) contains a Portfile
1620            # create a local dir for the extracted port, but only once
1621            if {![info exists macports::extracted_portdirs($url)]} {
1622                set macports::extracted_portdirs($url) [macports::fetch_port $url 0]
1623            }
1624            return $macports::extracted_portdirs($url)
1625        }
1626        default {
1627            return -code error "Unsupported protocol $protocol"
1628        }
1629    }
1630}
1631
1632##
1633# Get the path to the _resources directory of the source
1634#
1635# If the file is not available in the current source, it will fall back to the
1636# default source. This behavior is controlled by the fallback parameter.
1637#
1638# @param url port url
1639# @param path path in _resources we are interested in
1640# @param fallback fall back to the default source tree
1641# @return path to the _resources directory or the path to the fallback
1642proc macports::getportresourcepath {url {path {}} {fallback yes}} {
1643    global macports::sources_default
1644
1645    set protocol [getprotocol $url]
1646
1647    switch -- $protocol {
1648        file {
1649            set proposedpath [file normalize [file join [getportdir $url] .. ..]]
1650        }
1651        default {
1652            set proposedpath [getsourcepath $url]
1653        }
1654    }
1655
1656    # append requested path
1657    set proposedpath [file join $proposedpath _resources $path]
1658
1659    if {$fallback && ![file exists $proposedpath]} {
1660        return [getdefaultportresourcepath $path]
1661    }
1662
1663    return $proposedpath
1664}
1665
1666##
1667# Get the path to the _resources directory of the default source
1668#
1669# @param path path in _resources we are interested in
1670# @return path to the _resources directory of the default source
1671proc macports::getdefaultportresourcepath {{path {}}} {
1672    global macports::sources_default
1673
1674    set default_source_url [lindex $sources_default 0]
1675    if {[getprotocol $default_source_url] eq "file"} {
1676        set proposedpath [getportdir $default_source_url]
1677    } else {
1678        set proposedpath [getsourcepath $default_source_url]
1679    }
1680
1681    # append requested path
1682    set proposedpath [file join $proposedpath _resources $path]
1683
1684    return $proposedpath
1685}
1686
1687
1688##
1689# Opens a MacPorts portfile specified by a URL. The URL can be local (starting
1690# with file://), or remote (http, https, or ftp). In the local case, the URL
1691# can point to a directory containing a Portfile, or to a tarball in the format
1692# detailed below. In the remote case, the URL must point to a tarball. The
1693# Portfile is opened with the given list of options and variations. The result
1694# of this function should be treated as an opaque handle to a MacPorts
1695# Portfile.
1696#
1697# @param porturl URL to the directory of the port to be opened. Can the path to
1698#                a local directory, or an URL (both remote and local) pointing
1699#                to a tarball that
1700#                \li either contains a \c Portfile and possible a \c files
1701#                    directory, or
1702#                \li is a MacPorts binary archive, where the Portfile is in
1703#                    a file called \c +PORTFILE.
1704# @param options an optional array (in list format) of options
1705# @param variations an optional array (ist list format) of variations, passed
1706#                   to \c eval_variants after running the Portfile
1707# @param nocache a non-empty string, if port information caching should be
1708#                avoided.
1709proc mportopen {porturl {options {}} {variations {}} {nocache {}}} {
1710    global macports::portdbpath macports::portconf macports::open_mports auto_path
1711
1712    # Look for an already-open MPort with the same URL.
1713    # if found, return the existing reference and bump the refcount.
1714    if {$nocache ne ""} {
1715        set mport ""
1716    } else {
1717        set mport [dlist_match_multi $macports::open_mports [list porturl $porturl variations $variations options $options]]
1718    }
1719    if {$mport ne ""} {
1720        # just in case more than one somehow matches
1721        set mport [lindex $mport 0]
1722        set refcnt [ditem_key $mport refcnt]
1723        incr refcnt
1724        ditem_key $mport refcnt $refcnt
1725        return $mport
1726    }
1727
1728    # Will download if remote and extract if tarball.
1729    set portpath [macports::getportdir $porturl]
1730    ui_debug "Changing to port directory: $portpath"
1731    cd $portpath
1732    if {![file isfile Portfile]} {
1733        return -code error "Could not find Portfile in $portpath"
1734    }
1735
1736    set workername [interp create]
1737
1738    set mport [ditem_create]
1739    lappend macports::open_mports $mport
1740    ditem_key $mport porturl $porturl
1741    ditem_key $mport portpath $portpath
1742    ditem_key $mport workername $workername
1743    ditem_key $mport options $options
1744    ditem_key $mport variations $variations
1745    ditem_key $mport refcnt 1
1746
1747    macports::worker_init $workername $portpath $porturl [macports::getportbuildpath $portpath] $options $variations
1748
1749    $workername eval source Portfile
1750
1751    # add the default universal variant if appropriate, and set up flags that
1752    # are conditional on whether universal is set
1753    $workername eval universal_setup
1754
1755    # evaluate the variants
1756    if {[$workername eval eval_variants variations] != 0} {
1757        mportclose $mport
1758        error "Error evaluating variants"
1759    }
1760
1761    $workername eval port::run_callbacks
1762
1763    ditem_key $mport provides [$workername eval return \$subport]
1764
1765    return $mport
1766}
1767
1768# mportopen_installed
1769# opens a portfile stored in the registry
1770proc mportopen_installed {name version revision variants options} {
1771    global macports::registry.path
1772    set regref [lindex [registry::entry imaged $name $version $revision $variants] 0]
1773    set portfile_dir [file join ${registry.path} registry portfiles ${name}-${version}_${revision} [$regref portfile]]
1774
1775    set variations {}
1776    set minusvariant [lrange [split [$regref negated_variants] -] 1 end]
1777    set plusvariant [lrange [split [$regref variants] +] 1 end]
1778    foreach v $plusvariant {
1779        lappend variations $v +
1780    }
1781    foreach v $minusvariant {
1782        lappend variations $v -
1783    }
1784
1785    array set options_array $options
1786    set options_array(subport) $name
1787
1788    # find portgroups in registry
1789    set pgdirlist [list]
1790    foreach pg [$regref groups_used] {
1791        lappend pgdirlist [file join ${registry.path} registry portgroups [$pg sha256]-[$pg size]]
1792    }
1793    if {$pgdirlist ne ""} {
1794        set options_array(_portgroup_search_dirs) [list $pgdirlist]
1795    }
1796
1797    return [mportopen file://${portfile_dir}/ [array get options_array] $variations]
1798}
1799
1800# Traverse a directory with ports, calling a function on the path of ports
1801# (at the second depth).
1802# I.e. the structure of dir shall be:
1803# category/port/
1804# with a Portfile file in category/port/
1805#
1806# func:     function to call on every port directory (it is passed
1807#           category/port/ as its parameter)
1808# root:     the directory with all the categories directories.
1809proc mporttraverse {func {root .}} {
1810    # Save the current directory
1811    set pwd [pwd]
1812
1813    # Join the root.
1814    set pathToRoot [file join $pwd $root]
1815
1816    # Go to root because some callers expects us to be there.
1817    cd $pathToRoot
1818
1819    foreach category [lsort -increasing -unique [readdir $root]] {
1820        set pathToCategory [file join $root $category]
1821        # process the category dirs but not _resources
1822        if {[file isdirectory $pathToCategory] && [string index [file tail $pathToCategory] 0] ne "_"} {
1823            # Iterate on port directories.
1824            foreach port [lsort -increasing -unique [readdir $pathToCategory]] {
1825                set pathToPort [file join $pathToCategory $port]
1826                if {[file isdirectory $pathToPort] &&
1827                  [file exists [file join $pathToPort Portfile]]} {
1828                    # Call the function.
1829                    $func [file join $category $port]
1830
1831                    # Restore the current directory because some
1832                    # functions changes it.
1833                    cd $pathToRoot
1834                }
1835            }
1836        }
1837    }
1838
1839    # Restore the current directory.
1840    cd $pwd
1841}
1842
1843### _mportsearchpath is private; subject to change without notice
1844
1845# depregex -> regex on the filename to find.
1846# search_path -> directories to search
1847# executable -> whether we want to check that the file is executable by current
1848#               user or not.
1849proc _mportsearchpath {depregex search_path {executable 0} {return_match 0}} {
1850    set found 0
1851    foreach path $search_path {
1852        if {![file isdirectory $path]} {
1853            continue
1854        }
1855
1856        if {[catch {set filelist [readdir $path]} result]} {
1857            return -code error "$result ($path)"
1858        }
1859
1860        foreach filename $filelist {
1861            if {[regexp $depregex $filename] &&
1862              (($executable == 0) || [file executable [file join $path $filename]])} {
1863                ui_debug "Found Dependency: path: $path filename: $filename regex: $depregex"
1864                set found 1
1865                break
1866            }
1867        }
1868
1869        if {$found} {
1870            break
1871        }
1872    }
1873    if {$return_match} {
1874        if {$found} {
1875            return [file join $path $filename]
1876        } else {
1877            return {}
1878        }
1879    } else {
1880        return $found
1881    }
1882}
1883
1884
1885### _mportinstalled is private; may change without notice
1886
1887# Determine if a port is already *installed*, as in "in the registry".
1888proc _mportinstalled {mport} {
1889    # Check for the presence of the port in the registry
1890    set workername [ditem_key $mport workername]
1891    return [$workername eval registry_exists_for_name \$subport]
1892}
1893
1894# Determine if a port is active
1895proc _mportactive {mport} {
1896    set workername [ditem_key $mport workername]
1897    if {![catch {set reslist [$workername eval registry_active \$subport]}] && [llength $reslist] > 0} {
1898        set i [lindex $reslist 0]
1899        set name [lindex $i 0]
1900        set version [lindex $i 1]
1901        set revision [lindex $i 2]
1902        set variants [lindex $i 3]
1903        array set portinfo [mportinfo $mport]
1904        if {$name eq $portinfo(name) && $version eq $portinfo(version)
1905            && $revision == $portinfo(revision) && $variants eq $portinfo(canonical_active_variants)} {
1906            return 1
1907        }
1908    }
1909    return 0
1910}
1911
1912# Determine if the named port is active
1913proc _portnameactive {portname} {
1914    if {[catch {set reslist [registry::active $portname]}]} {
1915        return 0
1916    } else {
1917        return [expr {[llength $reslist] > 0}]
1918    }
1919}
1920
1921### _mportispresent is private; may change without notice
1922
1923# Determine if some depspec is satisfied or if the given port is installed
1924# and active.
1925# We actually start with the registry (faster?)
1926#
1927# mport     the port declaring the dep (context in which to evaluate $prefix etc)
1928# depspec   the dependency test specification (path, bin, lib, etc.)
1929proc _mportispresent {mport depspec} {
1930    set portname [lindex [split $depspec :] end]
1931    ui_debug "Searching for dependency: $portname"
1932    set res [_portnameactive $portname]
1933    if {$res != 0} {
1934        ui_debug "Found Dependency: receipt exists for $portname"
1935        return 1
1936    } else {
1937        # The receipt test failed, use one of the depspec regex mechanisms
1938        ui_debug "Didn't find receipt, going to depspec regex for: $portname"
1939        set workername [ditem_key $mport workername]
1940        set type [lindex [split $depspec :] 0]
1941        switch -- $type {
1942            lib {return [$workername eval _libtest $depspec]}
1943            bin {return [$workername eval _bintest $depspec]}
1944            path {return [$workername eval _pathtest $depspec]}
1945            port {return 0}
1946            default {return -code error "unknown depspec type: $type"}
1947        }
1948        return 0
1949    }
1950}
1951
1952### _mporterrorifconflictsinstalled is private; may change without notice
1953
1954# Determine if the port, per the conflicts option, has any conflicts
1955# with what is installed. If it does, raises an error unless force
1956# option is set.
1957#
1958# mport   the port to check for conflicts
1959proc _mporterrorifconflictsinstalled {mport} {
1960    set conflictlist {}
1961    array set portinfo [mportinfo $mport]
1962
1963    if {[info exists portinfo(conflicts)] &&
1964        [llength $portinfo(conflicts)] > 0} {
1965        ui_debug "Checking for conflicts against [_mportkey $mport subport]"
1966        foreach conflictport $portinfo(conflicts) {
1967            if {[_mportispresent $mport port:$conflictport]} {
1968                lappend conflictlist $conflictport
1969            }
1970        }
1971    } else {
1972        ui_debug "[_mportkey $mport subport] has no conflicts"
1973    }
1974
1975    if {[llength $conflictlist] != 0} {
1976        if {[macports::global_option_isset ports_force]} {
1977            ui_warn "Force option set; installing $portinfo(name) despite conflicts with: $conflictlist"
1978        } else {
1979            if {![macports::ui_isset ports_debug]} {
1980                ui_msg {}
1981            }
1982            ui_error "Can't install $portinfo(name) because conflicting ports are active: $conflictlist"
1983            return -code error "conflicting ports"
1984        }
1985    }
1986}
1987
1988### _mportexec is private; may change without notice
1989
1990proc _mportexec {target mport} {
1991    set portname [_mportkey $mport subport]
1992    macports::push_log $mport
1993    # xxx: set the work path?
1994    set workername [ditem_key $mport workername]
1995    $workername eval validate_macportsuser
1996    if {![catch {$workername eval check_variants $target} result] && $result == 0 &&
1997        ![catch {$workername eval check_supported_archs} result] && $result == 0 &&
1998        ![catch {$workername eval eval_targets $target} result] && $result == 0} {
1999        # If auto-clean mode, clean-up after dependency install
2000        if {$macports::portautoclean} {
2001            # Make sure we are back in the port path before clean
2002            # otherwise if the current directory had been changed to
2003            # inside the port,  the next port may fail when trying to
2004            # install because [pwd] will return a "no file or directory"
2005            # error since the directory it was in is now gone.
2006            set portpath [ditem_key $mport portpath]
2007            catch {cd $portpath}
2008            $workername eval eval_targets clean
2009        }
2010        # XXX hack to avoid running out of fds due to sqlite temp files, ticket #24857
2011        interp delete $workername
2012        macports::pop_log
2013        return 0
2014    } else {
2015        # An error occurred.
2016        global ::logenabled ::debuglogname
2017        ui_debug $::errorInfo
2018        if {[info exists ::logenabled] && $::logenabled && [info exists ::debuglogname]} {
2019            ui_error "See $::debuglogname for details."
2020        }
2021        macports::pop_log
2022        return 1
2023    }
2024}
2025
2026# mportexec
2027# Execute the specified target of the given mport.
2028proc mportexec {mport target} {
2029    set workername [ditem_key $mport workername]
2030
2031    # check for existence of macportsuser and use fallback if necessary
2032    $workername eval validate_macportsuser
2033    # check variants
2034    if {[$workername eval check_variants $target] != 0} {
2035        return 1
2036    }
2037    set portname [_mportkey $mport subport]
2038    set log_needs_pop no
2039    if {$target ne "clean"} {
2040        macports::push_log $mport
2041        set log_needs_pop yes
2042    }
2043
2044    # Use _target_needs_deps as a proxy for whether we're going to
2045    # build and will therefore need to check Xcode version and
2046    # supported_archs.
2047    if {[macports::_target_needs_deps $target]} {
2048        # possibly warn or error out depending on how old xcode is
2049        if {[$workername eval _check_xcode_version] != 0} {
2050            if {$log_needs_pop} {
2051                macports::pop_log
2052            }
2053            return 1
2054        }
2055        # error out if selected arch(s) not supported by this port
2056        if {[$workername eval check_supported_archs] != 0} {
2057            if {$log_needs_pop} {
2058                macports::pop_log
2059            }
2060            return 1
2061        }
2062    }
2063
2064    # Before we build the port, we must build its dependencies.
2065    set dlist {}
2066    if {[macports::_target_needs_deps $target] && [macports::_mport_has_deptypes $mport [macports::_deptypes_for_target $target $workername]]} {
2067        registry::exclusive_lock
2068        # see if we actually need to build this port
2069        if {$target ni {activate install} ||
2070            ![$workername eval registry_exists {$subport} {$version} {$revision} {$portvariants}]} {
2071
2072            # upgrade dependencies that are already installed
2073            if {![macports::global_option_isset ports_nodeps]} {
2074                macports::_upgrade_mport_deps $mport $target
2075            }
2076        }
2077
2078        ui_msg -nonewline "$macports::ui_prefix Computing dependencies for [_mportkey $mport subport]"
2079        if {[macports::ui_isset ports_debug]} {
2080            # play nice with debug messages
2081            ui_msg {}
2082        }
2083        if {[mportdepends $mport $target] != 0} {
2084            if {$log_needs_pop} {
2085                macports::pop_log
2086            }
2087            return 1
2088        }
2089        if {![macports::ui_isset ports_debug]} {
2090            ui_msg {}
2091        }
2092
2093        # Select out the dependents along the critical path,
2094        # but exclude this mport, we might not be installing it.
2095        set dlist [dlist_append_dependents $macports::open_mports $mport {}]
2096
2097        dlist_delete dlist $mport
2098
2099        # print the dep list
2100        if {[llength $dlist] > 0} {
2101            ##
2102            # User Interaction Question
2103            # Asking before installing dependencies
2104            if {[info exists macports::ui_options(questions_yesno)]} {
2105                set deplist {}
2106                foreach ditem $dlist {
2107                    lappend deplist [ditem_key $ditem provides]
2108                }
2109                set retvalue [$macports::ui_options(questions_yesno) "The following dependencies will be installed: " "TestCase#2" [lsort $deplist] {y} 0]
2110                if {$retvalue == 1} {
2111                    if {$log_needs_pop} {
2112                        macports::pop_log
2113                    }
2114                    foreach ditem $dlist {
2115                        mportclose $ditem
2116                    }
2117                    return 0
2118                } 
2119            } else {
2120                set depstring "$macports::ui_prefix Dependencies to be installed:"
2121                foreach ditem $dlist {
2122                    append depstring " [ditem_key $ditem provides]"
2123                }
2124                ui_msg $depstring
2125            }
2126        }
2127
2128        # install them
2129        set result [dlist_eval $dlist _mportactive [list _mportexec activate]]
2130
2131        registry::exclusive_unlock
2132
2133        if {$result ne ""} {
2134            ##
2135            # When this happens, the failing port usually already printed an
2136            # error message. Omit this one to avoid cluttering the output and
2137            # hiding the *real* problem.
2138
2139            #set errstring "The following dependencies were not installed:"
2140            #foreach ditem $result {
2141            #    append errstring " [ditem_key $ditem provides]"
2142            #}
2143            #ui_error $errstring
2144            foreach ditem $dlist {
2145                catch {mportclose $ditem}
2146            }
2147            if {$log_needs_pop} {
2148                macports::pop_log
2149            }
2150            return 1
2151        }
2152
2153        # Close the dependencies, we're done installing them.
2154        foreach ditem $dlist {
2155            mportclose $ditem
2156        }
2157    } else {
2158        # No dependencies, but we still need to check for conflicts.
2159        if {$target eq "" || $target eq "install" || $target eq "activate"} {
2160            if {[catch {_mporterrorifconflictsinstalled $mport}]} {
2161                if {$log_needs_pop} {
2162                    macports::pop_log
2163                }
2164                return 1
2165            }
2166        }
2167    }
2168
2169    set clean 0
2170    if {$macports::portautoclean && ($target eq "install" || $target eq "activate")} {
2171        # If we're doing an install, check if we should clean after
2172        set clean 1
2173    }
2174
2175    # Build this port with the specified target
2176    set result [$workername eval eval_targets $target]
2177
2178    # If auto-clean mode and successful install, clean-up after install
2179    if {$result == 0 && $clean == 1} {
2180        # Make sure we are back in the port path, just in case
2181        set portpath [ditem_key $mport portpath]
2182        catch {cd $portpath}
2183        $workername eval eval_targets clean
2184    }
2185
2186    global ::logenabled ::debuglogname
2187    if {$result != 0 && [info exists ::logenabled] && $::logenabled && [info exists ::debuglogname]} {
2188        ui_error "See $::debuglogname for details."
2189    }
2190
2191    if {$log_needs_pop} {
2192        macports::pop_log
2193    }
2194
2195    return $result
2196}
2197
2198# upgrade any dependencies of mport that are installed and needed for target
2199proc macports::_upgrade_mport_deps {mport target} {
2200    set options [ditem_key $mport options]
2201    set workername [ditem_key $mport workername]
2202    set deptypes [macports::_deptypes_for_target $target $workername]
2203    array set portinfo [mportinfo $mport]
2204    array set depscache {}
2205
2206    set required_archs [$workername eval get_canonical_archs]
2207    set depends_skip_archcheck [_mportkey $mport depends_skip_archcheck]
2208
2209    # Pluralize "arch" appropriately.
2210    set s [expr {[llength $required_archs] == 1 ? "" : "s"}]
2211
2212    set test _portnameactive
2213
2214    foreach deptype $deptypes {
2215        if {![info exists portinfo($deptype)]} {
2216            continue
2217        }
2218        foreach depspec $portinfo($deptype) {
2219            set dep_portname [$workername eval _get_dep_port $depspec]
2220            if {$dep_portname ne "" && ![info exists depscache(port:$dep_portname)] && [$test $dep_portname]} {
2221                set variants {}
2222
2223                # check that the dep has the required archs
2224                set active_archs [_get_registry_archs $dep_portname]
2225                if {$deptype ni {depends_fetch depends_extract} && $active_archs ni {{} noarch}
2226                    && $required_archs ne "noarch" && $dep_portname ni $depends_skip_archcheck} {
2227                    set missing {}
2228                    foreach arch $required_archs {
2229                        if {$arch ni $active_archs} {
2230                            lappend missing $arch
2231                        }
2232                    }
2233                    if {[llength $missing] > 0} {
2234                        set res [mportlookup $dep_portname]
2235                        array unset dep_portinfo
2236                        array set dep_portinfo [lindex $res 1]
2237                        if {[info exists dep_portinfo(installs_libs)] && !$dep_portinfo(installs_libs)} {
2238                            set missing {}
2239                        }
2240                    }
2241                    if {[llength $missing] > 0} {
2242                        if {[info exists dep_portinfo(variants)] && "universal" in $dep_portinfo(variants)} {
2243                            # dep offers a universal variant
2244                            if {[llength $active_archs] == 1} {
2245                                # not installed universal
2246                                set missing {}
2247                                foreach arch $required_archs {
2248                                    if {$arch ni $macports::universal_archs} {
2249                                        lappend missing $arch
2250                                    }
2251                                }
2252                                if {[llength $missing] > 0} {
2253                                    ui_error "Cannot install [_mportkey $mport subport] for the arch${s} '$required_archs' because"
2254                                    ui_error "its dependency $dep_portname is only installed for the arch '$active_archs'"
2255                                    ui_error "and the configured universal_archs '$macports::universal_archs' are not sufficient."
2256                                    return -code error "architecture mismatch"
2257                                } else {
2258                                    # upgrade the dep with +universal
2259                                    lappend variants universal +
2260                                    lappend options ports_upgrade_enforce-variants yes
2261                                    ui_debug "enforcing +universal upgrade for $dep_portname"
2262                                }
2263                            } else {
2264                                # already universal
2265                                ui_error "Cannot install [_mportkey $mport subport] for the arch${s} '$required_archs' because"
2266                                ui_error "its dependency $dep_portname is only installed for the archs '$active_archs'."
2267                                return -code error "architecture mismatch"
2268                            }
2269                        } else {
2270                            ui_error "Cannot install [_mportkey $mport subport] for the arch${s} '$required_archs' because"
2271                            ui_error "its dependency $dep_portname is only installed for the arch '$active_archs'"
2272                            ui_error "and does not have a universal variant."
2273                            return -code error "architecture mismatch"
2274                        }
2275                    }
2276                }
2277
2278                set status [macports::upgrade $dep_portname port:$dep_portname $variants $options depscache]
2279                # status 2 means the port was not found in the index
2280                if {$status != 0 && $status != 2 && ![macports::ui_isset ports_processall]} {
2281                    return -code error "upgrade $dep_portname failed"
2282                }
2283            }
2284        }
2285    }
2286}
2287
2288# get the archs with which the active version of portname is installed
2289proc macports::_get_registry_archs {portname} {
2290    set ilist [registry::active $portname]
2291    set i [lindex $ilist 0]
2292    set regref [registry::open_entry [lindex $i 0] [lindex $i 1] [lindex $i 2] [lindex $i 3] [lindex $i 5]]
2293    set archs [registry::property_retrieve $regref archs]
2294    if {$archs == 0} {
2295        set archs {}
2296    }
2297    return $archs
2298}
2299
2300proc macports::getsourcepath {url} {
2301    global macports::portdbpath
2302
2303    set source_path [split $url ://]
2304
2305    if {[_source_is_snapshot $url]} {
2306        # daily snapshot tarball
2307        return [file join $portdbpath sources [join [lrange $source_path 3 end-1] /] ports]
2308    }
2309
2310    return [file join $portdbpath sources [lindex $source_path 3] [lindex $source_path 4] [lindex $source_path 5]]
2311}
2312
2313##
2314# Checks whether a supplied source URL is for a daily snapshot tarball
2315# (private)
2316#
2317# @param url source URL to check
2318# @return a list containing filename and extension or an empty list
2319proc _source_is_snapshot {url {filename {}} {extension {}}} {
2320    upvar $filename myfilename
2321    upvar $extension myextension
2322
2323    if {[regexp {^(?:https?|ftp|rsync)://.+/(.+\.(tar\.gz|tar\.bz2|tar))$} $url -> f e]} {
2324        set myfilename $f
2325        set myextension $e
2326
2327        return 1
2328    }
2329
2330    return 0
2331}
2332
2333proc macports::getportbuildpath {id {portname {}}} {
2334    global macports::portdbpath
2335    regsub {://} $id {.} port_path
2336    regsub -all {/} $port_path {_} port_path
2337    return [file join $portdbpath build $port_path $portname]
2338}
2339
2340proc macports::getportlogpath {id {portname {}}} {
2341    global macports::portdbpath
2342    regsub {://} $id {.} port_path
2343    regsub -all {/} $port_path {_} port_path
2344    return [file join $portdbpath logs $port_path $portname]
2345}
2346
2347proc macports::getportworkpath_from_buildpath {portbuildpath} {
2348    return [file normalize [file join $portbuildpath work]]
2349}
2350
2351proc macports::getportworkpath_from_portdir {portpath {portname {}}} {
2352    return [macports::getportworkpath_from_buildpath [macports::getportbuildpath $portpath $portname]]
2353}
2354
2355proc macports::getindex {source} {
2356    # Special case file:// sources
2357    if {[macports::getprotocol $source] eq "file"} {
2358        return [file join [macports::getportdir $source] PortIndex]
2359    }
2360
2361    return [file join [macports::getsourcepath $source] PortIndex]
2362}
2363
2364# macports::GetVCSUpdateCmd --
2365#
2366# Determine whether the given directory is associated with a repository
2367# for a supported version control system. If so, return a list
2368# containing two strings:
2369#
2370#   1) The human-readable name of the version control system.
2371#   2) A command that will update the repository's working tree to the
2372#      latest commit/changeset/revision/whatever. This command should
2373#      work properly from any working directory, although it doesn't
2374#      have to worry about cleaning up after itself (restoring the
2375#      environment, changing back to the initial directory, etc.).
2376#
2377# If the directory is not associated with any supported system, return
2378# an empty list.
2379#
2380proc macports::GetVCSUpdateCmd portDir {
2381
2382    set oldPWD [pwd]
2383    cd $portDir
2384
2385    # Subversion
2386    if {![catch {macports::findBinary svn} svn] &&
2387        ([file exists .svn] ||
2388         ![catch {exec $svn info >/dev/null 2>@1}])
2389    } then {
2390        return [list Subversion "$svn update --non-interactive $portDir"]
2391    }
2392
2393    # Git
2394    if {![catch {macports::findBinary git} git] &&
2395        ![catch {exec $git rev-parse --is-inside-work-tree}]
2396    } then {
2397        if {![catch {exec $git config --local --get svn-remote.svn.url}]} {
2398            # git-svn repository
2399            return [list git-svn "cd $portDir && $git svn rebase || true"]
2400        }
2401        # regular git repository
2402        return [list Git "cd $portDir && $git pull --rebase || true"]
2403    }
2404
2405    # Add new VCSes here!
2406
2407    cd $oldPWD
2408    return [list]
2409}
2410
2411# macports::UpdateVCS --
2412#
2413# Execute the given command in a shell. If called with superuser
2414# privileges, execute the command as the user/group that owns the given
2415# directory, restoring privileges before returning.
2416#
2417# This proc could probably be generalized and used elsewhere.
2418#
2419proc macports::UpdateVCS {cmd portDir} {
2420    if {[getuid] == 0} {
2421        # Must change egid before dropping root euid.
2422        set oldEGID [getegid]
2423        set newEGID [name_to_gid [file attributes $portDir -group]]
2424        setegid $newEGID
2425        ui_debug "Changed effective group ID from $oldEGID to $newEGID"
2426        set oldEUID [geteuid]
2427        set newEUID [name_to_uid [file attributes $portDir -owner]]
2428        seteuid $newEUID
2429        ui_debug "Changed effective user ID from $oldEUID to $newEUID"
2430    }
2431    ui_debug $cmd
2432    catch {system $cmd} result options
2433    if {[getuid] == 0} {
2434        seteuid $oldEUID
2435        ui_debug "Changed effective user ID from $newEUID to $oldEUID"
2436        setegid $oldEGID
2437        ui_debug "Changed effective group ID from $newEGID to $oldEGID"
2438    }
2439    return -options $options $result
2440}
2441
2442proc mportsync {{optionslist {}}} {
2443    global macports::sources macports::portdbpath macports::rsync_options \
2444           tcl_platform macports::portverbose macports::autoconf::rsync_path \
2445           macports::autoconf::tar_path macports::autoconf::openssl_path \
2446           macports::ui_options
2447    array set options $optionslist
2448    if {[info exists options(no_reindex)]} {
2449        upvar $options(needed_portindex_var) any_needed_portindex
2450    }
2451
2452    set numfailed 0
2453
2454    ui_msg "$macports::ui_prefix Updating the ports tree"
2455    foreach source $sources {
2456        set flags [lrange $source 1 end]
2457        set source [lindex $source 0]
2458        if {"nosync" in $flags} {
2459            ui_debug "Skipping $source"
2460            continue
2461        }
2462        set needs_portindex false
2463        ui_info "Synchronizing local ports tree from $source"
2464        switch -regexp -- [macports::getprotocol $source] {
2465            {^file$} {
2466                set portdir [macports::getportdir $source]
2467                try -pass_signal {
2468                    set repoInfo [macports::GetVCSUpdateCmd $portdir] 
2469                } catch {*} {
2470                    ui_debug $::errorInfo
2471                    ui_info "Could not access contents of $portdir"
2472                    incr numfailed
2473                    continue
2474                }
2475                if {[llength $repoInfo]} {
2476                    lassign $repoInfo vcs cmd
2477                    try -pass_signal {
2478                        macports::UpdateVCS $cmd $portdir
2479                    } catch {*} {
2480                        ui_debug $::errorInfo
2481                        ui_info "Syncing local $vcs ports tree failed"
2482                        incr numfailed
2483                        continue
2484                    }
2485                }
2486                set needs_portindex true
2487            }
2488            {^rsync$} {
2489                # Where to, boss?
2490                set indexfile [macports::getindex $source]
2491                set destdir [file dirname $indexfile]
2492                set is_tarball [_source_is_snapshot $source]
2493                file mkdir $destdir
2494
2495                if {$is_tarball} {
2496                    set exclude_option {}
2497                    # need to do a few things before replacing the ports tree in this case
2498                    set destdir [file dirname $destdir]
2499                } else {
2500                    # Keep rsync happy with a trailing slash
2501                    if {[string index $source end] ne "/"} {
2502                        append source /
2503                    }
2504                    # don't sync PortIndex yet; we grab the platform specific one afterwards
2505                    set exclude_option '--exclude=/PortIndex*'
2506                }
2507                # Do rsync fetch
2508                set rsync_commandline "$macports::autoconf::rsync_path $rsync_options $exclude_option $source $destdir"
2509                try -pass_signal {
2510                    system $rsync_commandline
2511                } catch {*} {
2512                    ui_error "Synchronization of the local ports tree failed doing rsync"
2513                    incr numfailed
2514                    continue
2515                }
2516
2517                if {$is_tarball} {
2518                    # verify signature for tarball
2519                    global macports::archivefetch_pubkeys
2520                    set rsync_commandline "$macports::autoconf::rsync_path $rsync_options $exclude_option ${source}.rmd160 $destdir"
2521                    try -pass_signal {
2522                        system $rsync_commandline
2523                    } catch {*} {
2524                        ui_error "Synchronization of the ports tree signature failed doing rsync"
2525                        incr numfailed
2526                        continue
2527                    }
2528                    set tarball ${destdir}/[file tail $source]
2529                    set signature ${tarball}.rmd160
2530                    set openssl [macports::findBinary openssl $macports::autoconf::openssl_path]
2531                    set verified 0
2532                    foreach pubkey $macports::archivefetch_pubkeys {
2533                        try -pass_signal {
2534                            exec $openssl dgst -ripemd160 -verify $pubkey -signature $signature $tarball
2535                            set verified 1
2536                            ui_debug "successful verification with key $pubkey"
2537                            break
2538                        } catch {{*} eCode eMessage} {
2539                            ui_debug "failed verification with key $pubkey"
2540                            ui_debug "openssl output: $eMessage"
2541                        }
2542                    }
2543                    if {!$verified} {
2544                        ui_error "Failed to verify signature for ports tree!"
2545                        incr numfailed
2546                        continue
2547                    }
2548
2549                    # extract tarball and move into place
2550                    set tar [macports::findBinary tar $macports::autoconf::tar_path]
2551                    file mkdir ${destdir}/tmp
2552                    set tar_cmd "$tar -C ${destdir}/tmp -xf $tarball"
2553                    try -pass_signal {
2554                        system $tar_cmd
2555                    } catch {{*} eCode eMessage} {
2556                        ui_error "Failed to extract ports tree from tarball: $eMessage"
2557                        incr numfailed
2558                        continue
2559                    }
2560                    # save the local PortIndex data
2561                    if {[file isfile $indexfile]} {
2562                        file copy -force $indexfile ${destdir}/
2563                        file rename -force $indexfile ${destdir}/tmp/ports/
2564                        if {[file isfile ${indexfile}.quick]} {
2565                            file rename -force ${indexfile}.quick ${destdir}/tmp/ports/
2566                        }
2567                    }
2568                    file delete -force ${destdir}/ports
2569                    file rename ${destdir}/tmp/ports ${destdir}/ports
2570                    file delete -force ${destdir}/tmp
2571                }
2572
2573                set needs_portindex true
2574                # now sync the index if the local file is missing or older than a day
2575                if {![file isfile $indexfile] || [clock seconds] - [file mtime $indexfile] > 86400
2576                      || [info exists options(no_reindex)]} {
2577                    if {$is_tarball} {
2578                        # chop ports.tar off the end
2579                        set index_source [string range $source 0 end-[string length [file tail $source]]]
2580                    } else {
2581                        set index_source $source
2582                    }
2583                    set remote_indexfile "${index_source}PortIndex_${macports::os_platform}_${macports::os_major}_${macports::os_arch}/PortIndex"
2584                    set rsync_commandline "$macports::autoconf::rsync_path $rsync_options $remote_indexfile $destdir"
2585                    try -pass_signal {
2586                        system $rsync_commandline
2587                       
2588                        set ok 1
2589                        set needs_portindex false
2590                        if {$is_tarball} {
2591                            set ok 0
2592                            set needs_portindex true
2593                            # verify signature for PortIndex
2594                            set rsync_commandline "$macports::autoconf::rsync_path $rsync_options ${remote_indexfile}.rmd160 $destdir"
2595                            system $rsync_commandline
2596                            foreach pubkey $macports::archivefetch_pubkeys {
2597                                try -pass_signal {
2598                                    exec $openssl dgst -ripemd160 -verify $pubkey -signature ${destdir}/PortIndex.rmd160 ${destdir}/PortIndex
2599                                    set ok 1
2600                                    set needs_portindex false
2601                                    ui_debug "successful verification with key $pubkey"
2602                                    break
2603                                } catch {{*} eCode eMessage} {
2604                                    ui_debug "failed verification with key $pubkey"
2605                                    ui_debug "openssl output: $eMessage"
2606                                }
2607                            }
2608                            if {$ok} {
2609                                # move PortIndex into place
2610                                file rename -force ${destdir}/PortIndex ${destdir}/ports/
2611                            }
2612                        }
2613                        if {$ok} {
2614                            mports_generate_quickindex $indexfile
2615                        }
2616                    } catch {*} {
2617                        ui_debug "Synchronization of the PortIndex failed doing rsync"
2618                    }
2619                }
2620                try -pass_signal {
2621                    system [list chmod -R a+r $destdir]
2622                } catch {*} {
2623                    ui_warn "Setting world read permissions on parts of the ports tree failed, need root?"
2624                }
2625            }
2626            {^https?$|^ftp$} {
2627                if {![_source_is_snapshot $source filename extension]} {
2628                    ui_error "Synchronization using http, https and ftp only supported with tarballs."
2629                    ui_error "The source ${source} doesn't seem to point to a tarball."
2630                    ui_error "Please switch to a different sync protocol (e.g. rsync) in your sources.conf"
2631                    ui_error "Remove the line mentioned above from your sources.conf to silence this error."
2632                    incr numfailed
2633                    continue
2634                }
2635                # sync a daily port snapshot tarball
2636                set indexfile [macports::getindex $source]
2637                set destdir [file dirname $indexfile]
2638                set tarpath [file join [file normalize [file join $destdir ..]] $filename]
2639
2640                set updated 1
2641                if {[file isdirectory $destdir]} {
2642                    set moddate [file mtime $destdir]
2643                    # XXX, catch, don't fix rarely used code
2644                    if {[catch {set updated [curl isnewer $source $moddate]} error]} {
2645                        ui_warn "Cannot check if $source was updated, ($error)"
2646                    }
2647                }
2648
2649                if {(![info exists options(ports_force)] || !$options(ports_force)) && $updated <= 0} {
2650                    ui_info "No updates for $source"
2651                    continue
2652                }
2653
2654                file mkdir $destdir
2655
2656                set progressflag {}
2657                if {$macports::portverbose} {
2658                    set progressflag "--progress builtin"
2659                    set verboseflag "-v"
2660                } elseif {[info exists macports::ui_options(progress_download)]} {
2661                    set progressflag "--progress ${macports::ui_options(progress_download)}"
2662                    set verboseflag ""
2663                }
2664                try -pass_signal {
2665                    curl fetch {*}$progressflag $source $tarpath
2666                } catch {{*} eCode eMessage} {
2667                    ui_error [msgcat::mc "Fetching %s failed: %s" $source $eMessage]
2668                    incr numfailed
2669                    continue
2670                }
2671
2672                set extflag {}
2673                switch -- $extension {
2674                    {tar.gz} {
2675                        set extflag -z
2676                    }
2677                    {tar.bz2} {
2678                        set extflag -j
2679                    }
2680                }
2681
2682                set tar [macports::findBinary tar $macports::autoconf::tar_path]
2683                if {[catch {system "cd ${destdir}/.. && $tar $verboseflag $extflag -xf $filename"} error]} {
2684                    ui_error "Extracting $source failed ($error)"
2685                    incr numfailed
2686                    continue
2687                }
2688
2689                if {[catch {system "chmod -R a+r \"$destdir\""}]} {
2690                    ui_warn "Setting world read permissions on parts of the ports tree failed, need root?"
2691                }
2692
2693                set platindex "PortIndex_${macports::os_platform}_${macports::os_major}_${macports::os_arch}/PortIndex"
2694                if {[file isfile ${destdir}/$platindex] && [file isfile ${destdir}/${platindex}.quick]} {
2695                    file rename -force ${destdir}/$platindex ${destdir}/${platindex}.quick $destdir
2696                }
2697
2698                file delete $tarpath
2699            }
2700            {^mports$} {
2701                ui_error "Synchronization using the mports protocol no longer supported."
2702                ui_error "Please switch to a different sync protocol (e.g. rsync) in your sources.conf"
2703                ui_error "Remove the line starting with mports:// from your sources.conf to silence this error."
2704                incr numfailed
2705                continue
2706            }
2707            default {
2708                ui_warn "Unknown synchronization protocol for $source"
2709            }
2710        }
2711
2712        if {$needs_portindex} {
2713            set any_needed_portindex true
2714            if {![info exists options(no_reindex)]} {
2715                global macports::prefix
2716                set indexdir [file dirname [macports::getindex $source]]
2717                if {[catch {system "${macports::prefix}/bin/portindex $indexdir"}]} {
2718                    ui_error "updating PortIndex for $source failed"
2719                }
2720            }
2721        }
2722    }
2723
2724    # refresh the quick index if necessary (batch or interactive run)
2725    if {[info exists macports::ui_options(ports_commandfiles)]} {
2726        _mports_load_quickindex
2727    }
2728
2729    if {$numfailed == 1} {
2730        return -code error "Synchronization of 1 source failed"
2731    }
2732    if {$numfailed >= 2} {
2733        return -code error "Synchronization of $numfailed sources failed"
2734    }
2735}
2736
2737##
2738# Searches all configured port sources for a given pattern in a given field
2739# using a given matching style and optional case-sensitivity.
2740#
2741# @param pattern pattern to search for; will be interpreted according to the \a
2742#                matchstyle parameter
2743# @param case_sensitive "yes", if a case-sensitive search should be performed,
2744#                       "no" otherwise. Defaults to "yes".
2745# @param matchstyle One of the values \c exact, \c glob and \c regexp, where \c
2746#                   exact performs a standard string comparison, \c glob
2747#                   performs Tcl string matching using <tt>[string match]</tt>
2748#                   and \c regexp interprets \a pattern as a regular
2749#                   expression.
2750# @param field name of the field to apply \a pattern to. Must be one of the
2751#              fields available in the used portindex. The portindex currently
2752#              contains
2753#                \li \c name (the default)
2754#                \li \c homepage
2755#                \li \c description
2756#                \li \c long_description
2757#                \li \c license
2758#                \li \c categories
2759#                \li \c platforms
2760#                \li \c maintainers
2761#                \li \c variants
2762#                \li \c portdir
2763#                \li all \c depends_* values
2764#                \li \c epoch
2765#                \li \c version
2766#                \li \c revision
2767#                \li \c replaced_by
2768#                \li \c installs_libs
2769# @return a list where each even index (starting with 0) contains the name of
2770#         a matching port. Each entry at an odd index is followed by its
2771#         corresponding line from the portindex, which can be passed to
2772#         <tt>array set</tt>. The whole return value can also be passed to
2773#         <tt>array set</tt> to create an associate array where the port names
2774#         are the keys and the lines from portindex are the values.
2775proc mportsearch {pattern {case_sensitive yes} {matchstyle regexp} {field name}} {
2776    global macports::sources
2777    set matches [list]
2778    set easy [expr {$field eq "name"}]
2779
2780    set found 0
2781    foreach source $sources {
2782        set source [lindex $source 0]
2783        set protocol [macports::getprotocol $source]
2784        try -pass_signal {
2785            set fd [open [macports::getindex $source] r]
2786
2787            try -pass_signal {
2788                incr found 1
2789                while {[gets $fd line] >= 0} {
2790                    array unset portinfo
2791                    set name [lindex $line 0]
2792                    set len  [lindex $line 1]
2793                    set line [read $fd $len]
2794
2795                    if {$easy} {
2796                        set target $name
2797                    } else {
2798                        array set portinfo $line
2799                        if {![info exists portinfo($field)]} {
2800                            continue
2801                        }
2802                        set target $portinfo($field)
2803                    }
2804
2805                    switch -- $matchstyle {
2806                        exact {
2807                            if {$case_sensitive} {
2808                                set compres [string compare $pattern $target]
2809                            } else {
2810                                set compres [string compare -nocase $pattern $target]
2811                            }
2812                            set matchres [expr {0 == $compres}]
2813                        }
2814                        glob {
2815                            if {$case_sensitive} {
2816                                set matchres [string match $pattern $target]
2817                            } else {
2818                                set matchres [string match -nocase $pattern $target]
2819                            }
2820                        }
2821                        regexp {
2822                            if {$case_sensitive} {
2823                                set matchres [regexp -- $pattern $target]
2824                            } else {
2825                                set matchres [regexp -nocase -- $pattern $target]
2826                            }
2827                        }
2828                        default {
2829                            return -code error "mportsearch: Unsupported matching style: ${matchstyle}."
2830                        }
2831                    }
2832
2833                    if {$matchres == 1} {
2834                        if {$easy} {
2835                            array set portinfo $line
2836                        }
2837                        switch -- $protocol {
2838                            rsync {
2839                                # Rsync files are local
2840                                set source_url file://[macports::getsourcepath $source]
2841                            }
2842                            https -
2843                            http -
2844                            ftp {
2845                                # daily snapshot tarball
2846                                set source_url file://[macports::getsourcepath $source]
2847                            }
2848                            default {
2849                                set source_url $source
2850                            }
2851                        }
2852                        if {[info exists portinfo(portdir)]} {
2853                            set porturl ${source_url}/$portinfo(portdir)
2854                            lappend line porturl $porturl
2855                            ui_debug "Found port in $porturl"
2856                        } else {
2857                            ui_debug "Found port info: $line"
2858                        }
2859                        lappend matches $name
2860                        lappend matches $line
2861                    }
2862                }
2863            } catch * {
2864                ui_warn "It looks like your PortIndex file for $source may be corrupt."
2865                throw
2866            } finally {
2867                close $fd
2868            }
2869        } catch {*} {
2870            ui_warn "Can't open index file for source: $source"
2871        }
2872    }
2873    if {!$found} {
2874        return -code error "No index(es) found! Have you synced your port definitions? Try running 'port selfupdate'."
2875    }
2876
2877    return $matches
2878}
2879
2880##
2881# Returns the PortInfo for a single named port. The info comes from the
2882# PortIndex, and name matching is case-insensitive. Unlike mportsearch, only
2883# the first match is returned, but the return format is otherwise identical.
2884# The advantage is that mportlookup is usually much faster than mportsearch,
2885# due to the use of the quick index, which is a name-based index into the
2886# PortIndex.
2887#
2888# @param name name of the port to look up. Returns the first match while
2889#             traversing the sources in-order.
2890# @return associative array in list form where the first field is the port name
2891#         and the second field is the line from PortIndex containing the port
2892#         info. See the return value of mportsearch().
2893# @see mportsearch()
2894proc mportlookup {name} {
2895    global macports::portdbpath macports::sources macports::quick_index
2896
2897    set sourceno 0
2898    set matches [list]
2899    foreach source $sources {
2900        set source [lindex $source 0]
2901        set protocol [macports::getprotocol $source]
2902        if {![info exists quick_index(${sourceno},[string tolower $name])]} {
2903            # no entry in this source, advance to next source
2904            incr sourceno 1
2905            continue
2906        }
2907        # The quick index is keyed on the port name, and provides the offset in
2908        # the main PortIndex where the given port's PortInfo line can be found.
2909        set offset $quick_index(${sourceno},[string tolower $name])
2910        incr sourceno 1
2911        if {[catch {set fd [open [macports::getindex $source] r]} result]} {
2912            ui_warn "Can't open index file for source: $source"
2913        } else {
2914            try -pass_signal {
2915                seek $fd $offset
2916                gets $fd line
2917                set name [lindex $line 0]
2918                set len  [lindex $line 1]
2919                set line [read $fd $len]
2920
2921                array set portinfo $line
2922
2923                switch -- $protocol {
2924                    rsync {
2925                        set source_url file://[macports::getsourcepath $source]
2926                    }
2927                    https -
2928                    http -
2929                    ftp {
2930                        set source_url file://[macports::getsourcepath $source]
2931                    }
2932                    default {
2933                        set source_url $source
2934                    }
2935                }
2936                if {[info exists portinfo(portdir)]} {
2937                    lappend line porturl ${source_url}/$portinfo(portdir)
2938                }
2939                lappend matches $name
2940                lappend matches $line
2941            } catch * {
2942                ui_warn "It looks like your PortIndex file for $source may be corrupt."
2943            } finally {
2944                close $fd
2945            }
2946            if {[llength $matches] > 0} {
2947                # if we have a match, exit. If we don't, continue with the next
2948                # source.
2949                break
2950            }
2951        }
2952    }
2953
2954    return $matches
2955}
2956
2957##
2958# Returns all ports in the indices. Faster than 'mportsearch .*' because of the
2959# lack of matching.
2960#
2961# @return associative array in list form where the first field is the port name
2962#         and the second field is the line from PortIndex containing the port
2963#         info. See the return value of mportsearch().
2964# @see mportsearch()
2965proc mportlistall {} {
2966    global macports::sources
2967    set matches [list]
2968
2969    set found 0
2970    foreach source $sources {
2971        set source [lindex $source 0]
2972        set protocol [macports::getprotocol $source]
2973        try -pass_signal {
2974            set fd [open [macports::getindex $source] r]
2975
2976            try -pass_signal {
2977                incr found 1
2978                while {[gets $fd line] >= 0} {
2979                    array unset portinfo
2980                    set name [lindex $line 0]
2981                    set len  [lindex $line 1]
2982                    set line [read $fd $len]
2983
2984                    array set portinfo $line
2985
2986                    switch -- $protocol {
2987                        rsync {
2988                            set source_url file://[macports::getsourcepath $source]
2989                        }
2990                        https -
2991                        http -
2992                        ftp {
2993                            set source_url file://[macports::getsourcepath $source]
2994                        }
2995                        default {
2996                            set source_url $source
2997                        }
2998                    }
2999                    if {[info exists portinfo(portdir)]} {
3000                        lappend line porturl ${source_url}/$portinfo(portdir)
3001                    }
3002                    lappend matches $name $line
3003                }
3004            } catch * {
3005                ui_warn "It looks like your PortIndex file for $source may be corrupt."
3006                throw
3007            } finally {
3008                close $fd
3009            }
3010        } catch {*} {
3011            ui_warn "Can't open index file for source: $source"
3012        }
3013    }
3014    if {!$found} {
3015        return -code error "No index(es) found! Have you synced your port definitions? Try running 'port selfupdate'."
3016    }
3017
3018    return $matches
3019}
3020
3021##
3022# Loads PortIndex.quick from each source into the quick_index, generating it
3023# first if necessary. Private API of macports1.0, do not use this from outside
3024# macports1.0.
3025proc _mports_load_quickindex {} {
3026    global macports::sources macports::quick_index
3027
3028    unset -nocomplain macports::quick_index
3029
3030    set sourceno 0
3031    foreach source $sources {
3032        unset -nocomplain quicklist
3033        # chop off any tags
3034        set source [lindex $source 0]
3035        set index [macports::getindex $source]
3036        if {![file exists $index]} {
3037            incr sourceno
3038            continue
3039        }
3040        if {![file exists ${index}.quick]} {
3041            ui_warn "No quick index file found, attempting to generate one for source: $source"
3042            try -pass_signal {
3043                set quicklist [mports_generate_quickindex $index]
3044            } catch {*} {
3045                incr sourceno
3046                continue
3047            }
3048        }
3049        # only need to read the quick index file if we didn't just update it
3050        if {![info exists quicklist]} {
3051            try -pass_signal {
3052                set fd [open ${index}.quick r]
3053            } catch {*} {
3054                ui_warn "Can't open quick index file for source: $source"
3055                incr sourceno
3056                continue
3057            }
3058            set quicklist [read $fd]
3059            close $fd
3060        }
3061        foreach entry [split $quicklist \n] {
3062            set quick_index(${sourceno},[lindex $entry 0]) [lindex $entry 1]
3063        }
3064        incr sourceno 1
3065    }
3066    if {!$sourceno} {
3067        ui_warn "No index(es) found! Have you synced your port definitions? Try running 'port selfupdate'."
3068    }
3069}
3070
3071##
3072# Generates a PortIndex.quick file from a PortIndex by using the name field as
3073# key. This allows fast indexing into the PortIndex when using the port name as
3074# key.
3075#
3076# @param index the PortIndex file to create the index for. The resulting quick
3077#              index will be in a file named like \a index, but with ".quick"
3078#              appended.
3079# @return a list of entries written to the quick index file in the same format
3080#         if the file would just have been written.
3081# @throws if the given \a index cannot be opened, the output file cannot be
3082#         opened, an error occurs while using the PortIndex (e.g., because it
3083#         is corrupt), or the quick index generation failed for some other
3084#         reason.
3085proc mports_generate_quickindex {index} {
3086    try -pass_signal {
3087        set indexfd -1
3088        set quickfd -1
3089        set indexfd [open $index r]
3090        set quickfd [open ${index}.quick w]
3091    } catch {*} {
3092        ui_warn "Can't open index file: $index"
3093        return -code error
3094    }
3095    try -pass_signal {
3096        set offset [tell $indexfd]
3097        set quicklist {}
3098        while {[gets $indexfd line] >= 0} {
3099            if {[llength $line] != 2} {
3100                continue
3101            }
3102            set name [lindex $line 0]
3103            append quicklist "[string tolower $name] $offset\n"
3104
3105            set len [lindex $line 1]
3106            read $indexfd $len
3107            set offset [tell $indexfd]
3108        }
3109        puts -nonewline $quickfd $quicklist
3110    } catch {{*} eCode eMessage} {
3111        ui_warn "It looks like your PortIndex file $index may be corrupt."
3112        throw
3113    } finally {
3114        if {$indexfd != -1} {
3115            close $indexfd
3116        }
3117        if {$quickfd != -1} {
3118            close $quickfd
3119        }
3120    }
3121    if {[info exists quicklist]} {
3122        return $quicklist
3123    } else {
3124        ui_warn "Failed to generate quick index for: $index"
3125        return -code error
3126    }
3127}
3128
3129proc mportinfo {mport} {
3130    set workername [ditem_key $mport workername]
3131    return [$workername eval array get ::PortInfo]
3132}
3133
3134proc mportclose {mport} {
3135    global macports::open_mports macports::extracted_portdirs
3136    set refcnt [ditem_key $mport refcnt]
3137    incr refcnt -1
3138    ditem_key $mport refcnt $refcnt
3139    if {$refcnt == 0} {
3140        dlist_delete macports::open_mports $mport
3141        set workername [ditem_key $mport workername]
3142        # the hack in _mportexec might have already deleted the worker
3143        if {[interp exists $workername]} {
3144            interp delete $workername
3145        }
3146        set porturl [ditem_key $mport porturl]
3147        if {[info exists macports::extracted_portdirs($porturl)]} {
3148            # TODO port.tcl calls mportopen multiple times on the same port to
3149            # determine a number of attributes and will close the port after
3150            # each call. $macports::extracted_portdirs($porturl) will however
3151            # stay set, which means it will not be extracted twice. We could
3152            # (1) unset $macports::extracted_portdirs($porturl), which would
3153            # lead to downloading the port multiple times, or (2) fix the
3154            # port.tcl code to delay mportclose until the end.
3155            #ui_debug "Removing temporary port directory $macports::extracted_portdirs($porturl)"
3156            #file delete -force $macports::extracted_portdirs($porturl)
3157        }
3158        ditem_delete $mport
3159    }
3160}
3161
3162##### Private Depspec API #####
3163# This API should be considered work in progress and subject to change without notice.
3164##### "
3165
3166# _mportkey
3167# - returns a variable from the port's interpreter
3168
3169proc _mportkey {mport key} {
3170    set workername [ditem_key $mport workername]
3171    return [$workername eval [list set $key]]
3172}
3173
3174# mportdepends builds the list of mports which the given port depends on.
3175# This list is added to $mport.
3176# This list actually depends on the target.
3177# This method can optionally recurse through the dependencies, looking for
3178#   dependencies of dependencies.
3179# This method can optionally cut the search when ports are already installed or
3180#   the dependencies are satisfied.
3181#
3182# mport -> mport item
3183# target -> target to consider the dependency for
3184# recurseDeps -> if the search should be recursive
3185# skipSatisfied -> cut the search tree when encountering installed/satisfied
3186#                  dependencies ports.
3187# accDeps -> accumulator for recursive calls
3188# return 0 if everything was ok, an non zero integer otherwise.
3189proc mportdepends {mport {target {}} {recurseDeps 1} {skipSatisfied 1} {accDeps 0}} {
3190
3191    array set portinfo [mportinfo $mport]
3192    if {$accDeps} {
3193        upvar port_seen port_seen
3194    } else {
3195        array set port_seen {}
3196    }
3197
3198    # progress indicator
3199    if {![macports::ui_isset ports_debug]} {
3200        ui_info -nonewline .
3201        flush stdout
3202    }
3203
3204    if {$target in {{} install activate}} {
3205        if {[catch {_mporterrorifconflictsinstalled $mport}]} {
3206            return 1
3207        }
3208    }
3209
3210    set workername [ditem_key $mport workername]
3211    set deptypes [macports::_deptypes_for_target $target $workername]
3212
3213    set depPorts {}
3214    if {[llength $deptypes] > 0} {
3215        array set optionsarray [ditem_key $mport options]
3216        # avoid propagating requested flag from parent
3217        unset -nocomplain optionsarray(ports_requested)
3218        # subport will be different for deps
3219        unset -nocomplain optionsarray(subport)
3220        set options [array get optionsarray]
3221        set variations [ditem_key $mport variations]
3222        set required_archs [$workername eval get_canonical_archs]
3223        set depends_skip_archcheck [_mportkey $mport depends_skip_archcheck]
3224    }
3225
3226    # Process the dependencies for each of the deptypes
3227    foreach deptype $deptypes {
3228        if {![info exists portinfo($deptype)]} {
3229            continue
3230        }
3231        foreach depspec $portinfo($deptype) {
3232            # get the portname that satisfies the depspec
3233            set dep_portname [$workername eval _get_dep_port $depspec]
3234            # skip port/archs combos we've already seen, and ones with the same port but less archs than ones we've seen (or noarch)
3235            set seenkey ${dep_portname},[join $required_archs ,]
3236            set seen 0
3237            if {[info exists port_seen($seenkey)]} {
3238                set seen 1
3239            } else {
3240                set prev_seenkeys [array names port_seen ${dep_portname},*]
3241                set nrequired [llength $required_archs]
3242                foreach key $prev_seenkeys {
3243                    set key_archs [lrange [split $key ,] 1 end]
3244                    if {$key_archs eq "noarch" || $required_archs eq "noarch" || [llength $key_archs] > $nrequired} {
3245                        set seen 1
3246                        set seenkey $key
3247                        break
3248                    }
3249                }
3250            }
3251            if {$seen} {
3252                if {$port_seen($seenkey) != 0} {
3253                    # nonzero means the dep is not satisfied, so we have to record it
3254                    ditem_append_unique $mport requires $port_seen($seenkey)
3255                }
3256                continue
3257            }
3258
3259            # Is that dependency satisfied or this port installed?
3260            # If we don't skip or if it is not, add it to the list.
3261            set present [_mportispresent $mport $depspec]
3262
3263            if {!$skipSatisfied && $dep_portname eq ""} {
3264                set dep_portname [lindex [split $depspec :] end]
3265            }
3266
3267            set check_archs 0
3268            if {$dep_portname ne "" && $deptype ni {depends_fetch depends_extract}
3269                && $dep_portname ni $depends_skip_archcheck} {
3270                set check_archs 1
3271            }
3272
3273            # need to open the portfile even if the dep is installed if it doesn't have the right archs
3274            set parse 0
3275            if {!$skipSatisfied || !$present || ($check_archs && ![macports::_active_supports_archs $dep_portname $required_archs])} {
3276                set parse 1
3277            }
3278            if {$parse} {
3279                # Find the porturl
3280                try -pass_signal {
3281                    set res [mportlookup $dep_portname]
3282                } catch {{*} eCode eMessage} {
3283                    global errorInfo
3284                    ui_msg {}
3285                    ui_debug $errorInfo
3286                    ui_error "Internal error: port lookup failed: $eMessage"
3287                    return 1
3288                }
3289
3290                array unset dep_portinfo
3291                array set dep_portinfo [lindex $res 1]
3292                if {![info exists dep_portinfo(porturl)]} {
3293                    if {![macports::ui_isset ports_debug]} {
3294                        ui_msg {}
3295                    }
3296                    ui_error "Dependency '$dep_portname' not found."
3297                    return 1
3298                } elseif {[info exists dep_portinfo(installs_libs)] && !$dep_portinfo(installs_libs)} {
3299                    set check_archs 0
3300                    if {$skipSatisfied && $present} {
3301                        set parse 0
3302                    }
3303                }
3304
3305                if {$parse} {
3306                    set dep_options $options
3307                    lappend dep_options subport $dep_portinfo(name)
3308                    # Figure out the depport. Check the open_mports list first, since
3309                    # we potentially leak mport references if we mportopen each time,
3310                    # because mportexec only closes each open mport once.
3311                    set depport [dlist_match_multi $macports::open_mports [list porturl $dep_portinfo(porturl) options $dep_options]]
3312
3313                    if {$depport eq ""} {
3314                        # We haven't opened this one yet.
3315                        set depport [mportopen $dep_portinfo(porturl) $dep_options $variations]
3316                    }
3317                }
3318            }
3319
3320            # check archs
3321            if {$parse && $check_archs
3322                && ![macports::_mport_supports_archs $depport $required_archs]} {
3323
3324                set supported_archs [_mportkey $depport supported_archs]
3325                array unset variation_array
3326                array set variation_array [[ditem_key $depport workername] eval "array get variations"]
3327                mportclose $depport
3328                set arch_mismatch 1
3329                set has_universal 0
3330                if {[info exists dep_portinfo(variants)] && {universal} in $dep_portinfo(variants)} {
3331                    # a universal variant is offered
3332                    set has_universal 1
3333                    if {![info exists variation_array(universal)] || $variation_array(universal) ne "+"} {
3334                        set variation_array(universal) +
3335                        # try again with +universal
3336                        set depport [mportopen $dep_portinfo(porturl) $dep_options [array get variation_array]]
3337                        if {[macports::_mport_supports_archs $depport $required_archs]} {
3338                            set arch_mismatch 0
3339                        }
3340                    }
3341                }
3342                if {$arch_mismatch} {
3343                    macports::_explain_arch_mismatch [_mportkey $mport subport] $dep_portname $required_archs $supported_archs $has_universal
3344                    return 1
3345                }
3346            }
3347
3348            if {$parse} {
3349                if {$recurseDeps} {
3350                    # Add to the list we need to recurse on.
3351                    lappend depPorts $depport
3352                }
3353
3354                # Append the sub-port's provides to the port's requirements list.
3355                set depport_provides [ditem_key $depport provides]
3356                ditem_append_unique $mport requires $depport_provides
3357                # record actual archs we ended up getting
3358                set port_seen(${dep_portname},[join [macports::_mport_archs $depport] ,]) $depport_provides
3359            } elseif {$present && $dep_portname ne ""} {
3360                # record actual installed archs
3361                set port_seen(${dep_portname},[join [macports::_active_archs $dep_portname] ,]) 0
3362            }
3363        }
3364    }
3365
3366    # Loop on the depports.
3367    if {$recurseDeps} {
3368        # Dep ports should be installed (all dependencies must be satisfied).
3369        foreach depport $depPorts {
3370            # Any of these may have been closed by a previous recursive call
3371            # and replaced by a universal version. This is fine, just skip.
3372            if {[ditem_key $depport] ne ""} {
3373                set res [mportdepends $depport {} $recurseDeps $skipSatisfied 1]
3374                if {$res != 0} {
3375                    return $res
3376                }
3377            }
3378        }
3379    }
3380
3381    return 0
3382}
3383
3384# check if the given mport can support dependents with the given archs
3385proc macports::_mport_supports_archs {mport required_archs} {
3386    if {$required_archs eq "noarch"} {
3387        return 1
3388    }
3389    set provided_archs [_mport_archs $mport]
3390    if {$provided_archs eq "noarch"} {
3391        return 1
3392    }
3393    foreach arch $required_archs {
3394        if {$arch ni $provided_archs} {
3395            return 0
3396        }
3397    }
3398    return 1
3399}
3400
3401# return the archs of the given mport
3402proc macports::_mport_archs {mport} {
3403    set workername [ditem_key $mport workername]
3404    return [$workername eval get_canonical_archs]
3405}
3406
3407# check if the active version of a port supports the given archs
3408proc macports::_active_supports_archs {portname required_archs} {
3409    if {$required_archs eq "noarch"} {
3410        return 1
3411    }
3412    if {[catch {registry::active $portname}]} {
3413        return 0
3414    }
3415    set provided_archs [_active_archs $portname]
3416    if {$provided_archs eq "noarch" || $provided_archs eq "" || $provided_archs == 0} {
3417        return 1
3418    }
3419    foreach arch $required_archs {
3420        if {$arch ni $provided_archs} {
3421            return 0
3422        }
3423    }
3424    return 1
3425}
3426
3427# get the archs for a given active port
3428proc macports::_active_archs {portname} {
3429    if {[catch {set ilist [registry::active $portname]}]} {
3430        return {}
3431    }
3432    set i [lindex $ilist 0]
3433    set regref [registry::open_entry $portname [lindex $i 1] [lindex $i 2] [lindex $i 3] [lindex $i 5]]
3434    return [registry::property_retrieve $regref archs]
3435}
3436
3437# print an error message explaining why a port's archs are not provided by a dependency
3438proc macports::_explain_arch_mismatch {port dep required_archs supported_archs has_universal} {
3439    global macports::universal_archs
3440    if {![macports::ui_isset ports_debug]} {
3441        ui_msg {}
3442    }
3443
3444    set s [expr {[llength $required_archs] == 1 ? "" : "s"}]
3445
3446    ui_error "Cannot install $port for the arch${s} '$required_archs' because"
3447    if {$supported_archs ne ""} {
3448        set ss [expr {[llength $supported_archs] == 1 ? "" : "s"}]
3449        foreach arch $required_archs {
3450            if {$arch ni $supported_archs} {
3451                ui_error "its dependency $dep only supports the arch${ss} '$supported_archs'."
3452                return
3453            }
3454        }
3455    }
3456    if {$has_universal} {
3457        foreach arch $required_archs {
3458            if {$arch ni $universal_archs} {
3459                ui_error "its dependency $dep does not build for the required arch${s} by default"
3460                ui_error "and the configured universal_archs '$universal_archs' are not sufficient."
3461                return
3462            }
3463        }
3464        ui_error "its dependency $dep cannot build for the required arch${s}."
3465        return
3466    }
3467    ui_error "its dependency $dep does not build for the required arch${s} by default"
3468    ui_error "and does not have a universal variant."
3469}
3470
3471# check if the given mport has any dependencies of the given types
3472proc macports::_mport_has_deptypes {mport deptypes} {
3473    array set portinfo [mportinfo $mport]
3474    foreach type $deptypes {
3475        if {[info exists portinfo($type)] && $portinfo($type) ne ""} {
3476            return 1
3477        }
3478    }
3479    return 0
3480}
3481
3482# check if the given target needs dependencies installed first
3483proc macports::_target_needs_deps {target} {
3484    # XXX: need a better way than checking this hardcoded list
3485    switch -- $target {
3486        fetch -
3487        checksum -
3488        extract -
3489        patch -
3490        configure -
3491        build -
3492        test -
3493        destroot -
3494        install -
3495        activate -
3496        dmg -
3497        mdmg -
3498        pkg -
3499        mpkg {return 1}
3500        default {return 0}
3501    }
3502}
3503
3504# Determine dependency types required for target
3505proc macports::_deptypes_for_target {target workername} {
3506    switch -- $target {
3507        fetch       -
3508        checksum    {return depends_fetch}
3509        extract     -
3510        patch       {return "depends_fetch depends_extract"}
3511        configure   -
3512        build       {return "depends_fetch depends_extract depends_build depends_lib"}
3513        test        {return "depends_fetch depends_extract depends_build depends_lib depends_run depends_test"}
3514        destroot    {return "depends_fetch depends_extract depends_build depends_lib depends_run"}
3515        dmg         -
3516        pkg         -
3517        mdmg        -
3518        mpkg        {
3519            if {[global_option_isset ports_binary_only] ||
3520                (![global_option_isset ports_source_only] && [$workername eval _archive_available])} {
3521                return "depends_lib depends_run"
3522            } else {
3523                return "depends_fetch depends_extract depends_build depends_lib depends_run"
3524            }
3525        }
3526        install     -
3527        activate    -
3528        {}          {
3529            if {[global_option_isset ports_binary_only] ||
3530                [$workername eval registry_exists \$subport \$version \$revision \$portvariants]
3531                || (![global_option_isset ports_source_only] && [$workername eval _archive_available])} {
3532                return "depends_lib depends_run"
3533            } else {
3534                return "depends_fetch depends_extract depends_build depends_lib depends_run"
3535            }
3536        }
3537    }
3538    return {}
3539}
3540
3541# selfupdate procedure
3542proc macports::selfupdate {{optionslist {}} {updatestatusvar {}}} {
3543    global macports::prefix macports::portdbpath macports::rsync_server macports::rsync_dir \
3544           macports::rsync_options macports::autoconf::macports_version \
3545           macports::autoconf::rsync_path tcl_platform macports::autoconf::openssl_path \
3546           macports::autoconf::tar_path
3547    array set options $optionslist
3548
3549    # variable that indicates whether we actually updated base
3550    if {$updatestatusvar ne ""} {
3551        upvar $updatestatusvar updatestatus
3552        set updatestatus no
3553    }
3554
3555    # are we syncing a tarball? (implies detached signature)
3556    set is_tarball 0
3557    if {[string range $rsync_dir end-3 end] eq ".tar"} {
3558        set is_tarball 1
3559        set mp_source_path [file join $portdbpath sources $rsync_server [file dirname $rsync_dir]]
3560    } else {
3561        if {[string index $rsync_dir end] ne "/"} {
3562            append rsync_dir /
3563        }
3564        set mp_source_path [file join $portdbpath sources $rsync_server $rsync_dir]
3565    }
3566    # create the path to the to be downloaded sources if it doesn't exist
3567    if {![file exists $mp_source_path]} {
3568        file mkdir $mp_source_path
3569    }
3570    ui_debug "MacPorts sources location: $mp_source_path"
3571
3572    # sync the MacPorts sources
3573    ui_msg "$macports::ui_prefix Updating MacPorts base sources using rsync"
3574    try -pass_signal {
3575        system "$rsync_path $rsync_options rsync://${rsync_server}/$rsync_dir $mp_source_path"
3576    } catch {{*} eCode eMessage} {
3577        return -code error "Error synchronizing MacPorts sources: $eMessage"
3578    }
3579
3580    if {$is_tarball} {
3581        # verify signature for tarball
3582        global macports::archivefetch_pubkeys
3583        try -pass_signal {
3584            system "$rsync_path $rsync_options rsync://${rsync_server}/${rsync_dir}.rmd160 $mp_source_path"
3585        } catch {{*} eCode eMessage} {
3586            return -code error "Error synchronizing MacPorts source signature: $eMessage"
3587        }
3588        set openssl [findBinary openssl $macports::autoconf::openssl_path]
3589        set tarball ${mp_source_path}/[file tail $rsync_dir]
3590        set signature ${tarball}.rmd160
3591        set verified 0
3592        foreach pubkey $macports::archivefetch_pubkeys {
3593            try -pass_signal {
3594                exec $openssl dgst -ripemd160 -verify $pubkey -signature $signature $tarball
3595                set verified 1
3596                ui_debug "successful verification with key $pubkey"
3597                break
3598            }  catch {{*} eCode eMessage} {
3599                ui_debug "failed verification with key $pubkey"
3600                ui_debug "openssl output: $eMessage"
3601            }
3602        }
3603        if {!$verified} {
3604            return -code error "Failed to verify signature for MacPorts source!"
3605        }
3606
3607        # extract tarball and move into place
3608        set tar [macports::findBinary tar $macports::autoconf::tar_path]
3609        file mkdir ${mp_source_path}/tmp
3610        set tar_cmd "$tar -C ${mp_source_path}/tmp -xf $tarball"
3611        try -pass_signal {
3612            system $tar_cmd
3613        } catch {*} {
3614            return -code error "Failed to extract MacPorts sources from tarball!"
3615        }
3616        file delete -force ${mp_source_path}/base
3617        file rename ${mp_source_path}/tmp/base ${mp_source_path}/base
3618        file delete -force ${mp_source_path}/tmp
3619        # set the final extracted source path
3620        set mp_source_path ${mp_source_path}/base
3621    }
3622
3623    # echo current MacPorts version
3624    ui_msg "MacPorts base version $macports::autoconf::macports_version installed,"
3625
3626    if {[info exists options(ports_force)] && $options(ports_force)} {
3627        set use_the_force_luke yes
3628        ui_debug "Forcing a rebuild and reinstallation of MacPorts"
3629    } else {
3630        set use_the_force_luke no
3631        ui_debug "Rebuilding and reinstalling MacPorts if needed"
3632    }
3633
3634    # Choose what version file to use: old, floating point format or new, real version number format
3635    set version_file [file join $mp_source_path config macports_version]
3636    if {[file exists $version_file]} {
3637        set fd [open $version_file r]
3638        gets $fd macports_version_new
3639        close $fd
3640        # echo downloaded MacPorts version
3641        ui_msg "MacPorts base version $macports_version_new downloaded."
3642    } else {
3643        ui_warn "No version file found, please rerun selfupdate."
3644        set macports_version_new 0
3645    }
3646
3647    # check if we we need to rebuild base
3648    set comp [vercmp $macports_version_new $macports::autoconf::macports_version]
3649
3650    # syncing ports tree.
3651    if {![info exists options(ports_selfupdate_nosync)] || !$options(ports_selfupdate_nosync)} {
3652        if {$comp > 0} {
3653            # updated portfiles potentially need new base to parse - tell sync to try to
3654            # use prefabricated PortIndex files and signal if it couldn't
3655            lappend optionslist no_reindex 1 needed_portindex_var needed_portindex
3656        }
3657        try {
3658            mportsync $optionslist
3659        }  catch {{*} eCode eMessage} {
3660            return -code error "Couldn't sync the ports tree: $eMessage"
3661        }
3662    }
3663
3664    if {$use_the_force_luke || $comp > 0} {
3665        if {[info exists options(ports_dryrun)] && $options(ports_dryrun)} {
3666            ui_msg "$macports::ui_prefix MacPorts base is outdated, selfupdate would install $macports_version_new (dry run)"
3667        } else {
3668            ui_msg "$macports::ui_prefix MacPorts base is outdated, installing new version $macports_version_new"
3669
3670            # get installation user/group and permissions
3671            set owner [file attributes $prefix -owner]
3672            set group [file attributes $prefix -group]
3673            set perms [string range [file attributes $prefix -permissions] end-3 end]
3674            if {$tcl_platform(user) ne "root" && $tcl_platform(user) ne $owner} {
3675                return -code error "User $tcl_platform(user) does not own $prefix - try using sudo"
3676            }
3677            ui_debug "Permissions OK"
3678
3679            set configure_args "--prefix=[macports::shellescape $prefix] --with-install-user=[macports::shellescape $owner] --with-install-group=[macports::shellescape $group] --with-directory-mode=[macports::shellescape $perms]"
3680            # too many users have an incompatible readline in /usr/local, see ticket #10651
3681            if {$tcl_platform(os) ne "Darwin" || $prefix eq "/usr/local"
3682                || ([glob -nocomplain /usr/local/lib/lib{readline,history}*] eq "" && [glob -nocomplain /usr/local/include/readline/*.h] eq "")} {
3683                append configure_args " --enable-readline"
3684            } else {
3685                ui_warn "Disabling readline support due to readline in /usr/local"
3686            }
3687
3688            if {$prefix eq "/usr/local" || $prefix eq "/usr"} {
3689                append configure_args " --with-unsupported-prefix"
3690            }
3691
3692            # Choose a sane compiler
3693            set cc_arg {}
3694            if {$::macports::os_platform eq "darwin"} {
3695                set cc_arg "CC=/usr/bin/cc OBJC=/usr/bin/cc "
3696            }
3697
3698            # do the actual configure, build and installation of new base
3699            ui_msg "Installing new MacPorts release in $prefix as ${owner}:${group}; permissions ${perms}\n"
3700            try {
3701                system -W $mp_source_path "${cc_arg}./configure $configure_args && make SELFUPDATING=1 && make install SELFUPDATING=1"
3702            } catch {{*} eCode eMessage} {
3703                return -code error "Error installing new MacPorts base: $eMessage"
3704            }
3705            if {[info exists updatestatus]} {
3706                set updatestatus yes
3707            }
3708        }
3709    } elseif {$comp < 0} {
3710        ui_msg "$macports::ui_prefix MacPorts base is probably trunk or a release candidate"
3711    } else {
3712        ui_msg "$macports::ui_prefix MacPorts base is already the latest version"
3713    }
3714
3715    # set the MacPorts sources to the right owner
3716    set sources_owner [file attributes [file join $portdbpath sources/] -owner]
3717    ui_debug "Setting MacPorts sources ownership to $sources_owner"
3718    try {
3719        exec [findBinary chown $macports::autoconf::chown_path] -R $sources_owner [file join $portdbpath sources/]
3720    }  catch {{*} eCode eMessage} {
3721        return -code error "Couldn't change permissions of the MacPorts sources at $mp_source_path to ${sources_owner}: $eMessage"
3722    }
3723
3724    if {![info exists options(ports_selfupdate_nosync)] || !$options(ports_selfupdate_nosync)} {
3725        if {[info exists needed_portindex]} {
3726            ui_msg "Not all sources could be fully synced using the old version of MacPorts."
3727            ui_msg "Please run selfupdate again now that MacPorts base has been updated."
3728        } else {
3729            ui_msg "\nThe ports tree has been updated. To upgrade your installed ports, you should run"
3730            ui_msg "  port upgrade outdated"
3731        }
3732    }
3733
3734    return 0
3735}
3736
3737# upgrade API wrapper procedure
3738# return codes:
3739#   0 = success
3740#   1 = general failure
3741#   2 = port name not found in index
3742#   3 = port not installed
3743proc macports::upgrade {portname dspec variationslist optionslist {depscachename {}}} {
3744    # only installed ports can be upgraded
3745    if {![registry::entry_exists_for_name $portname]} {
3746        ui_error "$portname is not installed"
3747        return 3
3748    }
3749    if {$depscachename ne ""} {
3750        upvar $depscachename depscache
3751    } else {
3752        array set depscache {}
3753    }
3754    # stop upgrade from being called via mportexec as well
3755    set orig_nodeps yes
3756    if {![info exists macports::global_options(ports_nodeps)]} {
3757        set macports::global_options(ports_nodeps) yes
3758        set orig_nodeps no
3759    }
3760
3761    # run the actual upgrade
3762    set status [macports::_upgrade $portname $dspec $variationslist $optionslist depscache]
3763
3764    if {!$orig_nodeps} {
3765        unset -nocomplain macports::global_options(ports_nodeps)
3766    }
3767
3768    return $status
3769}
3770
3771# main internal upgrade procedure
3772proc macports::_upgrade {portname dspec variationslist optionslist {depscachename {}}} {
3773    global macports::global_variations
3774    array set options $optionslist
3775
3776    if {$depscachename ne ""} {
3777        upvar $depscachename depscache
3778    }
3779
3780    # Is this a dry run?
3781    set is_dryrun no
3782    if {[info exists options(ports_dryrun)] && $options(ports_dryrun)} {
3783        set is_dryrun yes
3784    }
3785
3786    # Is this a rev-upgrade-called run?
3787    set is_revupgrade no
3788    if {[info exists options(ports_revupgrade)] && $options(ports_revupgrade)} {
3789        set is_revupgrade yes
3790        # unset revupgrade options so we can upgrade dependencies with the same
3791        # $options without also triggering a rebuild there, see #40150
3792        unset options(ports_revupgrade)
3793    }
3794    set is_revupgrade_second_run no
3795    if {[info exists options(ports_revupgrade_second_run)] && $options(ports_revupgrade_second_run)} {
3796        set is_revupgrade_second_run yes
3797        # unset revupgrade options so we can upgrade dependencies with the same
3798        # $options without also triggering a rebuild there, see #40150
3799        unset options(ports_revupgrade_second_run)
3800    }
3801
3802    # check if the port is in tree
3803    try {
3804        mportlookup $portname
3805    } catch {{*} eCode eMessage} {
3806        global errorInfo
3807        ui_debug $errorInfo
3808        ui_error "port lookup failed: $eMessage"
3809        return 1
3810    }
3811    # argh! port doesnt exist!
3812    if {$result eq ""} {
3813        ui_warn "No port $portname found in the index."
3814        return 2
3815    }
3816    # fill array with information
3817    array set portinfo [lindex $result 1]
3818    # set portname again since the one we were passed may not have had the correct case
3819    set portname $portinfo(name)
3820    set options(subport) $portname
3821
3822    set ilist {}
3823    if {[catch {set ilist [registry::installed $portname {}]} result]} {
3824        if {$result eq "Registry error: $portname not registered as installed."} {
3825            ui_debug "$portname is *not* installed by MacPorts"
3826
3827            # We need to pass _mportispresent a reference to the mport that is
3828            # actually declaring the dependency on the one we're checking for.
3829            # We got here via _upgrade_dependencies, so we grab it from 2 levels up.
3830            upvar 2 mport parentmport
3831            if {![_mportispresent $parentmport $dspec]} {
3832                # open porthandle
3833                set porturl $portinfo(porturl)
3834                if {![info exists porturl]} {
3835                    set porturl file://./
3836                }
3837                # Grab the variations from the parent
3838                upvar 2 variations variations
3839
3840                if {[catch {set mport [mportopen $porturl [array get options] [array get variations]]} result]} {
3841                    global errorInfo
3842                    ui_debug $errorInfo
3843                    ui_error "Unable to open port: $result"
3844                    return 1
3845                }
3846                # While we're at it, update the portinfo
3847                array unset portinfo
3848                array set portinfo [mportinfo $mport]
3849
3850                # upgrade its dependencies first
3851                set status [_upgrade_dependencies portinfo depscache variationslist options]
3852                if {$status != 0 && $status != 2 && ![ui_isset ports_processall]} {
3853                    catch {mportclose $mport}
3854                    return $status
3855                }
3856                # now install it
3857                if {[catch {set result [mportexec $mport activate]} result]} {
3858                    global errorInfo
3859                    ui_debug $errorInfo
3860                    ui_error "Unable to exec port: $result"
3861                    catch {mportclose $mport}
3862                    return 1
3863                }
3864                if {$result > 0} {
3865                    ui_error "Problem while installing $portname"
3866                    catch {mportclose $mport}
3867                    return $result
3868                }
3869                # we just installed it, so mark it done in the cache
3870                set depscache(port:$portname) 1
3871                mportclose $mport
3872            } else {
3873                # dependency is satisfied by something other than the named port
3874                ui_debug "$portname not installed, soft dependency satisfied"
3875                # mark this depspec as satisfied in the cache
3876                set depscache($dspec) 1
3877            }
3878            # the rest of the proc doesn't matter for a port that is freshly
3879            # installed or not installed
3880            return 0
3881        } else {
3882            ui_error "Checking installed version failed: $result"
3883            return 1
3884        }
3885    } else {
3886        # we'll now take care of upgrading it, so we can add it to the cache
3887        set depscache(port:$portname) 1
3888    }
3889
3890    # set version_in_tree and revision_in_tree
3891    if {![info exists portinfo(version)]} {
3892        ui_error "Invalid port entry for ${portname}, missing version"
3893        return 1
3894    }
3895    set version_in_tree $portinfo(version)
3896    set revision_in_tree $portinfo(revision)
3897    set epoch_in_tree $portinfo(epoch)
3898
3899    # find latest version installed and active version (if any)
3900    set anyactive no
3901    set version_installed {}
3902    foreach i $ilist {
3903        set variant [lindex $i 3]
3904        set version [lindex $i 1]
3905        set revision [lindex $i 2]
3906        set epoch [lindex $i 5]
3907        if {$version_installed eq "" || ($epoch > $epoch_installed && $version ne $version_installed) ||
3908                ($epoch >= $epoch_installed && [vercmp $version $version_installed] > 0)
3909                || ($epoch >= $epoch_installed
3910                    && [vercmp $version $version_installed] == 0
3911                    && $revision > $revision_installed)} {
3912            set version_installed $version
3913            set revision_installed $revision
3914            set variant_installed $variant
3915            set epoch_installed $epoch
3916        }
3917
3918        set isactive [lindex $i 4]
3919        if {$isactive == 1} {
3920            set anyactive yes
3921            set version_active $version
3922            set revision_active $revision
3923            set variant_active $variant
3924            set epoch_active $epoch
3925        }
3926    }
3927
3928    # output version numbers
3929    ui_debug "epoch: in tree: $epoch_in_tree installed: $epoch_installed"
3930    ui_debug "$portname ${version_in_tree}_$revision_in_tree exists in the ports tree"
3931    ui_debug "$portname ${version_installed}_$revision_installed $variant_installed is the latest installed"
3932    if {$anyactive} {
3933        ui_debug "$portname ${version_active}_$revision_active $variant_active is active"
3934        # save existing variant for later use
3935        set oldvariant $variant_active
3936        set regref [registry::open_entry $portname $version_active $revision_active $variant_active $epoch_active]
3937    } else {
3938        ui_debug "no version of $portname is active"
3939        set oldvariant $variant_installed
3940        set regref [registry::open_entry $portname $version_installed $revision_installed $variant_installed $epoch_installed]
3941    }
3942    set oldnegatedvariant [registry::property_retrieve $regref negated_variants]
3943    if {$oldnegatedvariant == 0} {
3944        set oldnegatedvariant {}
3945    }
3946    set requestedflag [registry::property_retrieve $regref requested]
3947    set os_platform_installed [registry::property_retrieve $regref os_platform]
3948    set os_major_installed [registry::property_retrieve $regref os_major]
3949
3950    # Before we do
3951    # dependencies, we need to figure out the final variants,
3952    # open the port, and update the portinfo.
3953    set porturl $portinfo(porturl)
3954    if {![info exists porturl]} {
3955        set porturl file://./
3956    }
3957
3958    # Note $variationslist is left alone and so retains the original
3959    # requested variations, which should be passed to recursive calls to
3960    # upgrade; while variations gets existing variants and global variations
3961    # merged in later on, so it applies only to this port's upgrade
3962    array set variations $variationslist
3963
3964    set globalvarlist [array get macports::global_variations]
3965
3966    set minusvariant [lrange [split $oldnegatedvariant -] 1 end]
3967    set plusvariant [lrange [split $oldvariant +] 1 end]
3968    ui_debug "Merging existing variants '${oldvariant}$oldnegatedvariant' into variants"
3969    set oldvariantlist [list]
3970    foreach v $plusvariant {
3971        lappend oldvariantlist $v +
3972    }
3973    foreach v $minusvariant {
3974        lappend oldvariantlist $v -
3975    }
3976
3977    # merge in the old variants
3978    foreach {variation value} $oldvariantlist {
3979        if {![info exists variations($variation)]} {
3980            set variations($variation) $value
3981        }
3982    }
3983
3984    # Now merge in the global (i.e. variants.conf) variations.
3985    # We wait until now so that existing variants for this port
3986    # override global variations
3987    foreach {variation value} $globalvarlist {
3988        if {![info exists variations($variation)]} {
3989            set variations($variation) $value
3990        }
3991    }
3992
3993    ui_debug "new fully merged portvariants: [array get variations]"
3994
3995    # at this point we need to check if a different port will be replacing this one
3996    if {[info exists portinfo(replaced_by)] && ![info exists options(ports_upgrade_no-replace)]} {
3997        ui_msg "$macports::ui_prefix $portname is replaced by $portinfo(replaced_by)"
3998        if {[catch {mportlookup $portinfo(replaced_by)} result]} {
3999            global errorInfo
4000            ui_debug $errorInfo
4001            ui_error "port lookup failed: $result"
4002            return 1
4003        }
4004        if {$result eq ""} {
4005            ui_error "No port $portinfo(replaced_by) found."
4006            return 1
4007        }
4008        array unset portinfo
4009        array set portinfo [lindex $result 1]
4010        set newname $portinfo(name)
4011
4012        set porturl $portinfo(porturl)
4013        if {![info exists porturl]} {
4014            set porturl file://./
4015        }
4016        set depscache(port:$newname) 1
4017    } else {
4018        set newname $portname
4019    }
4020
4021    array set interp_options [array get options]
4022    set interp_options(ports_requested) $requestedflag
4023    set interp_options(subport) $newname
4024    # Mark this port to be rebuilt from source if this isn't the first time it
4025    # was flagged as broken by rev-upgrade
4026    if {$is_revupgrade_second_run} {
4027        set interp_options(ports_source_only) yes
4028    }
4029
4030    if {[catch {set mport [mportopen $porturl [array get interp_options] [array get variations]]} result]} {
4031        global errorInfo
4032        ui_debug $errorInfo
4033        ui_error "Unable to open port: $result"
4034        return 1
4035    }
4036    array unset interp_options
4037
4038    array unset portinfo
4039    array set portinfo [mportinfo $mport]
4040    set version_in_tree $portinfo(version)
4041    set revision_in_tree $portinfo(revision)
4042    set epoch_in_tree $portinfo(epoch)
4043
4044    set build_override 0
4045    set will_install yes
4046    # check installed version against version in ports
4047    if {([vercmp $version_installed $version_in_tree] > 0
4048            || ([vercmp $version_installed $version_in_tree] == 0
4049                && [vercmp $revision_installed $revision_in_tree] >= 0))
4050        && ![info exists options(ports_upgrade_force)]} {
4051        if {$portname ne $newname} {
4052            ui_debug "ignoring versions, installing replacement port"
4053        } elseif {$epoch_installed < $epoch_in_tree && $version_installed ne $version_in_tree} {
4054            set build_override 1
4055            ui_debug "epoch override ... upgrading!"
4056        } elseif {[info exists options(ports_upgrade_enforce-variants)] && $options(ports_upgrade_enforce-variants)
4057                  && [info exists portinfo(canonical_active_variants)] && $portinfo(canonical_active_variants) ne $oldvariant} {
4058            ui_debug "variant override ... upgrading!"
4059        } elseif {$os_platform_installed ne "" && $os_major_installed ne "" && $os_platform_installed != 0
4060                  && ([_mportkey $mport os.platform] ne $os_platform_installed
4061                  || [_mportkey $mport os.major] != $os_major_installed)} {
4062            ui_debug "platform mismatch ... upgrading!"
4063            set build_override 1
4064        } elseif {$is_revupgrade_second_run} {
4065            ui_debug "rev-upgrade override ... upgrading (from source)!"
4066            set build_override 1
4067        } elseif {$is_revupgrade} {
4068            ui_debug "rev-upgrade override ... upgrading!"
4069            # in the first run of rev-upgrade, only activate possibly already existing files and check for missing dependencies
4070            # do nothing, just prevent will_install being set to no below
4071        } else {
4072            if {[info exists portinfo(canonical_active_variants)] && $portinfo(canonical_active_variants) ne $oldvariant} {
4073                if {[llength $variationslist] > 0} {
4074                    ui_warn "Skipping upgrade since $portname ${version_installed}_$revision_installed >= $portname ${version_in_tree}_${revision_in_tree}, even though installed variants \"$oldvariant\" do not match \"$portinfo(canonical_active_variants)\". Use 'upgrade --enforce-variants' to switch to the requested variants."
4075                } else {
4076                    ui_debug "Skipping upgrade since $portname ${version_installed}_$revision_installed >= $portname ${version_in_tree}_${revision_in_tree}, even though installed variants \"$oldvariant\" do not match \"$portinfo(canonical_active_variants)\"."
4077                }
4078            } else {
4079                ui_debug "No need to upgrade! $portname ${version_installed}_$revision_installed >= $portname ${version_in_tree}_$revision_in_tree"
4080            }
4081            set will_install no
4082        }
4083    }
4084
4085    set will_build no
4086    set already_installed [registry::entry_exists $newname $version_in_tree $revision_in_tree $portinfo(canonical_active_variants)]
4087    # avoid building again unnecessarily
4088    if {$will_install &&
4089        ([info exists options(ports_upgrade_force)]
4090            || $build_override == 1
4091            || !$already_installed)} {
4092        set will_build yes
4093    }
4094
4095    # first upgrade dependencies
4096    if {![info exists options(ports_nodeps)]} {
4097        # the last arg is because we might have to build from source if a rebuild is being forced
4098        set status [_upgrade_dependencies portinfo depscache variationslist options [expr {$will_build && $already_installed}]]
4099        if {$status != 0 && $status != 2 && ![ui_isset ports_processall]} {
4100            catch {mportclose $mport}
4101            return $status
4102        }
4103    } else {
4104        ui_debug "Not following dependencies"
4105    }
4106
4107    if {!$will_install} {
4108        # nothing to do for this port, so just check if we have to do dependents
4109        if {[info exists options(ports_do_dependents)]} {
4110            # We do dependents ..
4111            set options(ports_nodeps) 1
4112
4113            registry::open_dep_map
4114            if {$anyactive} {
4115                set deplist [registry::list_dependents $portname $version_active $revision_active $variant_active]
4116            } else {
4117                set deplist [registry::list_dependents $portname $version_installed $revision_installed $variant_installed]
4118            }
4119
4120            if {[llength deplist] > 0} {
4121                foreach dep $deplist {
4122                    set mpname [lindex $dep 2]
4123                    if {![llength [array get depscache port:$mpname]]} {
4124                        set status [macports::_upgrade $mpname port:$mpname $variationslist [array get options] depscache]
4125                        if {$status != 0 && $status != 2 && ![ui_isset ports_processall]} {
4126                            catch {mportclose $mport}
4127                            return $status
4128                        }
4129                    }
4130                }
4131            }
4132        }
4133        mportclose $mport
4134        return 0
4135    }
4136
4137    if {$will_build} {
4138        if {$already_installed
4139            && ([info exists options(ports_upgrade_force)] || $build_override == 1)} {
4140            # Tell archivefetch/unarchive not to use the installed archive, i.e. a
4141            # fresh one will be either fetched or built locally.
4142            # Ideally this would be done in the interp_options when we mportopen,
4143            # but we don't know if we want to do this at that point.
4144            set workername [ditem_key $mport workername]
4145            $workername eval "set force_archive_refresh yes"
4146
4147            # run archivefetch and destroot for version_in_tree
4148            # doing this instead of just running install ensures that we have the
4149            # new copy ready but not yet installed, so we can safely uninstall the
4150            # existing one.
4151            if {[catch {set result [mportexec $mport archivefetch]} result] || $result != 0} {
4152                if {[info exists ::errorInfo]} {
4153                    ui_debug $::errorInfo
4154                }
4155                catch {mportclose $mport}
4156                return 1
4157            }
4158            # the following is a noop if archivefetch found an archive
4159            if {[catch {set result [mportexec $mport destroot]} result] || $result != 0} {
4160                if {[info exists ::errorInfo]} {
4161                    ui_debug $::errorInfo
4162                }
4163                catch {mportclose $mport}
4164                return 1
4165            }
4166        } else {
4167            # Normal non-forced case
4168            # install version_in_tree (but don't activate yet)
4169            if {[catch {set result [mportexec $mport install]} result] || $result != 0} {
4170                if {[info exists ::errorInfo]} {
4171                    ui_debug $::errorInfo
4172                }
4173                catch {mportclose $mport}
4174                return 1
4175            }
4176        }
4177    }
4178
4179    # are we installing an existing version due to force or epoch override?
4180    if {$already_installed
4181        && ([info exists options(ports_upgrade_force)] || $build_override == 1)} {
4182         ui_debug "Uninstalling $newname ${version_in_tree}_${revision_in_tree}$portinfo(canonical_active_variants)"
4183        # we have to force the uninstall in case of dependents
4184        set force_cur [info exists options(ports_force)]
4185        set options(ports_force) yes
4186        set existing_epoch [lindex [registry::installed $newname ${version_in_tree}_${revision_in_tree}$portinfo(canonical_active_variants)] 0 5]
4187        set newregref [registry::open_entry $newname $version_in_tree $revision_in_tree $portinfo(canonical_active_variants) $existing_epoch]
4188        if {$is_dryrun} {
4189            ui_msg "Skipping uninstall $newname @${version_in_tree}_${revision_in_tree}$portinfo(canonical_active_variants) (dry run)"
4190        } elseif {![registry::run_target $newregref uninstall [array get options]]
4191                  && [catch {registry_uninstall::uninstall $newname $version_in_tree $revision_in_tree $portinfo(canonical_active_variants) [array get options]} result]} {
4192            global errorInfo
4193            ui_debug $errorInfo
4194            ui_error "Uninstall $newname ${version_in_tree}_${revision_in_tree}$portinfo(canonical_active_variants) failed: $result"
4195            catch {mportclose $mport}
4196            return 1
4197        }
4198        if {!$force_cur} {
4199            unset options(ports_force)
4200        }
4201        if {$anyactive && $version_in_tree eq $version_active && $revision_in_tree == $revision_active
4202            && $portinfo(canonical_active_variants) eq $variant_active && $portname eq $newname} {
4203            set anyactive no
4204        }
4205    }
4206    if {$anyactive && $portname ne $newname} {
4207        # replaced_by in effect, deactivate the old port
4208        # we have to force the deactivate in case of dependents
4209        set force_cur [info exists options(ports_force)]
4210        set options(ports_force) yes
4211        if {$is_dryrun} {
4212            ui_msg "Skipping deactivate $portname @${version_active}_${revision_active}$variant_active (dry run)"
4213        } elseif {![catch {registry::active $portname}] &&
4214                  ![registry::run_target $regref deactivate [array get options]]
4215                  && [catch {portimage::deactivate $portname $version_active $revision_active $variant_active [array get options]} result]} {
4216            global errorInfo
4217            ui_debug $errorInfo
4218            ui_error "Deactivating $portname @${version_active}_${revision_active}$variant_active failed: $result"
4219            catch {mportclose $mport}
4220            return 1
4221        }
4222        if {!$force_cur} {
4223            unset options(ports_force)
4224        }
4225        set anyactive no
4226    }
4227    if {[info exists options(port_uninstall_old)] && $portname eq $newname} {
4228        # uninstalling now could fail due to dependents when not forced,
4229        # because the new version is not installed
4230        set uninstall_later yes
4231    }
4232
4233    if {$is_dryrun} {
4234        if {$anyactive} {
4235            ui_msg "Skipping deactivate $portname @${version_active}_${revision_active}$variant_active (dry run)"
4236        }
4237        ui_msg "Skipping activate $newname @${version_in_tree}_${revision_in_tree}$portinfo(canonical_active_variants) (dry run)"
4238    } elseif {[catch {set result [mportexec $mport activate]} result]} {
4239        global errorInfo
4240        ui_debug $errorInfo
4241        ui_error "Couldn't activate $newname ${version_in_tree}_${revision_in_tree}$portinfo(canonical_active_variants): $result"
4242        catch {mportclose $mport}
4243        return 1
4244    }
4245
4246    # Check if we have to do dependents
4247    if {[info exists options(ports_do_dependents)]} {
4248        # We do dependents ..
4249        set options(ports_nodeps) 1
4250
4251        registry::open_dep_map
4252        if {$portname ne $newname} {
4253            set deplist [registry::list_dependents $newname $version_in_tree $revision_in_tree $portinfo(canonical_active_variants)]
4254        } else {
4255            set deplist [list]
4256        }
4257        if {$anyactive} {
4258            set deplist [concat $deplist [registry::list_dependents $portname $version_active $revision_active $variant_active]]
4259        } else {
4260            set deplist [concat $deplist [registry::list_dependents $portname $version_installed $revision_installed $variant_installed]]
4261        }
4262
4263        if {[llength deplist] > 0} {
4264            foreach dep $deplist {
4265                set mpname [lindex $dep 2]
4266                if {![llength [array get depscache port:$mpname]]} {
4267                    set status [macports::_upgrade $mpname port:$mpname $variationslist [array get options] depscache]
4268                    if {$status != 0 && $status != 2 && ![ui_isset ports_processall]} {
4269                        catch {mportclose $mport}
4270                        return $status
4271                    }
4272                }
4273            }
4274        }
4275    }
4276
4277    if {[info exists uninstall_later] && $uninstall_later} {
4278        foreach i $ilist {
4279            set version [lindex $i 1]
4280            set revision [lindex $i 2]
4281            set variant [lindex $i 3]
4282            if {$version eq $version_in_tree && $revision == $revision_in_tree && $variant eq $portinfo(canonical_active_variants) && $portname eq $newname} {
4283                continue
4284            }
4285            set epoch [lindex $i 5]
4286            ui_debug "Uninstalling $portname ${version}_${revision}$variant"
4287            set regref [registry::open_entry $portname $version $revision $variant $epoch]
4288            if {$is_dryrun} {
4289                ui_msg "Skipping uninstall $portname @${version}_${revision}$variant (dry run)"
4290            } elseif {![registry::run_target $regref uninstall $optionslist]
4291                      && [catch {registry_uninstall::uninstall $portname $version $revision $variant $optionslist} result]} {
4292                global errorInfo
4293                ui_debug $errorInfo
4294                # replaced_by can mean that we try to uninstall all versions of the old port, so handle errors due to dependents
4295                if {$result ne "Please uninstall the ports that depend on $portname first." && ![ui_isset ports_processall]} {
4296                    ui_error "Uninstall $portname @${version}_${revision}$variant failed: $result"
4297                    catch {mportclose $mport}
4298                    return 1
4299                }
4300            }
4301        }
4302    }
4303
4304    # close the port handle
4305    mportclose $mport
4306    return 0
4307}
4308
4309# upgrade_dependencies: helper proc for upgrade
4310# Calls upgrade on each dependency listed in the PortInfo.
4311# Uses upvar to access the variables.
4312proc macports::_upgrade_dependencies {portinfoname depscachename variationslistname optionsname {build_needed no}} {
4313    upvar $portinfoname portinfo $depscachename depscache \
4314          $variationslistname variationslist \
4315          $optionsname options
4316    upvar mport parentmport
4317
4318    # If we're following dependents, we only want to follow this port's
4319    # dependents, not those of all its dependencies. Otherwise, we would
4320    # end up processing this port's dependents n+1 times (recursively!),
4321    # where n is the number of dependencies this port has, since this port
4322    # is of course a dependent of each of its dependencies. Plus the
4323    # dependencies could have any number of unrelated dependents.
4324
4325    # So we save whether we're following dependents, unset the option
4326    # while doing the dependencies, and restore it afterwards.
4327    set saved_do_dependents [info exists options(ports_do_dependents)]
4328    unset -nocomplain options(ports_do_dependents)
4329
4330    set parentworker [ditem_key $parentmport workername]
4331    # each required dep type is upgraded
4332    if {$build_needed && ![global_option_isset ports_binary_only]} {
4333        set dtypes [_deptypes_for_target destroot $parentworker]
4334    } else {
4335        set dtypes [_deptypes_for_target install $parentworker]
4336    }
4337
4338    set status 0
4339    foreach dtype $dtypes {
4340        if {[info exists portinfo($dtype)]} {
4341            foreach i $portinfo($dtype) {
4342                set d [$parentworker eval _get_dep_port $i]
4343                if {![llength [array get depscache port:$d]] && ![llength [array get depscache $i]]} {
4344                    if {$d ne ""} {
4345                        set dspec port:$d
4346                    } else {
4347                        set dspec $i
4348                        set d [lindex [split $i :] end]
4349                    }
4350                    set status [macports::_upgrade $d $dspec $variationslist [array get options] depscache]
4351                    if {$status != 0 && $status != 2 && ![ui_isset ports_processall]} break
4352                }
4353            }
4354        }
4355        if {$status != 0 && $status != 2 && ![ui_isset ports_processall]} break
4356    }
4357    # restore dependent-following to its former value
4358    if {$saved_do_dependents} {
4359        set options(ports_do_dependents) yes
4360    }
4361    return $status
4362}
4363
4364# mportselect
4365#   * command: The only valid commands are list, set, show and summary
4366#   * group: This argument should correspond to a directory under
4367#            ${macports::prefix}/etc/select.
4368#   * version: This argument is only used by the 'set' command.
4369# On error mportselect returns with the code 'error'.
4370proc mportselect {command {group ""} {version {}}} {
4371    ui_debug "mportselect \[$command] \[$group] \[$version]"
4372
4373    set conf_path ${macports::prefix}/etc/select/$group
4374    if {![file isdirectory $conf_path]} {
4375        return -code error "The specified group '$group' does not exist."
4376    }
4377
4378    switch -- $command {
4379        list {
4380            if {[catch {set versions [glob -directory $conf_path *]} result]} {
4381                global errorInfo
4382                ui_debug "${result}: $errorInfo"
4383                return -code error [concat "No configurations associated" \
4384                                           "with '$group' were found."]
4385            }
4386
4387            # Return the sorted list of versions (excluding base and current).
4388            set lversions {}
4389            foreach v $versions {
4390                # Only the file name corresponds to the version name.
4391                set v [file tail $v]
4392                if {$v eq "base" || $v eq "current"} {
4393                    continue
4394                }
4395                lappend lversions [file tail $v]
4396            }
4397            return [lsort $lversions]
4398        }
4399        summary {
4400            # Return the list of portgroups in ${macports::prefix}/etc/select
4401            if {[catch {set lportgroups [glob -directory $conf_path -tails *]} result]} {
4402                global errorInfo
4403                ui_debug "${result}: $errorInfo"
4404                return -code error [concat "No ports with the select" \
4405                                           "option were found."]
4406            }
4407            return [lsort $lportgroups]
4408        }
4409        set {
4410            # Use ${conf_path}/$version to read in sources.
4411            if {$version eq "" || $version eq "base" || $version eq "current"
4412                    || [catch {set src_file [open "${conf_path}/$version"]} result]} {
4413                global errorInfo
4414                ui_debug "${result}: $errorInfo"
4415                return -code error "The specified version '$version' is not valid."
4416            }
4417            set srcs [split [read -nonewline $src_file] \n]
4418            close $src_file
4419
4420            # Use ${conf_path}/base to read in targets.
4421            if {[catch {set tgt_file [open ${conf_path}/base]} result]} {
4422                global errorInfo
4423                ui_debug "${result}: $errorInfo"
4424                return -code error [concat "The configuration file" \
4425                                           "'${conf_path}/base' could not be" \
4426                                           "opened."]
4427            }
4428            set tgts [split [read -nonewline $tgt_file] \n]
4429            close $tgt_file
4430
4431            # Iterate through the configuration files executing the specified
4432            # actions.
4433            set i 0
4434            foreach tgt $tgts {
4435                set src [lindex $srcs $i]
4436
4437                switch -glob -- $src {
4438                    - {
4439                        # The source is unavailable for this file.
4440                        set tgt [file join $macports::prefix $tgt]
4441                        file delete $tgt
4442                        ui_debug "rm -f $tgt"
4443                    }
4444                    /* {
4445                        # The source is an absolute path.
4446                        set tgt [file join $macports::prefix $tgt]
4447                        file delete $tgt
4448                        file link -symbolic $tgt $src
4449                        ui_debug "ln -sf $src $tgt"
4450                    }
4451                    default {
4452                        # The source is a relative path.
4453                        set src [file join $macports::prefix $src]
4454                        set tgt [file join $macports::prefix $tgt]
4455                        file delete $tgt
4456                        file link -symbolic $tgt $src
4457                        ui_debug "ln -sf $src $tgt"
4458                    }
4459                }
4460                incr i
4461            }
4462
4463            # Update the selected version.
4464            set selected_version ${conf_path}/current
4465            if {[file exists $selected_version]} {
4466                file delete $selected_version
4467            }
4468            symlink $version $selected_version
4469            return
4470        }
4471        show {
4472            set selected_version ${conf_path}/current
4473
4474            if {[catch {file type $selected_version} err]} {
4475                # this might be okay if nothing was selected yet,
4476                # just log the error for debugging purposes
4477                ui_debug "cannot determine selected version for $group: $err"
4478                return none
4479            } else {
4480                return [file readlink $selected_version]
4481            }
4482        }
4483    }
4484    return
4485}
4486
4487# Return a good temporary directory to use; /tmp if TMPDIR is not set
4488# in the environment
4489proc macports::gettmpdir {args} {
4490    global env
4491
4492    if {[info exists env(TMPDIR)]} {
4493        return $env(TMPDIR)
4494    } else {
4495        return /tmp
4496    }
4497}
4498
4499# check if the system we're on can run code of the given architecture
4500proc macports::arch_runnable {arch} {
4501    global macports::os_major macports::os_arch macports::os_platform
4502    if {$macports::os_platform eq "darwin"} {
4503        if {$macports::os_major >= 11 && [string first ppc $arch] == 0} {
4504            return no
4505        } elseif {$macports::os_arch eq "i386" && $arch eq "ppc64"} {
4506            return no
4507        } elseif {$macports::os_major <= 8 && $arch eq "x86_64"} {
4508            return no
4509        }
4510    }
4511    return yes
4512}
4513
4514proc macports::diagnose_main {opts} {
4515   
4516    # Calls the main function for the 'port diagnose' command.
4517    #
4518    # Args:
4519    #           None
4520    # Returns:
4521    #           0 on successful execution.
4522
4523    diagnose::main $opts
4524    return 0
4525}
4526
4527proc macports::reclaim_main {} {
4528    # Calls the main function for the 'port reclaim' command.
4529    #
4530    # Args:
4531    #           None
4532    # Returns:
4533    #           None
4534
4535    try {
4536        reclaim::main
4537    } catch {{POSIX SIG SIGINT} eCode eMessage} {
4538        ui_error [msgcat::mc "reclaim aborted: SIGINT received."]
4539        return 2
4540    } catch {{POSIX SIG SIGTERM} eCode eMessage} {
4541        ui_error [msgcat::mc "reclaim aborted: SIGTERM received."]
4542        return 2
4543    } catch {{*} eCode eMessage} {
4544        ui_debug "reclaim failed: $::errorInfo"
4545        ui_error [msgcat::mc "reclaim failed: %s" $eMessage]
4546        return 1
4547    }
4548    return 0
4549}
4550
4551##
4552# Execute the rev-upgrade scan and attempt to rebuild all ports found to be
4553# broken. Depends on the revupgrade_mode setting from macports.conf.
4554#
4555# @param opts
4556#        A Tcl array serialized into a list using array get containing options
4557#        for MacPorts. Options used exclusively by rev-upgrade are
4558#        ports_rev-upgrade_id-loadcmd-check, a boolean indicating whether the
4559#        ID load command of binaries should be check for sanity. This is mostly
4560#        useful for maintainers.
4561# @return 0 if report-only mode is enabled, no ports are broken, or the
4562#         rebuilds finished successfully. 1 if an exception occured during the
4563#         execution of rev-upgrade, 2 if the execution was aborted on user
4564#         request.
4565proc macports::revupgrade {opts} {
4566    set run_loop 1
4567    array set broken_port_counts {}
4568    try {
4569        while {$run_loop == 1} {
4570            set run_loop [revupgrade_scanandrebuild broken_port_counts $opts]
4571        }
4572        return 0
4573    } catch {{POSIX SIG SIGINT} eCode eMessage} {
4574        ui_debug "rev-upgrade failed: $::errorInfo"
4575        ui_error [msgcat::mc "rev-upgrade aborted: SIGINT received."]
4576        return 2
4577    } catch {{POSIX SIG SIGTERM} eCode eMessage} {
4578        ui_error [msgcat::mc "rev-upgrade aborted: SIGTERM received."]
4579        return 2
4580    } catch {{*} eCode eMessage} {
4581        ui_debug "rev-upgrade failed: $::errorInfo"
4582        ui_error [msgcat::mc "rev-upgrade failed: %s" $eMessage]
4583        return 1
4584    }
4585}
4586
4587##
4588# Helper function for rev-upgrade. Do not consider this to be part of public
4589# API. Use macports::revupgrade instead.
4590#
4591# @param broken_port_counts_name
4592#        The name of a Tcl array that's being used to store the number of times
4593#        a port has been rebuilt so far.
4594# @param opts
4595#        A serialized version of a Tcl array that contains options for
4596#        MacPorts. Options used by this method are
4597#        ports_rev-upgrade_id-loadcmd-check, a boolean indicating whether the
4598#        ID loadcommand of binaries should also be checked during rev-upgrade
4599#        and ports_dryrun, a boolean indicating whether no action should be
4600#        taken.
4601# @return 1 if ports were rebuilt and this function should be called again,
4602#         0 otherwise.
4603proc macports::revupgrade_scanandrebuild {broken_port_counts_name opts} {
4604    upvar $broken_port_counts_name broken_port_counts
4605    array set options $opts
4606
4607    set files [registry::file search active 1 binary -null]
4608    set files_count [llength $files]
4609    set fancy_output [expr {![macports::ui_isset ports_debug] && [info exists macports::ui_options(progress_generic)]}]
4610    if {$fancy_output} {
4611        set revupgrade_progress $macports::ui_options(progress_generic)
4612    }
4613    if {$files_count > 0} {
4614        registry::write {
4615            try {
4616                ui_msg "$macports::ui_prefix Updating database of binaries"
4617                set i 1
4618                if {$fancy_output} {
4619                    $revupgrade_progress start
4620                }
4621                foreach f $files {
4622                    if {$fancy_output} {
4623                        if {$files_count < 10000 || $i % 100 == 1} {
4624                            $revupgrade_progress update $i $files_count
4625                        }
4626                    }
4627                    set fpath [$f actual_path]
4628                    ui_debug "Updating binary flag for file $i of ${files_count}: $fpath"
4629                    incr i
4630
4631                    try {
4632                        $f binary [fileIsBinary $fpath]
4633                    } catch {{POSIX SIG SIGINT} eCode eMessage} {
4634                        if {$fancy_output} {
4635                            $revupgrade_progress intermission
4636                        }
4637                        ui_debug [msgcat::mc "Aborted: SIGINT signal received"]
4638                        throw
4639                    } catch {{POSIX SIG SIGTERM} eCode eMessage} {
4640                        if {$fancy_output} {
4641                            $revupgrade_progress intermission
4642                        }
4643                        ui_debug [msgcat::mc "Aborted: SIGTERM signal received"]
4644                        throw
4645                    } catch {{*} eCode eMessage} {
4646                        if {$fancy_output} {
4647                            $revupgrade_progress intermission
4648                        }
4649                        # handle errors (e.g. file not found, permission denied) gracefully
4650                        ui_warn "Error determining file type of `$fpath': $eMessage"
4651                        ui_warn "A file belonging to the `[[registry::entry owner $fpath] name]' port is missing or unreadable. Consider reinstalling it."
4652                    }
4653                }
4654            } catch {*} {
4655                if {${fancy_output}} {
4656                    $revupgrade_progress intermission
4657                }
4658                ui_error "Updating database of binaries failed"
4659                throw
4660            }
4661        }
4662        if {$fancy_output} {
4663            $revupgrade_progress finish
4664        }
4665    }
4666
4667    set broken_files {};
4668    set binaries [registry::file search active 1 binary 1]
4669    set binary_count [llength $binaries]
4670    if {$binary_count > 0} {
4671        ui_msg "$macports::ui_prefix Scanning binaries for linking errors"
4672        set handle [machista::create_handle]
4673        if {$handle eq "NULL"} {
4674            error "Error creating libmachista handle"
4675        }
4676        array unset files_warned_about
4677        array set files_warned_about [list]
4678
4679        if {$fancy_output} {
4680            $revupgrade_progress start
4681        }
4682
4683        try {
4684            set i 1
4685            foreach b $binaries {
4686                if {$fancy_output} {
4687                    if {$binary_count < 10000 || $i % 10 == 1} {
4688                        $revupgrade_progress update $i $binary_count
4689                    }
4690                }
4691                set bpath [$b actual_path]
4692                #ui_debug "${i}/${binary_count}: $bpath"
4693                incr i
4694
4695                set resultlist [machista::parse_file $handle $bpath]
4696                set returncode [lindex $resultlist 0]
4697                set result     [lindex $resultlist 1]
4698
4699                if {$returncode != $machista::SUCCESS} {
4700                    if {$returncode == $machista::EMAGIC} {
4701                        # not a Mach-O file
4702                        # ignore silently, these are only static libs anyway
4703                        #ui_debug "Error parsing file ${bpath}: [machista::strerror $returncode]"
4704                    } else {
4705                        if {$fancy_output} {
4706                            $revupgrade_progress intermission
4707                        }
4708                        ui_warn "Error parsing file ${bpath}: [machista::strerror $returncode]"
4709                    }
4710                    continue;
4711                }
4712
4713                set architecture [$result cget -mt_archs]
4714                while {$architecture ne "NULL"} {
4715                    if {[info exists options(ports_rev-upgrade_id-loadcmd-check)] && $options(ports_rev-upgrade_id-loadcmd-check)} {
4716                        if {[$architecture cget -mat_install_name] ne "NULL" && [$architecture cget -mat_install_name] ne ""} {
4717                            # check if this lib's install name actually refers to this file itself
4718                            # if this is not the case software linking against this library might have erroneous load commands
4719
4720                            try {
4721                                set idloadcmdpath [revupgrade_handle_special_paths $bpath [$architecture cget -mat_install_name]]
4722                                if {[string index $idloadcmdpath 0] ne "/"} {
4723                                    set port [registry::entry owner $bpath]
4724                                    if {$port ne ""} {
4725                                        set portname [$port name]
4726                                    } else {
4727                                        set portname <unknown-port>
4728                                    }
4729                                    if {$fancy_output} {
4730                                        $revupgrade_progress intermission
4731                                    }
4732                                    ui_warn "ID load command in ${bpath}, arch [machista::get_arch_name [$architecture cget -mat_arch]] (belonging to port $portname) contains relative path"
4733                                } elseif {![file exists $idloadcmdpath]} {
4734                                    set port [registry::entry owner $bpath]
4735                                    if {$port ne ""} {
4736                                        set portname [$port name]
4737                                    } else {
4738                                        set portname <unknown-port>
4739                                    }
4740                                    if {$fancy_output} {
4741                                        $revupgrade_progress intermission
4742                                    }
4743                                    ui_warn "ID load command in ${bpath}, arch [machista::get_arch_name [$architecture cget -mat_arch]] refers to non-existent file $idloadcmdpath"
4744                                    ui_warn "This is probably a bug in the $portname port and might cause problems in libraries linking against this file"
4745                                } else {
4746                                    set hash_this [sha256 file $bpath]
4747                                    set hash_idloadcmd [sha256 file $idloadcmdpath]
4748
4749                                    if {$hash_this ne $hash_idloadcmd} {
4750                                        set port [registry::entry owner $bpath]
4751                                        if {$port ne ""} {
4752                                            set portname [$port name]
4753                                        } else {
4754                                            set portname <unknown-port>
4755                                        }
4756                                        if {$fancy_output} {
4757                                            $revupgrade_progress intermission
4758                                        }
4759                                        ui_warn "ID load command in ${bpath}, arch [machista::get_arch_name [$architecture cget -mat_arch]] refers to file ${idloadcmdpath}, which is a different file"
4760                                        ui_warn "This is probably a bug in the $portname port and might cause problems in libraries linking against this file"
4761                                    }
4762                                }
4763                            } catch {{POSIX SIG SIGINT} eCode eMessage} {
4764                                if {$fancy_output} {
4765                                    $revupgrade_progress intermission
4766                                }
4767                                ui_debug [msgcat::mc "Aborted: SIGINT signal received"]
4768                                throw
4769                            } catch {{POSIX SIG SIGTERM} eCode eMessage} {
4770                                if {$fancy_output} {
4771                                    $revupgrade_progress intermission
4772                                }
4773                                ui_debug [msgcat::mc "Aborted: SIGTERM signal received"]
4774                                throw
4775                            } catch {*} {}
4776                        }
4777                    }
4778
4779                    set archname [machista::get_arch_name [$architecture cget -mat_arch]]
4780                    if {![arch_runnable $archname]} {
4781                        ui_debug "skipping $archname in $bpath since this system can't run it anyway"
4782                        set architecture [$architecture cget -next]
4783                        continue
4784                    }
4785
4786                    set loadcommand [$architecture cget -mat_loadcmds]
4787
4788                    while {$loadcommand ne "NULL"} {
4789                        try {
4790                            set filepath [revupgrade_handle_special_paths $bpath [$loadcommand cget -mlt_install_name]]
4791                        } catch {{POSIX SIG SIGINT} eCode eMessage} {
4792                            if {$fancy_output} {
4793                                $revupgrade_progress intermission
4794                            }
4795                            ui_debug [msgcat::mc "Aborted: SIGINT signal received"]
4796                            throw
4797                        } catch {{POSIX SIG SIGTERM} eCode eMessage} {
4798                            if {$fancy_output} {
4799                                $revupgrade_progress intermission
4800                            }
4801                            ui_debug [msgcat::mc "Aborted: SIGTERM signal received"]
4802                            throw
4803                        } catch {*} {
4804                            set loadcommand [$loadcommand cget -next]
4805                            continue;
4806                        }
4807
4808                        set libresultlist [machista::parse_file $handle $filepath]
4809                        set libreturncode [lindex $libresultlist 0]
4810                        set libresult     [lindex $libresultlist 1]
4811
4812                        if {$libreturncode != $machista::SUCCESS} {
4813                            if {![info exists files_warned_about($filepath)]} {
4814                                if {$fancy_output} {
4815                                    $revupgrade_progress intermission
4816                                }
4817                                ui_info "Could not open ${filepath}: [machista::strerror $libreturncode] (referenced from $bpath)"
4818                                if {[string first [file separator] $filepath] == -1} {
4819                                    ui_info "${filepath} seems to be referenced using a relative path. This may be a problem with its canonical library name and require the use of install_name_tool(1) to fix."
4820                                }
4821                                set files_warned_about($filepath) yes
4822                            }
4823                            if {$libreturncode == $machista::EFILE} {
4824                                ui_debug "Marking $bpath as broken"
4825                                lappend broken_files $bpath
4826                            }
4827                            set loadcommand [$loadcommand cget -next]
4828                            continue;
4829                        }
4830
4831                        set libarchitecture [$libresult cget -mt_archs]
4832                        set libarch_found false;
4833                        while {$libarchitecture ne "NULL"} {
4834                            if {[$architecture cget -mat_arch] ne [$libarchitecture cget -mat_arch]} {
4835                                set libarchitecture [$libarchitecture cget -next]
4836                                continue;
4837                            }
4838
4839                            if {[$loadcommand cget -mlt_version] ne [$libarchitecture cget -mat_version] && [$loadcommand cget -mlt_comp_version] > [$libarchitecture cget -mat_comp_version]} {
4840                                if {$fancy_output} {
4841                                    $revupgrade_progress intermission
4842                                }
4843                                ui_info "Incompatible library version: $bpath requires version [machista::format_dylib_version [$loadcommand cget -mlt_comp_version]] or later, but $filepath provides version [machista::format_dylib_version [$libarchitecture cget -mat_comp_version]]"
4844                                ui_debug "Marking $bpath as broken"
4845                                lappend broken_files $bpath
4846                            }
4847
4848                            set libarch_found true;
4849                            break;
4850                        }
4851
4852                        if {!$libarch_found} {
4853                            ui_debug "Missing architecture [machista::get_arch_name [$architecture cget -mat_arch]] in file $filepath"
4854                            if {[path_is_in_prefix $filepath]} {
4855                                ui_debug "Marking $bpath as broken"
4856                                lappend broken_files $bpath
4857                            } else {
4858                                ui_debug "Missing architecture [machista::get_arch_name [$architecture cget -mat_arch]] in file outside prefix referenced from $bpath"
4859                                # ui_debug "   How did you get that compiled anyway?"
4860                            }
4861                        }
4862                        set loadcommand [$loadcommand cget -next]
4863                    }
4864
4865                    set architecture [$architecture cget -next]
4866                }
4867            }
4868        } catch {*} {
4869            if {$fancy_output} {
4870                $revupgrade_progress intermission
4871            }
4872            throw
4873        }
4874        if {$fancy_output} {
4875            $revupgrade_progress finish
4876        }
4877
4878        machista::destroy_handle $handle
4879
4880        set num_broken_files [llength $broken_files]
4881        set s [expr {$num_broken_files == 1 ? "" : "s"}]
4882
4883        if {$num_broken_files == 0} {
4884            ui_msg "$macports::ui_prefix No broken files found."
4885            return 0
4886        }
4887        ui_msg "$macports::ui_prefix Found $num_broken_files broken file${s}, matching files to ports"
4888        set broken_ports {}
4889        set broken_files [lsort -unique $broken_files]
4890        foreach file $broken_files {
4891            set port [registry::entry owner $file]
4892            if {$port ne ""} {
4893                lappend broken_ports $port
4894                lappend broken_files_by_port($port) $file
4895            } else {
4896                ui_error "Broken file $file doesn't belong to any port."
4897            }
4898        }
4899        set broken_ports [lsort -unique $broken_ports]
4900
4901        if {$macports::revupgrade_mode eq "rebuild"} {
4902            # don't try to rebuild ports that don't exist in the tree
4903            set temp_broken_ports {}
4904            foreach port $broken_ports {
4905                set portname [$port name]
4906                if {[catch {mportlookup $portname} result]} {
4907                    ui_debug $::errorInfo
4908                    error "lookup of portname $portname failed: $result"
4909                }
4910                if {[llength $result] >= 2} {
4911                    lappend temp_broken_ports $port
4912                } else {
4913                    ui_warn "No port $portname found in the index; can't rebuild"
4914                }
4915            }
4916
4917            if {[llength $temp_broken_ports] == 0} {
4918                ui_msg "$macports::ui_prefix Broken files found, but all associated ports are not in the index and so cannot be rebuilt."
4919                return 0
4920            }
4921        } else {
4922            set temp_broken_ports $broken_ports
4923        }
4924
4925        set broken_ports {}
4926
4927        foreach port $temp_broken_ports {
4928            set portname [$port name]
4929
4930            if {![info exists broken_port_counts($portname)]} {
4931                set broken_port_counts($portname) 0
4932            }
4933            incr broken_port_counts($portname)
4934            if {$broken_port_counts($portname) > 3} {
4935                ui_error "Port $portname is still broken after rebuilding it more than 3 times."
4936                if {$fancy_output} {
4937                    ui_error "Please run port -d -y rev-upgrade and use the output to report a bug."
4938                }
4939                set rebuild_tries [expr {$broken_port_counts($portname) - 1}]
4940                set s [expr {$rebuild_tries == 1 ? "" : "s"}]
4941                error "Port $portname still broken after rebuilding $rebuild_tries time${s}"
4942            } elseif {$broken_port_counts($portname) > 1 && [global_option_isset ports_binary_only]} {
4943                error "Port $portname still broken after reinstalling -- can't rebuild due to binary-only mode"
4944            }
4945            lappend broken_ports $port
4946        }
4947        unset temp_broken_ports
4948
4949        set num_broken_ports [llength $broken_ports]
4950        set s [expr {$num_broken_ports == 1 ? "" : "s"}]
4951
4952        if {$macports::revupgrade_mode ne "rebuild"} {
4953            ui_msg "$macports::ui_prefix Found $num_broken_ports broken port${s}:"
4954            foreach port $broken_ports {
4955                ui_msg "     [$port name] @[$port version] [$port variants][$port negated_variants]"
4956                foreach f $broken_files_by_port($port) {
4957                    ui_msg "         $f"
4958                }
4959            }
4960            return 0
4961        }
4962
4963        ui_msg "$macports::ui_prefix Found $num_broken_ports broken port${s}, determining rebuild order"
4964        # broken_ports are the nodes in our graph
4965        # now we need adjacents
4966        foreach port $broken_ports {
4967            # initialize with empty list
4968            set adjlist($port) {}
4969            set revadjlist($port) {}
4970            ui_debug "Broken: [$port name]"
4971        }
4972
4973        array set visited {}
4974        foreach port $broken_ports {
4975            # stack of broken nodes we've come across
4976            set stack {}
4977            lappend stack $port
4978
4979            # build graph
4980            if {![info exists visited($port)]} {
4981                revupgrade_buildgraph $port stack adjlist revadjlist visited
4982            }
4983        }
4984
4985        set unsorted_ports $broken_ports
4986        set topsort_ports {}
4987        while {[llength $unsorted_ports] > 0} {
4988            set lowest_adj_number [llength $adjlist([lindex $unsorted_ports 0])]
4989            set lowest_adj_port [lindex $unsorted_ports 0]
4990
4991            foreach port $unsorted_ports {
4992                set len [llength $adjlist($port)]
4993                if {$len < $lowest_adj_number} {
4994                    set lowest_adj_port $port
4995                    set lowest_adj_number $len
4996                }
4997                if {$len == 0} {
4998                    # this node has no further dependencies
4999                    # add it to topsorted list
5000                    lappend topsort_ports $port
5001                    # remove from unsorted list
5002                    set index [lsearch -exact $unsorted_ports $port]
5003                    set unsorted_ports [lreplace $unsorted_ports $index $index]
5004
5005                    # remove edges
5006                    foreach target $revadjlist($port) {
5007                        set index [lsearch -exact $adjlist($target) $port]
5008                        set adjlist($target) [lreplace $adjlist($target) $index $index]
5009                    }
5010
5011                    break;
5012                }
5013            }
5014
5015            # if we arrive here and lowest_adj_number is larger than 0, then we
5016            # have a loop in the graph and need to break it somehow
5017            if {$lowest_adj_number > 0} {
5018                ui_debug "Breaking loop in dependency graph by starting with [$lowest_adj_port name], which has $lowest_adj_number dependencies"
5019                lappend topsort_ports $lowest_adj_port
5020
5021                set index [lsearch -exact $unsorted_ports $lowest_adj_port]
5022                set unsorted_ports [lreplace $unsorted_ports $index $index]
5023
5024                foreach target $revadjlist($port) {
5025                    set index [lsearch -exact $adjlist($target) $lowest_adj_port]
5026                    set adjlist($target) [lreplace $adjlist($target) $index $index]
5027                }
5028            }
5029        }
5030
5031        set broken_portnames {}
5032        if {![info exists macports::ui_options(questions_yesno)]} {
5033            ui_msg "$macports::ui_prefix Rebuilding in order"
5034        }
5035        foreach port $topsort_ports {
5036            lappend broken_portnames [$port name]@[$port version][$port variants]
5037            if {![info exists macports::ui_options(questions_yesno)]} {
5038                ui_msg "     [$port name] @[$port version] [$port variants][$port negated_variants]"
5039            }
5040        }
5041
5042        ##
5043        # User Interaction Question
5044        # Asking before rebuilding in rev-upgrade
5045        if {[info exists macports::ui_options(questions_yesno)]} {
5046            ui_msg "You can always run 'port rev-upgrade' again to fix errors."
5047            set retvalue [$macports::ui_options(questions_yesno) "The following ports will be rebuilt:" "TestCase#1" $broken_portnames {y} 0]
5048            if {$retvalue == 1} {
5049                # quit as user answered 'no'
5050                return 0
5051            }
5052            unset macports::ui_options(questions_yesno)
5053        }
5054
5055        # shared depscache for all ports that are going to be rebuilt
5056        array set depscache {}
5057        set status 0
5058        array set my_options [array get macports::global_options]
5059        set my_options(ports_revupgrade) yes
5060        foreach port $topsort_ports {
5061            set portname [$port name]
5062            if {![info exists depscache(port:$portname)]} {
5063                unset -nocomplain my_options(ports_revupgrade_second_run) \
5064                                  my_options(ports_nodeps)
5065                if {$broken_port_counts($portname) > 1} {
5066                    set my_options(ports_revupgrade_second_run) yes
5067
5068                    if {$broken_port_counts($portname) > 2} {
5069                        # runtime deps are upgraded the first time, build deps
5070                        # the second, so none left to do the third time
5071                        set my_options(ports_nodeps) yes
5072                    }
5073                }
5074
5075                # call macports::upgrade with ports_revupgrade option to rebuild the port
5076                set status [macports::upgrade $portname port:$portname \
5077                    {} [array get my_options] depscache]
5078                ui_debug "Rebuilding port $portname finished with status $status"
5079                if {$status != 0} {
5080                    error "Error rebuilding $portname"
5081                }
5082            }
5083        }
5084
5085        if {[info exists options(ports_dryrun)] && $options(ports_dryrun)} {
5086            ui_warn "If this was no dry run, rev-upgrade would now run the checks again to find unresolved and newly created problems"
5087            return 0
5088        }
5089        return 1
5090    }
5091
5092    return 0
5093}
5094
5095# Return whether a path is in the macports prefix
5096# Usage: path_is_in_prefix path_to_test
5097# Returns true if the path is in the prefix, false otherwise
5098proc macports::path_is_in_prefix {path} {
5099    global macports::prefix macports::applications_dir
5100    if {[string first $macports::prefix $path] == 0} {
5101        return yes
5102    }
5103    if {[string first $macports::applications_dir $path] == 0} {
5104        return yes
5105    }
5106    return no
5107}
5108
5109# Function to replace macros in loadcommand paths with their proper values (which are usually determined at load time)
5110# Usage: revupgrade_handle_special_paths name_of_file path_from_loadcommand
5111# Returns the corrected path on success or an error in case of failure.
5112# Note that we can't reliably replace @executable_path, because it's only clear when executing a file where it was executed from.
5113# Replacing @rpath does not work yet, but it might be possible to get it working using the rpath attribute in the file containing the
5114# loadcommand
5115proc macports::revupgrade_handle_special_paths {fname path} {
5116    set corrected_path $path
5117
5118    set loaderpath_idx [string first @loader_path $corrected_path]
5119    if {$loaderpath_idx != -1} {
5120        set corrected_path [string replace $corrected_path $loaderpath_idx ${loaderpath_idx}+11 [file dirname $fname]]
5121    }
5122
5123    set executablepath_idx [string first @executable_path $corrected_path]
5124    if {$executablepath_idx != -1} {
5125        ui_debug "Ignoring loadcommand containing @executable_path in $fname"
5126        error "@executable_path in loadcommand"
5127    }
5128
5129    set rpath_idx [string first @rpath $corrected_path]
5130    if {$rpath_idx != -1} {
5131        ui_debug "Ignoring loadcommand containing @rpath in $fname"
5132        error "@rpath in loadcommand"
5133    }
5134
5135    return $corrected_path
5136}
5137
5138# Recursively build the dependency graph between broken ports
5139# Usage: revupgrade_buildgraph start_port name_of_stack name_of_adjacency_list name_of_reverse_adjacency_list name_of_visited_map
5140proc macports::revupgrade_buildgraph {port stackname adjlistname revadjlistname visitedname} {
5141    upvar $stackname stack
5142    upvar $adjlistname adjlist
5143    upvar $revadjlistname revadjlist
5144    upvar $visitedname visited
5145
5146    set visited($port) true
5147
5148    ui_debug "Processing port [$port name] @[$port epoch]:[$port version]_[$port revision] [$port variants] [$port negated_variants]"
5149    set dependent_ports [$port dependents]
5150    foreach dep $dependent_ports {
5151        set is_broken_port false
5152
5153        if {[info exists adjlist($dep)]} {
5154            ui_debug "Dependent [$dep name] is broken, adding edge from [$dep name] to [[lindex $stack 0] name]"
5155            ui_debug "Making [$dep name] new head of stack"
5156            # $dep is one of the broken ports
5157            # add an edge to the last broken port in the DFS
5158            lappend revadjlist([lindex $stack 0]) $dep
5159            lappend adjlist($dep) [lindex $stack 0]
5160            # make this port the new last broken port by prepending it to the stack
5161            set stack [linsert $stack 0 $dep]
5162
5163            set is_broken_port true
5164        }
5165        if {![info exists visited($dep)]} {
5166            revupgrade_buildgraph $dep stack adjlist revadjlist visited
5167        }
5168        if {$is_broken_port} {
5169            ui_debug "Removing [$dep name] from stack"
5170            # remove $dep from the stack
5171            set stack [lrange $stack 1 end]
5172        }
5173    }
5174}
5175
5176# get cached ping time for host, modified by blacklist and preferred list
5177proc macports::get_pingtime {host} {
5178    global macports::ping_cache macports::host_blacklisted macports::host_preferred
5179    if {[info exists host_blacklisted($host)]} {
5180        return -1
5181    } elseif {[info exists host_preferred($host)]} {
5182        return 1
5183    } elseif {[info exists ping_cache($host)]} {
5184        # expire entries after 1 day
5185        if {[clock seconds] - [lindex $ping_cache($host) 1] <= 86400} {
5186            return [lindex $ping_cache($host) 0]
5187        }
5188    }
5189    return {}
5190}
5191
5192# cache a ping time of ms for host
5193proc macports::set_pingtime {host ms} {
5194    global macports::ping_cache
5195    set ping_cache($host) [list $ms [clock seconds]]
5196}
5197
5198# read and cache archive_sites.conf (called from port1.0 code)
5199proc macports::get_archive_sites_conf_values {} {
5200    global macports::archive_sites_conf_values macports::autoconf::macports_conf_path
5201    if {![info exists archive_sites_conf_values]} {
5202        set archive_sites_conf_values {}
5203        set all_names {}
5204        array set defaults {applications_dir /Applications/MacPorts prefix /opt/local type tbz2}
5205        set conf_file ${macports_conf_path}/archive_sites.conf
5206        set conf_options {applications_dir frameworks_dir name prefix type urls}
5207        if {[file isfile $conf_file]} {
5208            set fd [open $conf_file r]
5209            while {[gets $fd line] >= 0} {
5210                if {[regexp {^(\w+)([ \t]+(.*))?$} $line match option ignore val] == 1} {
5211                    if {$option in $conf_options} {
5212                        if {$option eq "name"} {
5213                            set cur_name $val
5214                            lappend all_names $val
5215                        } elseif {[info exists cur_name]} {
5216                            set trimmedval [string trim $val]
5217                            if {$option eq "urls"} {
5218                                set processed_urls {}
5219                                foreach url $trimmedval {
5220                                    lappend processed_urls ${url}:nosubdir
5221                                }
5222                                lappend archive_sites_conf_values portfetch::mirror_sites::sites($cur_name) $processed_urls
5223                                set sites($cur_name) $processed_urls
5224                            } else {
5225                                lappend archive_sites_conf_values portfetch::mirror_sites::archive_${option}($cur_name) $trimmedval
5226                                set archive_${option}($cur_name) $trimmedval
5227                            }
5228                        } else {
5229                            ui_warn "archive_sites.conf: ignoring '$option' occurring before name"
5230                        }
5231                    } else {
5232                        ui_warn "archive_sites.conf: ignoring unknown key '$option'"
5233                    }
5234                }
5235            }
5236            close $fd
5237
5238            # check for unspecified values and set to defaults
5239            foreach cur_name $all_names {
5240                foreach key [array names defaults] {
5241                    if {![info exists archive_${key}($cur_name)]} {
5242                        set archive_${key}($cur_name) $defaults($key)
5243                        lappend archive_sites_conf_values portfetch::mirror_sites::archive_${key}($cur_name) $defaults($key)
5244                    }
5245                }
5246                if {![info exists archive_frameworks_dir($cur_name)]} {
5247                    set archive_frameworks_dir($cur_name) $archive_prefix($cur_name)/Library/Frameworks
5248                    lappend archive_sites_conf_values portfetch::mirror_sites::archive_frameworks_dir($cur_name) $archive_frameworks_dir($cur_name)
5249                }
5250                if {![info exists sites($cur_name)]} {
5251                    ui_warn "archive_sites.conf: no urls set for $cur_name"
5252                    set sites($cur_name) {}
5253                    lappend archive_sites_conf_values portfetch::mirror_sites::sites($cur_name) {}
5254                }
5255            }
5256        }
5257    }
5258    return $archive_sites_conf_values
5259}
5260
5261##
5262# Escape a string for use in a POSIX shell, e.g., when passing it to the \c system Pextlib extension. This is necessary
5263# to handle cases such as group names with backslashes correctly. See #43875 for an example of a problem caused by
5264# missing quotes.
5265#
5266# @param arg The argument that should be escaped for use in a POSIX shell
5267# @return A quoted version of the argument
5268proc macports::shellescape {arg} {
5269    set mapping {}
5270    # Replace each backslash by a double backslash. Apparently Bash treats Backslashes in single-quoted strings
5271    # differently depending on whether is was invoked as sh or bash: echo 'using \backslashes' preserves the backslash
5272    # in bash mode, but interprets it in sh mode. Since the `system' command uses sh, escape backslashes.
5273    lappend mapping "\\" "\\\\"
5274    # Replace each single quote with a single quote (closing the currently open string), an escaped single quote \'
5275    # (additional backslash needed to escape the backslash in Tcl), and another single quote (opening a new quoted
5276    # string).
5277    lappend mapping "'" "'\\''"
5278
5279    # Add a single quote at the start, escape all single quotes in the argument, and add a single quote at the end
5280    return "'[string map $mapping $arg]'"
5281}
Note: See TracBrowser for help on using the repository browser.