# -*- 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
# diagnose.tcl
#
# Copyright (c) 2002-2003 Apple Inc.
# Copyright (c) 2004-2005 Paul Guyot, <pguyot@kallisys.net>.
# Copyright (c) 2004-2006 Ole Guldberg Jensen <olegb@opendarwin.org>.
# Copyright (c) 2004-2005 Robert Shaw <rshaw@opendarwin.org>
# Copyright (c) 2004-2014, 2016-2018 The MacPorts Project
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. Neither the name of Apple Inc. nor the names of its contributors
#    may be used to endorse or promote products derived from this software
#    without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

# Todo:

# Done:
# Add -q for quiet mode, where we don't print anything
# Check for command line tools
# Check for any DYLD_* environment variables
# Check for '.la' in dylib and '.prl'
# Check if installed files are readable
# Check for openssl
# Crowd-source more ideas from the mailing-list
# Check if $PATH is first
# Check for issues with compilation. Compile small, simple file, check for "couldn't create cache file"
# check_for_stray_developer_directory
# Check for *.h, *.hpp, *.hxx in /usr/local/include
# Check for *.dylib in /usr/local/lib
# Check for other package managers. Fink = /sw, homebrew = /usr/local/Cellar
# Check for all files installed by ports exists
# Check for archives from all ports exists
# Check for things in /usr/local
# Check for x11.app if the OS is 10.6 and suggest installing xorg-server or the xquartz site
# Add error catching for lines without an equals sign.
# Support comments for the parser
# Check for amount of drive space
# Move port_diagnose.ini to the port tree, below _resources
# Check for curl
# Check for rsync
# Check if macports is in /opt/local


package provide diagnose 1.0

package require macports

namespace eval diagnose {

    # Command line argument that determines whether or not to output things fancily.
    variable quiet 0

    proc main {opts} {

        # The main function. Handles all the calls to the correct functions, and sets the config_options array,
        # as well as the parser_options array.
        #
        # Args:
        #           opts - The options passed in. Currently the only option available is 'quiet'.
        # Returns:
        #           None

        # Setting the 'quiet' variable based on what was passed in.
        if {$opts ne ""} {
            set diagnose::quiet 1
        } else {
            set diagnose::quiet 0
        }

        array set config_options    [list]
        set parser_options          [list macports_location profile_path shell_location \
                                    xcode_build]

        set user_config_path        "${macports::autoconf::macports_conf_path}/port_diagnose.ini"
        set xcode_config_path       [macports::getdefaultportresourcepath "macports1.0/xcode_versions.ini"]

        # Make sure the xcode config exists
        check_xcode_config $xcode_config_path

        # Read the config files
        get_config config_options $parser_options $user_config_path
        get_config config_options $parser_options $xcode_config_path
        if {![info exists config_options(macports_location)]} {
            set config_options(macports_location) "${macports::prefix}"
        }
        if {![info exists config_options(profile_path)]} {
            set config_options(profile_path) "${macports::user_home}/.bash_profile"
        }
        if {![info exists config_options(shell_location)]} {
            if {[info exists macports::sudo_user]} {
                set username ${macports::sudo_user}
            } else {
                set username [exec id -un]
            }
            set config_options(shell_location) [lindex [exec /usr/bin/dscl . -read "/Users/${username}" shell] end]
        }

        # Start the checks
        check_path $config_options(macports_location) $config_options(profile_path) $config_options(shell_location)
        check_macports_location
        check_archs
        check_permissions
        check_xcode config_options
        check_for_app curl
        check_for_app rsync
        check_for_app openssl
        check_free_space
        check_for_x11
        check_for_files_in_usr_local
        check_tarballs
        check_port_files
        check_for_package_managers
        check_for_stray_developer_directory
        check_compilation_error_cache
        check_for_dyld
        check_for_clt
    }

    proc check_for_clt {} {

        # Checks to see if the Xcode Command Line Tools are installed by checking if the file
        # /Library/Developer/CommandLineTools exists if the system is running 10.9+, or if they're
        # running an older version, if /usr/include exists.
        #
        # Args:
        #           None
        # Returns:
        #           None

        output "command line tools"

        if {[vercmp ${macports::macos_version_major} >= "10.9"]} {

            if {![file exists "/Library/Developer/CommandLineTools/"]} {

                ui_warn "Xcode Command Line Tools do not appear to be installed. To install them, please enter the command:
                                    xcode-select --install"
                success_fail 0
                return
            } elseif {$macports::xcodecltversion eq "none"} {
                ui_warn "The Xcode Command Line Tools package appears to be installed, but its receipt appears to be missing."
                ui_warn "The Command Line Tools may be outdated, which can cause problems."
                ui_warn "Please see: <https://trac.macports.org/wiki/ProblemHotlist#reinstall-clt>"
            }
            success_fail 1
            return

        } else {

            if {![file exists "/usr/include"]} {

                ui_warn "Xcode Command Line Tools do not appear to be installed. \
                         To install them, please follow the instructions for your OS version at:
                                    <https://guide.macports.org/#installing.xcode>"
                success_fail 0
                return
            }
            success_fail 1
        }
    }

    proc check_for_dyld {} {

        # Checks to see if the current MacPorts session is running with a DYLD_* environment
        # variable set.
        #
        # Args:
        #           None
        # Returns:
        #           None

        output "DYLD_* environment variables"

        set printenv        [exec printenv]
        set split           [split $printenv]

        if {[regexp {DYLD_.} $split]} {
            ui_warn "found a DYLD_* environment variable. These are known to cause issues with MacPorts. Please\
                     unset the variable for the duration MacPorts is running."

            success_fail 0
            return
        }

        success_fail 1
   }

    proc output {string} {

        # Outputs the given string formatted correctly.
        #
        # Args:
        #           string - The string to be output
        # Returns:
        #           None

        if {!${diagnose::quiet}} {
            ui_info -nonewline "Checking for $string... "
        }
    }

    proc success_fail {result} {

        # Either outputs a [SUCCESS] or [FAILED], depending on the result.
        #
        # Args:
        #           result - An integer value. 1 = [SUCCESS], anything else = [FAILED]
        # Returns:
        #           None

        if {!${diagnose::quiet}} {

            if {$result == 1} {

                ui_info "\[SUCCESS\]"
                return
            }

            ui_info "\[FAILED\]"
        }
    }

    proc check_compilation_error_cache {} {

        # Checks to see if the compiler can compile properly, or it throws the error, "couldn't create cache file".
        #
        # Args:
        #           None
        # Returns:
        #           None

        # TODO: Should we drop privileges?

        output "compilation errors"

        # 'clang' will fail when using
        # https://trac.macports.org/wiki/UsingTheRightCompiler#testing
        if {![file isfile /usr/bin/cc]} {
            ui_error "No compiler found at /usr/bin/cc"
            success_fail 0
            return
        }

        set builddir "[macports::gettmpdir]/port_diagnose"
        file mkdir $builddir
        set filepath    "${builddir}/test.c"
        set fd          [open $filepath w]

        puts $fd "int main() { return 0; }"
        close $fd

        catch {exec /usr/bin/cc $filepath -o "${builddir}/main_test"} output

        file delete -force $builddir

        if {[string length $output] > 0} {
            # Some type of error
            if {[string match "*couldn't create cache file*" $output]} {
                ui_warn "found errors when attempting to compile file. To fix this issue, delete your tmp folder using:
                       rm -rf \$TMPDIR"
            } else {
                ui_warn $output
            }
            success_fail 0
            return
        }

        success_fail 1

    }

    proc check_for_stray_developer_directory {} {

        # Checks to see if the script to remove leftover files from Xcode has been run or not. Implementation heavily influenced
        # by Homebrew implementation.
        #
        # Args:
        #           None
        # Returns:
        #           None

        output "stray developer directory"

        set uninstaller "/Developer/Library/uninstall-developer-folder"

        if {${macports::xcodeversion} >= 4.3 && [file exists $uninstaller]} {
            ui_warn "you have leftover files from an older version of Xcode. You should delete them by using, $uninstaller"

            success_fail 0
            return
        }

        success_fail 1
    }

    proc check_for_package_managers {} {

        # Checks to see if either Fink or Homebrew are installed on the system. If they are, it warns them and suggest they uninstall
        # or move them to a different location.
        #
        # Args:
        #           None
        # Returns:
        #           None

        output "HomeBrew"

        if {[file exists "/usr/local/Cellar"]} {
            ui_warn "it seems you have Homebrew installed on this system -- Because Homebrew uses /usr/local, this can potentially cause issues\
                     with MacPorts. We'd recommend you either uninstall it, or move it from /usr/local for now."

            success_fail 0

        } else {

            success_fail 1
        }

        output "Fink"
        if {[file exists "/sw"]} {
            ui_warn "it seems you have Fink installed on your system -- This could potentially cause issues with MacPorts. We'd recommend you\
                     either uninstall it, or move it from /sw for now."

            success_fail 0

        } else {

            success_fail 1
        }
    }

    proc check_port_files {} {

        # Checks to see if each file installed by all active and installed ports actually exists on the filesystem. If not, it warns
        # the user and suggests the user deactivate and reactivate the port.
        #
        # Args:
        #           None
        # Returns:
        #           None


        set activePorts [registry::entry installed]

        set filesForPorts [dict create]
        set totalFiles 0

        foreach port $activePorts {
            set files [$port files]
            dict set filesForPorts [$port name] $files
            incr totalFiles [llength $files]
            #registry::entry close $port
        }

        set fancyOutput [expr {   ![macports::ui_isset ports_debug] \
                               && ![macports::ui_isset ports_verbose] \
                               && [info exists macports::ui_options(progress_generic)] \
                               && !${diagnose::quiet}}]

        if {$fancyOutput} {
            set progress $macports::ui_options(progress_generic)
        }

        if {$totalFiles > 0} {
            if {$fancyOutput} {
                output "files installed by ports on disk"
                if {!${diagnose::quiet}} {
                    # we need a newline here or the progress bar will overwrite the line
                    ui_msg ""
                }
                $progress start
            }

            set currentFile 1
            foreach name [lsort [dict keys $filesForPorts]] {
                foreach file [dict get $filesForPorts $name] {
                    if {$fancyOutput} {
                        $progress update $currentFile $totalFiles
                    } else {
                        output "file '$file' on disk"
                    }

                    if {[catch {file type $file}]} {
                        if {$fancyOutput} {
                            $progress intermission
                        } else {
                            success_fail 0
                        }
                        ui_warn "couldn't find file '$file' for port '$name'. Please deactivate and reactivate the port to fix this issue."
                    } elseif {!$fancyOutput} {
                        success_fail 1
                    }
                    # TODO: check permissions against those in the port image.
                    # Can't just check for readability because some files
                    # (and/or their parent directories) should not be readable
                    # by normal users for various reasons.

                    incr currentFile
                }
            }

            if {$fancyOutput} {
                $progress finish
            }
        }
    }

    proc check_tarballs {} {

        # Checks if the archives for each installed port in /opt/local/var/macports/software/$name is actually in there. If not, it warns
        # the user and suggest a reinstallation of the port.
        #
        # Args:
        #           None
        # Returns:
        #           None

        set ports [registry::entry imaged]

        foreach port $ports {
            if {![file exists [$port location]]} {
                ui_warn "couldn't find the archive for '[$port name] @[$port version]_[$port revision][$port variants]'. Please uninstall and reinstall this port."
            }
            #registry::entry close $port
        }
    }

    proc check_for_files_in_usr_local {} {

        # Checks for dylibs in /usr/local/lib and header files in /usr/local/include, and warns the user about said files if they
        # are found.
        #
        # Args:
        #           None
        # Returns:
        #           None

        output "dylibs in /usr/local/lib"

        if {[glob -nocomplain -directory "/usr/local/lib" *.dylib *.la *.prl] ne ""} {
            ui_warn "found dylibs in your /usr/local/lib directory. These are known to cause problems. We'd recommend\
                     you remove them."

            success_fail 0

        } else {

            success_fail 1
        }

        output "header files in /usr/local/include"

        if {[glob -nocomplain -directory "/usr/local/include" *.h *.hpp *.hxx] ne ""} {
            ui_warn "found header files in your /usr/local/include directory. These are known to cause problems. We'd recommend\
                     you remove them."

            success_fail 0

        } else {

            success_fail 1
        }
    }

    proc check_for_x11 {} {

        # Checks to see if the user is using the X11.app, and if they're on 10.6. If they are, it alerts them about it.
        #
        # Args:
        #           None
        # Returns:
        #           None

        if {${macports::macos_version_major} eq "10.6"} {
            output "X11.app on Mac OS X 10.6 systems"

            if {[file exists /Applications/X11.app]} {
                ui_error "it seems you have Mac OS X 10.6 installed, and are using X11 from \"X11.app\". This has been known to cause issues.\
                         To fix this, please install xorg-server, by using the command 'sudo port install xorg-server', or installing it from\
                         their website, https://www.xquartz.org/releases/."

                success_fail 0
                return
            }
            success_fail 1
        }
    }

    proc check_free_space {} {

        # Checks to see if the user has less than 5 gigs of space left, and warns if they don't.
        #
        # Args:
        #           None
        # Returns:
        #           None

        output "free disk space"

        set output          [exec df -g]
        set tokens          [split $output \n]
        set disk_info       [lindex $tokens 1]
        set available       [lindex $disk_info 3]

        if {$available < 5} {
            ui_warn "you have less than 5 GiB of free disk space! This can cause serious errors. We recommend trying to clear out unnecessary\
                     programs and files by running 'sudo port reclaim', or manually uninstalling/deleting programs and folders on your drive."

            success_fail 0
            return
        }

        success_fail 1
    }

    proc check_macports_location {} {

        # Checks to see if port is where it should be. If it isn't, freak the frick out.
        #
        # Args:
        #           None
        # Returns:
        #           None

        output "MacPorts' location"

        if {![file exists ${macports::prefix}/bin/port]} {
            ui_error "the port command was not found in ${macports::prefix}/bin. This can potentially cause errors. It's recommended you move it back to ${macports::prefix}/bin."
            success_fail 0
            return
        }

        success_fail 1
   }

    proc check_for_app {app} {

        # Check's if the binary supplied exists in /usr/bin. If it doesn't, it warns the user.
        #
        # Args:
        #           app - The name of the app to check for.
        # Returns
        #           None

        output "for '$app'"

        if {![file exists /usr/bin/$app]} {
            ui_error "$app is needed by MacPorts to function normally, but wasn't found on this system. We'd recommend\
                      installing it for continued use of MacPorts."
            success_fail 0
            return
        }

        success_fail 1
    }

    proc check_xcode {config_options} {

        # Checks to see if the currently installed version of Xcode works with the current OS version.
        #
        # Args:
        #           config_options - The associative array containing all options in the config files
        # Returns:
        #           None

        output "correct Xcode version"

        upvar $config_options config

        set mac_version     ${macports::macos_version_major}
        set xcode_current   ${macports::xcodeversion}
        if {[info exists config(xcode_version_$mac_version)]} {
            set xcode_versions  $config(xcode_version_$mac_version)
        } else {
            ui_warn "No Xcode version info was found for your OS version."
            success_fail 0
            return
        }

        if {$xcode_current in $xcode_versions} {
            success_fail 1
        } else {
            foreach ver $xcode_versions {
                if {[vercmp $xcode_current $ver] < 0} {
                    # installed Xcode is older than a known good version
                    set too_old 1
                    break
                }
            }
            if {![info exists too_old]} {
                foreach osvers_var [array names config -glob xcode_version_*] {
                    set osvers [string range $osvers_var [string length xcode_version_] end]
                    if {[vercmp $osvers $mac_version] > 0} {
                        foreach xcvers $config(xcode_version_$osvers) {
                            if {[vercmp $xcode_current $xcvers] >= 0} {
                                # installed Xcode is for a newer OS version
                                set too_new 1
                                break
                            }
                        }
                        if {[info exists too_new]} {
                            break
                        }
                    }
                }
            }
            if {[info exists too_old] || [info exists too_new]} {
                ui_error "The installed version of Xcode, $xcode_current, is not supported by MacPorts.\
                          For your currently installed system, the following versions of Xcode are supported:\
                          $xcode_versions"
                success_fail 0
            } else {
                ui_warn "The installed version of Xcode, $xcode_current, is newer than the versions\
                         currently known to MacPorts. It most likely works, but has not been tested.\
                         For your currently installed system, the following versions of Xcode are known to work:\
                         $xcode_versions"
                success_fail 1
            }
        }
    }

    proc check_xcode_config {path} {

        # Checks to see if xcode_versions.ini exists. If it does, it returns. If it doesn't, then it raises an error.
        #
        # Args:
        #           None
        # Returns:
        #           None

        if {![file exists $path]} {
            ui_error "No configuration file found at $path. Please run,
                        \"port selfupdate\""
            error "missing [file tail $path]"

        }
    }

    proc get_config {config_options parser_options path} {

        # Reads in and parses the configuration file passed in to $path. After parsing, all variables found are assigned
        # in the 'config_options' associative array.
        #
        # Args:
        #           config_options - The associative array responsible for holding all the configuration options.
        #           parser_options - The list responsible for holding each option to set/look for in the configuration file.
        #           path           - The path to the correct config_file
        # Returns:
        #           None.

        if {![file isfile $path]} {
            return
        }

        upvar $config_options config

        set fd   [open $path r]
        set text [read $fd]
        set data [split $text "\n"]

        close $fd

        foreach line $data {

            # Ignore comments
            if {[string index $line 0] eq "#"} {
                continue
            }

            #The tokens
            set tokens [split $line "="]

            # Only care about things that are in $parser_options or are lists of xcode versions
            if {[lindex $tokens 0] in $parser_options || [string match xcode_version_* [lindex $tokens 0]]} {
                set config([lindex $tokens 0]) [lindex $tokens 1]

            # Ignore whitespace
            } elseif {[lindex $tokens 0] eq ""} {
                continue

            } else {
                ui_error "unrecognized config option in file $path: [lindex $tokens 0]"
            }
        }
    }

    proc check_path {port_loc profile_path shell_loc} {

        # Checks to see if port_location/bin and port_location/sbin are in the $PATH environment variable.
        # If they aren't, it appends it to the correct shell's profile file.
        #
        # Args:
        #           port_loc        - The location of port (as set in the config file)
        #           profile_path    - The location of the profile file (as set in the config file)
        #           shell_loc       - The location of the shell binary (as set in the config file)
        # Returns:
        #           None.

        set known_shells [list bash csh ksh sh tcsh zsh]
        set shell_name [file tail $shell_loc]
        if {$shell_name ni $known_shells} {
            return
        }

        set path ${macports::user_path}
        set split [split $path :]

        if {"$port_loc/bin" ni $split || "$port_loc/sbin" ni $split} {
            ui_warn "Your \$PATH environment variable does not currently include $port_loc/bin or $port_loc/sbin"

            ui_msg "Please refer to the guide on how to configure your shell:"
            ui_msg "  https://guide.macports.org/#installing.shell"

            # XXX Only works for bash. Should set default profile_path based on the shell.
            if {[info exists macports::ui_options(questions_yesno)] && $shell_name eq "bash"} {
                ui_msg "MacPorts can also write the required configuration to $profile_path for you."
                set question "Would you like to add $port_loc/bin to your \$PATH variable now?"
                set retval [$macports::ui_options(questions_yesno) $msg "DiagnoseFixPATH" "" n 0 $question]
                if {$retval == 0} {
                    # XXX: this should use the same paths and comments as the
                    # postflight script of the pkg installer. Maybe they could even
                    # share code?
                    ui_debug "Attempting to add $port_loc/bin to $profile_path"

                    if {[file exists $profile_path]} {
                        if {[file writable $profile_path]} {
                            # XXX: Should keep a backup like postflight script does
                            set fd [open $profile_path a]
                            puts $fd ""
                            puts $fd "# MacPorts diagnose addition on [clock format $date -format "%Y-%m-%dT%H:%M:%S%z"]"
                            puts $fd "export PATH=$port_loc/bin:$port_loc/sbin:\$PATH"
                            puts $fd ""
                            close $fd

                            ui_msg "Added PATH environment variable to your $shell_name configuration. It is important that you now close this terminal window and then open a new terminal window to load the modified environment from ${profile_path}."
                        } else {
                            ui_error "Can't write to ${profile_path}."
                        }
                    } else {
                        ui_error "$profile_path does not exist."
                    }
                } else {
                    ui_msg "Not fixing your \$PATH variable."
                }
            }
       }
   }

    proc check_permissions {} {
        # check that everyone has rx permissions on macports directories,
        # and that they are not world writable
        set no_r_dirs [list]
        set no_x_dirs [list]
        set ww_dirs [list]
        foreach subdir [list build distfiles incoming logs registry software] {
            lappend check_paths [file join $macports::portdbpath $subdir]
        }
        while {[llength $check_paths] > 0} {
            set path [lindex $check_paths end]
            set check_paths [lreplace ${check_paths}[set check_paths {}] end end]
            if {[catch {file type $path}]} {
                continue
            }
            set perms [file attributes $path -permissions]
            if {$perms & 0002 && $path ni $ww_dirs} {
                lappend ww_dirs $path
            }
            if {!($perms & 0001) && $path ni $no_x_dirs} {
                lappend no_x_dirs $path
            }
            if {!($perms & 0004) && $path ni $no_r_dirs} {
                lappend no_r_dirs $path
            }
            set parent [file dirname $path]
            if {$parent ni $check_paths && $path ne $macports::prefix && $parent ne "/"} {
                lappend check_paths $parent
            }
        }
        # don't complain about writable parent dirs of prefix or sources
        set check_paths [list [file dirname $macports::prefix]]
        foreach source $macports::sources {
            set sourcedir [file dirname [macports::getindex [lindex $source 0]]]
            if {$sourcedir ni $check_paths} {
                lappend check_paths $sourcedir
            }
        }
        while {[llength $check_paths] > 0} {
            set path [lindex $check_paths end]
            set check_paths [lreplace ${check_paths}[set check_paths {}] end end]
            set perms [file attributes $path -permissions]
            if {!($perms & 0001) && $path ni $no_x_dirs} {
                lappend no_x_dirs $path
            }
            if {!($perms & 0004) && $path ni $no_r_dirs} {
                lappend no_r_dirs $path
            }
            set parent [file dirname $path]
            if {$parent ni $check_paths && $parent ne "/"} {
                lappend check_paths $parent
            }
        }
        set problem 0
        if {[llength $no_r_dirs] > 0} {
            set problem 1
            ui_warn "The following directories are not readable by all users:"
            foreach p $no_r_dirs {
                ui_msg "  $p"
            }
        }
        if {[llength $no_x_dirs] > 0} {
            set problem 1
            ui_warn "The following directories are not searchable by all users:"
            foreach p $no_x_dirs {
                ui_msg "  $p"
            }
        }
        if {[llength $ww_dirs] > 0} {
            set problem 1
            ui_warn "The following directories are writable by all users:"
            foreach p $ww_dirs {
                ui_msg "  $p"
            }
        }
        if {$problem} {
            ui_warn "Recommended directory permissions are 0755; use 'chmod 0755 <path>' to correct."
        }
    }

    proc check_archs {} {
        set known_archs [list arm64 i386 ppc ppc64 x86_64]
        if {($macports::os_subplatform eq "macosx" && [llength $macports::build_arch] != 1)
                || [llength $macports::build_arch] > 1} {
            ui_warn "build_arch should contain one architecture name, but it is currently '$macports::build_arch'"
        } elseif {$macports::build_arch ni $known_archs} {
            ui_warn "build_arch should be one of '$known_archs', but it is currently '$macports::build_arch'"
        }
        if {$macports::os_subplatform eq "macosx" && [llength $macports::universal_archs] > 0} {
            if {[llength $macports::build_arch] == 1 && $macports::build_arch ni $macports::universal_archs} {
                ui_warn "universal_archs ($macports::universal_archs) should contain build_arch ($macports::build_arch)"
            }
            if {[llength $macports::universal_archs] < 2 && $macports::os_major ni [list 18 19]} {
                ui_warn "universal_archs should contain at least 2 archs, but it is currently '$macports::universal_archs'"
            }
            foreach arch $macports::universal_archs {
                if {$arch ni $known_archs} {
                    ui_warn "universal_archs contains '$arch' which is not one of '$known_archs'"
                }
            }
        }
    }
}
