Projects
New Ticket     Wiki     Browse Source     Timeline     Roadmap     Bug Reports     Search

root/trunk/base/src/port1.0/portutil.tcl

Revision 45034, 73.2 KB (checked in by perry@…, 21 hours ago)

src/port, src/port1.0 - Cleaned up the variants code related to Ticket #14178.
* PortInfo(_variants) -> PortInfo(vinfo)
* port variants --index works now.
* action_variants uses PortInfo(variant_desc) as a fallback.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
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# portutil.tcl
3# $Id$
4#
5# Copyright (c) 2004 Robert Shaw <rshaw@opendarwin.org>
6# Copyright (c) 2002 Apple Computer, Inc.
7# Copyright (c) 2006, 2007 Markus W. Weissmann <mww@macports.org>
8# All rights reserved.
9#
10# Redistribution and use in source and binary forms, with or without
11# modification, are permitted provided that the following conditions
12# are met:
13# 1. Redistributions of source code must retain the above copyright
14#    notice, this list of conditions and the following disclaimer.
15# 2. Redistributions in binary form must reproduce the above copyright
16#    notice, this list of conditions and the following disclaimer in the
17#    documentation and/or other materials provided with the distribution.
18# 3. Neither the name of Apple Computer, Inc. nor the names of its contributors
19#    may be used to endorse or promote products derived from this software
20#    without specific prior written permission.
21#
22# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32# POSSIBILITY OF SUCH DAMAGE.
33#
34
35package provide portutil 1.0
36package require Pextlib 1.0
37package require macports_dlist 1.0
38package require macports_util 1.0
39package require msgcat
40package require porttrace 1.0
41
42global targets target_uniqid all_variants
43
44set targets [list]
45set target_uniqid 0
46
47set all_variants [list]
48
49########### External High Level Procedures ###########
50
51namespace eval options {
52}
53
54# option
55# This is an accessor for Portfile options.  Targets may use
56# this in the same style as the standard Tcl "set" procedure.
57#   name  - the name of the option to read or write
58#   value - an optional value to assign to the option
59
60proc option {name args} {
61    # XXX: right now we just transparently use globals
62    # eventually this will need to bridge the options between
63    # the Portfile's interpreter and the target's interpreters.
64    global $name
65    if {[llength $args] > 0} {
66        ui_debug "setting option $name to $args"
67        set $name [lindex $args 0]
68    }
69    return [set $name]
70}
71
72# exists
73# This is an accessor for Portfile options.  Targets may use
74# this procedure to test for the existence of a Portfile option.
75#   name - the name of the option to test for existence
76
77proc exists {name} {
78    # XXX: right now we just transparently use globals
79    # eventually this will need to bridge the options between
80    # the Portfile's interpreter and the target's interpreters.
81    global $name
82    return [info exists $name]
83}
84
85##
86# Handle an option
87#
88# @param option name of the option
89# @param args arguments
90proc handle_option {option args} {
91    global $option user_options option_procs
92
93    if {![info exists user_options($option)]} {
94        set $option $args
95    }
96}
97
98##
99# Handle option-append
100#
101# @param option name of the option
102# @param args arguments
103proc handle_option-append {option args} {
104    global $option user_options option_procs
105
106    if {![info exists user_options($option)]} {
107        if {[info exists $option]} {
108            set $option [concat [set $option] $args]
109        } else {
110            set $option $args
111        }
112    }
113}
114
115##
116# Handle option-delete
117#
118# @param option name of the option
119# @param args arguments
120proc handle_option-delete {option args} {
121    global $option user_options option_procs
122
123    if {![info exists user_options($option)] && [info exists $option]} {
124        set temp [set $option]
125        foreach val $args {
126            set temp [ldelete $temp $val]
127        }
128        set $option $temp
129    }
130}
131
132# options
133# Exports options in an array as externally callable procedures
134# Thus, "options name date" would create procedures named "name"
135# and "date" that set global variables "name" and "date", respectively
136# When an option is modified in any way, options::$option is called,
137# if it exists
138# Arguments: <list of options>
139proc options {args} {
140    foreach option $args {
141        interp alias {} $option {} handle_option $option
142        interp alias {} $option-append {} handle_option-append $option
143        interp alias {} $option-delete {} handle_option-delete $option
144    }
145}
146
147##
148# Export options into PortInfo
149#
150# @param option the name of the option
151# @param action set or delete
152# @param value the value to be set, defaults to an empty string
153proc options::export {option action {value ""}} {
154    global $option PortInfo
155    switch $action {
156        set {
157            set PortInfo($option) $value
158        }
159        delete {
160            unset PortInfo($option)
161        }
162    }
163}
164
165##
166# Export multiple options
167#
168# @param args list of ports to be exported
169proc options_export {args} {
170    foreach option $args {
171        option_proc $option options::export
172    }
173}
174
175##
176# Registers a proc to be called when an option is changed
177#
178# @param option the name of the option
179# @param args name of proc (and additional arguments)
180proc option_proc {option args} {
181    global option_procs $option
182    if {[info exists option_procs($option)]} {
183        set option_procs($option) [concat $option_procs($option) $args]
184        # we're already tracing
185    } else {
186        set option_procs($option) $args
187        trace add variable $option {read write unset} option_proc_trace
188    }
189}
190
191# option_proc_trace
192# trace handler for option reads. Calls option procedures with correct arguments.
193proc option_proc_trace {optionName index op} {
194    global option_procs
195    upvar $optionName $optionName
196    switch $op {
197        write {
198            foreach p $option_procs($optionName) {
199                $p $optionName set [set $optionName]
200            }
201        }
202        read {
203            foreach p $option_procs($optionName) {
204                $p $optionName read
205            }
206        }
207        unset {
208            foreach p $option_procs($optionName) {
209                if {[catch {$p $optionName delete} result]} {
210                    ui_debug "error during unset trace ($p): $result\n$::errorInfo"
211                }
212            }
213            trace add variable $optionName {read write unset} option_proc_trace
214        }
215    }
216}
217
218# commands
219# Accepts a list of arguments, of which several options are created
220# and used to form a standard set of command options.
221proc commands {args} {
222    foreach option $args {
223        options use_${option} ${option}.dir ${option}.pre_args ${option}.args ${option}.post_args ${option}.env ${option}.type ${option}.cmd
224    }
225}
226
227# Given a command name, assemble a command string
228# composed of the command options.
229proc command_string {command} {
230    global ${command}.dir ${command}.pre_args ${command}.args ${command}.post_args ${command}.cmd
231   
232    if {[info exists ${command}.dir]} {
233        append cmdstring "cd \"[set ${command}.dir]\" &&"
234    }
235   
236    if {[info exists ${command}.cmd]} {
237        foreach string [set ${command}.cmd] {
238            append cmdstring " $string"
239        }
240    } else {
241        append cmdstring " ${command}"
242    }
243
244    foreach var "${command}.pre_args ${command}.args ${command}.post_args" {
245        if {[info exists $var]} {
246            foreach string [set ${var}] {
247                append cmdstring " ${string}"
248            }
249        }
250    }
251
252    ui_debug "Assembled command: '$cmdstring'"
253    return $cmdstring
254}
255
256# Given a command name, execute it with the options.
257# command_exec command [-notty] [command_prefix [command_suffix]]
258# command           name of the command
259# command_prefix    additional command prefix (typically pipe command)
260# command_suffix    additional command suffix (typically redirection)
261proc command_exec {command args} {
262    global ${command}.env ${command}.env_array env
263    set notty 0
264    set command_prefix ""
265    set command_suffix ""
266
267    if {[llength $args] > 0} {
268        if {[lindex $args 0] == "-notty"} {
269            set notty 1
270            set args [lrange $args 1 end]
271        }
272
273        if {[llength $args] > 0} {
274            set command_prefix [lindex $args 0]
275            if {[llength $args] > 1} {
276                set command_suffix [lindex $args 1]
277            }
278        }
279    }
280   
281    # Set the environment.
282    # If the array doesn't exist, we create it with the value
283    # coming from ${command}.env
284    # Otherwise, it means the caller actually played with the environment
285    # array already (e.g. configure flags).
286    if {![array exists ${command}.env_array]} {
287        parse_environment ${command}
288    }
289    if {[option macosx_deployment_target] ne ""} {
290        set ${command}.env_array(MACOSX_DEPLOYMENT_TARGET) [option macosx_deployment_target]
291    }
292   
293    # Debug that.
294    ui_debug "Environment: [environment_array_to_string ${command}.env_array]"
295
296    # Get the command string.
297    set cmdstring [command_string ${command}]
298   
299    # Call this command.
300    # TODO: move that to the system native call?
301    # Save the environment.
302    array set saved_env [array get env]
303    # Set the overriden variables from the portfile.
304    array set env [array get ${command}.env_array]
305    # Call the command.
306    set fullcmdstring "$command_prefix $cmdstring $command_suffix"
307    if {$notty} {
308        set code [catch {system -notty $fullcmdstring} result]
309    } else {
310        set code [catch {system $fullcmdstring} result]
311    }
312    # Unset the command array until next time.
313    array unset ${command}.env_array
314   
315    # Restore the environment.
316    array unset env *
317    unsetenv *
318    array set env [array get saved_env]
319
320    # Return as if system had been called directly.
321    return -code $code $result
322}
323
324# default
325# Sets a variable to the supplied default if it does not exist,
326# and adds a variable trace. The variable traces allows for delayed
327# variable and command expansion in the variable's default value.
328proc default {option val} {
329    global $option option_defaults
330    if {[info exists option_defaults($option)]} {
331        ui_debug "Re-registering default for $option"
332        # remove the old trace
333        trace vdelete $option rwu default_check
334    } else {
335        # If option is already set and we did not set it
336        # do not reset the value
337        if {[info exists $option]} {
338            return
339        }
340    }
341    set option_defaults($option) $val
342    set $option $val
343    trace variable $option rwu default_check
344}
345
346# default_check
347# trace handler to provide delayed variable & command expansion
348# for default variable values
349proc default_check {optionName index op} {
350    global option_defaults $optionName
351    switch $op {
352        w {
353            unset option_defaults($optionName)
354            trace vdelete $optionName rwu default_check
355            return
356        }
357        r {
358            upvar $optionName option
359            uplevel #0 set $optionName $option_defaults($optionName)
360            return
361        }
362        u {
363            unset option_defaults($optionName)
364            trace vdelete $optionName rwu default_check
365            return
366        }
367    }
368}
369
370# variant <provides> [<provides> ...] [requires <requires> [<requires>]]
371# Portfile level procedure to provide support for declaring variants
372proc variant {args} {
373    global all_variants PortInfo porturl
374
375    # Each key in PortInfo(vinfo) maps to an array which contains the
376    # following keys:
377    #   * conflicts
378    #   * description: This key's mapping is duplicated in
379    #                  PortInfo(variant_desc) for backward compatibility
380    #                  reasons (specifically 1.7.0's format of PortIndex).
381    #   * is_default: This key exists iff the variant is a default variant.
382    #   * requires
383    if {![info exists PortInfo(vinfo)]} {
384        set PortInfo(vinfo) {}
385    }
386    array set vinfo $PortInfo(vinfo)
387
388    set len [llength $args]
389    set code [lindex $args end]
390    set args [lrange $args 0 [expr $len - 2]]
391   
392    set ditem [variant_new "temp-variant"]
393   
394    # mode indicates what the arg is interpreted as.
395    # possible mode keywords are: requires, conflicts, provides
396    # The default mode is provides.  Arguments are added to the
397    # most recently specified mode (left to right).
398    set mode "provides"
399    foreach arg $args {
400        switch -exact $arg {
401            description -
402            provides -
403            requires -
404            conflicts { set mode $arg }
405            default { ditem_append $ditem $mode $arg }     
406        }
407    }
408    ditem_key $ditem name "[join [ditem_key $ditem provides] -]"
409
410    # make a user procedure named variant-blah-blah
411    # we will call this procedure during variant-run
412    makeuserproc "variant-[ditem_key $ditem name]" \{$code\}
413   
414    # Export provided variant to PortInfo
415    # (don't list it twice if the variant was already defined, which can happen
416    # with universal or group code).
417    set variant_provides [ditem_key $ditem provides]
418    if {[variant_exists $variant_provides]} {
419        # This variant was already defined. Remove it from the dlist.
420        variant_remove_ditem $variant_provides
421    } else {
422        # Create an array to contain the variant's information.
423        if {![info exists vinfo($variant_provides)]} {
424            set vinfo($variant_provides) {}
425        }
426        array set variant $vinfo($variant_provides)
427   
428        # Set conflicts.
429        set vconflicts [join [lsort [ditem_key $ditem conflicts]]]
430        if {$vconflicts ne ""} {
431            array set variant [list conflicts $vconflicts]
432        }
433
434        lappend PortInfo(variants) $variant_provides
435        set vdesc [join [ditem_key $ditem description]]
436
437        # read global variant description, if none given
438        if {$vdesc == ""} {
439            set vdesc [variant_desc $porturl $variant_provides]
440        }
441
442        # Set description.
443        if {$vdesc ne ""} {
444            array set variant [list description $vdesc]
445            # XXX: The following line should be removed after 1.8.0 is
446            #      released.
447            lappend PortInfo(variant_desc) $variant_provides $vdesc
448        }
449
450        # Set requires.
451        set vrequires [join [lsort [ditem_key $ditem requires]]]
452        if {$vrequires ne ""} {
453            array set variant [list requires $vrequires]
454        }
455    }
456
457    # Add the variant (back) to PortInfo(vinfo).
458    array set vinfo [list $variant_provides [array get variant]]
459    set PortInfo(vinfo) [array get vinfo]
460
461    # Finally append the ditem to the dlist.
462    lappend all_variants $ditem
463}
464
465# variant_isset name
466# Returns 1 if variant name selected, otherwise 0
467proc variant_isset {name} {
468    global variations
469   
470    if {[info exists variations($name)] && $variations($name) == "+"} {
471        return 1
472    }
473    return 0
474}
475
476# variant_set name
477# Sets variant to run for current portfile
478proc variant_set {name} {
479    global variations
480    set variations($name) +
481}
482
483# variant_unset name
484# Clear variant for current portfile
485proc variant_unset {name} {
486    global variations
487   
488    set variations($name) -
489}
490
491# variant_undef name
492# Undefine a variant for the current portfile.
493proc variant_undef {name} {
494    global variations PortInfo
495   
496    # Remove it from the list of selected variations.
497    array unset variations $name
498
499    # Remove the variant from the portinfo.
500    if {[info exists PortInfo(variants)]} {
501        set variant_index [lsearch -exact $PortInfo(variants) $name]
502        if {$variant_index >= 0} {
503            set new_list [lreplace $PortInfo(variants) $variant_index $variant_index]
504            if {"$new_list" == {}} {
505                unset PortInfo(variants)
506            } else {
507                set PortInfo(variants) $new_list
508            }
509        }
510    }
511   
512    # And from the dlist.
513    variant_remove_ditem $name
514}
515
516# variant_remove_ditem name
517# Remove variant name's ditem from the all_variants dlist
518proc variant_remove_ditem {name} {
519    global all_variants
520    set item_index 0
521    foreach variant_item $all_variants {
522        set item_provides [ditem_key $variant_item provides]
523        if {$item_provides == $name} {
524            set all_variants [lreplace $all_variants $item_index $item_index]
525            break
526        }
527       
528        incr item_index
529    }
530}
531
532# variant_exists name
533# determine if a variant exists.
534proc variant_exists {name} {
535    global PortInfo
536    if {[info exists PortInfo(variants)] &&
537      [lsearch -exact $PortInfo(variants) $name] >= 0} {
538        return 1
539    }
540
541    return 0
542}
543
544##
545# Get description for a variant from global descriptions file
546#
547# @param porturl url to a port
548# @param variant name
549# @return description from descriptions file or an empty string
550proc variant_desc {porturl variant} {
551    global variant_descs_global
552
553    set descfile [getportresourcepath $porturl "port1.0/variant_descriptions.conf"]
554    if {![info exists variant_descs_global($descfile)]} {
555        set variant_descs_global($descfile) yes
556
557        if {[file exists $descfile]} {
558            if {[catch {set fd [open $descfile r]} err]} {
559                ui_warn "Could not open global variant description file: $err"
560                return ""
561            }
562            set lineno 0
563            while {[gets $fd line] >= 0} {
564                incr lineno
565                set name [lindex $line 0]
566                set desc [lindex $line 1]
567                if {$name != "" && $desc != ""} {
568                    set variant_descs_global(${descfile}_$name) $desc
569                } else {
570                    ui_warn "Invalid variant description in $descfile at line $lineno"
571                }
572            }
573            close $fd
574        }
575    }
576
577    if {[info exists variant_descs_global(${descfile}_${variant})]} {
578        return $variant_descs_global(${descfile}_${variant})
579    } else {
580        return ""
581    }
582}
583
584# platform <os> [<release>] [<arch>]
585# Portfile level procedure to provide support for declaring platform-specifics
586# Basically, just wrap 'variant', so that Portfiles' platform declarations can
587# be more readable, and support arch and version specifics
588proc platform {args} {
589    global all_variants PortInfo os.platform os.arch os.version os.major
590   
591    set len [llength $args]
592    set code [lindex $args end]
593    set os [lindex $args 0]
594    set args [lrange $args 1 [expr $len - 2]]
595   
596    set ditem [variant_new "temp-variant"]
597   
598    foreach arg $args {
599        if {[regexp {(^[0-9]+$)} $arg match result]} {
600            set release $result
601        } elseif {[regexp {([a-zA-Z0-9]*)} $arg match result]} {
602            set arch $result
603        }
604    }
605   
606    # Add the variant for this platform
607    set platform $os
608    if {[info exists release]} { set platform ${platform}_${release} }
609    if {[info exists arch]} { set platform ${platform}_${arch} }
610   
611    # Pick up a unique name.
612    if {[variant_exists $platform]} {
613        set suffix 1
614        while {[variant_exists "$platform-$suffix"]} {
615            incr suffix
616        }
617       
618        set platform "$platform-$suffix"
619    }
620    variant $platform $code
621   
622    # Set the variant if this platform matches the platform we're on
623    set matches 1
624    if {[info exists os.platform] && ${os.platform} == $os} {
625        set sel_platform $os
626        if {[info exists os.major] && [info exists release]} {
627            if {${os.major} == $release } {
628                set sel_platform ${sel_platform}_${release}
629            } else {
630                set matches 0
631            }
632        }
633        if {$matches == 1 && [info exists arch] && [info exists os.arch]} {
634            if {${os.arch} == $arch} {
635                set sel_platform ${sel_platform}_${arch}
636            } else {
637                set matches 0
638            }
639        }
640        if {$matches == 1} {
641            variant_set $sel_platform
642        }
643    }
644}
645
646########### Environment utility functions ###########
647
648# Parse the environment string of a command, storing the values into the
649# associated environment array.
650proc parse_environment {command} {
651    global ${command}.env ${command}.env_array
652
653    if {[info exists ${command}.env]} {
654        # Flatten the environment string.
655        set the_environment [join [set ${command}.env]]
656   
657        while {[regexp "^(?: *)(\[^= \]+)=(\"|'|)(\[^\"'\]*?)\\2(?: +|$)(.*)$" ${the_environment} matchVar key delimiter value remaining]} {
658            set the_environment ${remaining}
659            set ${command}.env_array(${key}) ${value}
660        }
661    } else {
662        array set ${command}.env_array {}
663    }
664}
665
666# Append to the value in the parsed environment.
667# Leave the environment untouched if the value is empty.
668proc append_to_environment_value {command key value} {
669    global ${command}.env_array
670
671    if {[string length $value] == 0} {
672        return
673    }
674
675    # Parse out any delimiter.
676    set append_value $value
677    if {[regexp {^("|')(.*)\1$} $append_value matchVar append_delim matchedValue]} {
678        set append_value $matchedValue
679    }
680
681    if {[info exists ${command}.env_array($key)]} {
682        set original_value [set ${command}.env_array($key)]
683        set ${command}.env_array($key) "${original_value} ${append_value}"
684    } else {
685        set ${command}.env_array($key) $append_value
686    }
687}
688
689# Append several items to a value in the parsed environment.
690proc append_list_to_environment_value {command key vallist} {
691    foreach {value} $vallist {
692        append_to_environment_value ${command} $key $value
693    }
694}
695
696# Build the environment as a string.
697# Remark: this method is only used for debugging purposes.
698proc environment_array_to_string {environment_array} {
699    upvar 1 ${environment_array} env_array
700   
701    set theString ""
702    foreach {key value} [array get env_array] {
703        if {$theString == ""} {
704            set theString "$key='$value'"
705        } else {
706            set theString "${theString} $key='$value'"
707        }
708    }
709   
710    return $theString
711}
712
713########### Distname utility functions ###########
714
715# Given a distribution file name, return the appended tag
716# Example: getdisttag distfile.tar.gz:tag1 returns "tag1"
717# / isn't included in the regexp, thus allowing port specification in URLs.
718proc getdisttag {name} {
719    if {[regexp {.+:([0-9A-Za-z_-]+)$} $name match tag]} {
720        return $tag
721    } else {
722        return ""
723    }
724}
725
726# Given a distribution file name, return the name without an attached tag
727# Example : getdistname distfile.tar.gz:tag1 returns "distfile.tar.gz"
728# / isn't included in the regexp, thus allowing port specification in URLs.
729proc getdistname {name} {
730    regexp {(.+):[0-9A-Za-z_-]+$} $name match name
731    return $name
732}
733
734
735########### Misc Utility Functions ###########
736
737# tbool (testbool)
738# If the variable exists in the calling procedure's namespace
<