source: branches/gsoc13-tests/src/package1.0/portpkg.tcl @ 139170

Last change on this file since 139170 was 111323, checked in by marius@…, 7 years ago

Merge from trunk.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 20.9 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# portpkg.tcl
3# $Id: portpkg.tcl 111323 2013-09-18 23:11:02Z marius@macports.org $
4#
5# Copyright (c) 2005, 2007 - 2013 The MacPorts Project
6# Copyright (c) 2002 - 2003 Apple Inc.
7# All rights reserved.
8#
9# Redistribution and use in source and binary forms, with or without
10# modification, are permitted provided that the following conditions
11# are met:
12# 1. Redistributions of source code must retain the above copyright
13#    notice, this list of conditions and the following disclaimer.
14# 2. Redistributions in binary form must reproduce the above copyright
15#    notice, this list of conditions and the following disclaimer in the
16#    documentation and/or other materials provided with the distribution.
17# 3. Neither the name of Apple Inc. nor the names of its contributors
18#    may be used to endorse or promote products derived from this software
19#    without specific prior written permission.
20#
21# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
25# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31# POSSIBILITY OF SUCH DAMAGE.
32#
33
34package provide portpkg 1.0
35package require portutil 1.0
36
37set org.macports.pkg [target_new org.macports.pkg portpkg::pkg_main]
38target_runtype ${org.macports.pkg} always
39target_provides ${org.macports.pkg} pkg
40target_requires ${org.macports.pkg} archivefetch unarchive destroot
41target_prerun ${org.macports.pkg} portpkg::pkg_start
42
43namespace eval portpkg {
44}
45
46# define options
47options package.type package.destpath package.flat package.resources package.scripts
48
49# Set defaults
50default package.destpath {${workpath}}
51default package.resources {${workpath}/pkg_resources}
52default package.scripts  {${workpath}/pkg_scripts}
53# Need productbuild to make flat packages really work
54default package.flat     {[expr [vercmp $macosx_deployment_target 10.6] >= 0]}
55
56set_ui_prefix
57
58proc portpkg::pkg_start {args} {
59    global packagemaker_path portpkg::packagemaker \
60           portpkg::language xcodeversion portpath porturl \
61           package.resources package.scripts package.flat \
62           subport epoch version revision description long_description \
63           homepage workpath os.major
64
65    if {![info exists packagemaker_path]} {
66        if {[vercmp $xcodeversion 4.3] >= 0} {
67            set packagemaker_path /Applications/PackageMaker.app
68            if {![file exists $packagemaker_path]} {
69                ui_warn "PackageMaker.app not found; you may need to install it or set packagemaker_path in macports.conf"
70            }
71        } else {
72            set packagemaker_path "[option developer_dir]/Applications/Utilities/PackageMaker.app"
73        }
74    }
75    set packagemaker "${packagemaker_path}/Contents/MacOS/PackageMaker"
76
77    set language "English"
78    file mkdir "${package.resources}/${language}.lproj"
79    file attributes "${package.resources}/${language}.lproj" -permissions 0755
80    file mkdir ${package.scripts}
81    file attributes ${package.scripts} -permissions 0755
82
83    # long_description, description, or homepage may not exist
84    foreach variable {long_description description homepage} {
85        if {![info exists $variable]} {
86            set pkg_$variable ""
87        } else {
88            set pkg_$variable [set $variable]
89        }
90    }
91    write_welcome_html ${package.resources}/${language}.lproj/Welcome.html $subport $epoch $version $revision $pkg_long_description $pkg_description $pkg_homepage
92    file copy -force -- [getportresourcepath $porturl "port1.0/package/background.tiff"] ${package.resources}/${language}.lproj/background.tiff
93
94    if {${package.flat} && ${os.major} >= 9} {
95        write_distribution "${workpath}/Distribution" $subport $epoch $version $revision
96    }
97}
98
99proc portpkg::pkg_main {args} {
100    global subport epoch version revision UI_PREFIX
101
102    if {[getuid] == 0 && [geteuid] != 0} {
103        elevateToRoot "pkg"
104    }
105
106    return [package_pkg $subport $epoch $version $revision]
107}
108
109proc portpkg::package_pkg {portname portepoch portversion portrevision} {
110    global UI_PREFIX portdbpath destpath workpath prefix description \
111    package.flat package.destpath portpath os.version os.major \
112    package.resources package.scripts portpkg::packagemaker \
113    pkg_post_unarchive_deletions portpkg::language
114
115    set portepoch_namestr ""
116    if {${portepoch} != "0"} {
117        set portepoch_namestr "${portepoch}_"
118    }
119    set portrevision_namestr ""
120    if {${portrevision} != "0"} {
121        set portrevision_namestr "_${portrevision}"
122    }
123
124    set pkgpath "${package.destpath}/${portname}-${portepoch_namestr}${portversion}${portrevision_namestr}.pkg"
125
126    ui_msg "$UI_PREFIX [format [msgcat::mc "Creating pkg for %s version %s_%s_%s at %s"] ${portname} ${portepoch} ${portversion} ${portrevision} ${pkgpath}]"
127
128    if {[file readable $pkgpath] && ([file mtime ${pkgpath}] >= [file mtime ${portpath}/Portfile])} {
129        ui_msg "$UI_PREFIX [format [msgcat::mc "Package for %s version %s_%s_%s at %s is up-to-date"] ${portname} ${portepoch} ${portversion} ${portrevision} ${pkgpath}]"
130        return 0
131    }
132
133    foreach dir {etc var tmp} {
134        if ([file exists "${destpath}/$dir"]) {
135            # certain toplevel directories really are symlinks. leaving them as directories make pax lose the symlinks. that's bad.
136            file mkdir "${destpath}/private/${dir}"
137            eval file rename [glob ${destpath}/${dir}/*] "${destpath}/private/${dir}"
138            delete "${destpath}/${dir}"
139        }
140    }
141
142    if {[info exists pkg_post_unarchive_deletions]} {
143        foreach rmfile ${pkg_post_unarchive_deletions} {
144            set full_rmfile "${destpath}${prefix}/${rmfile}"
145            if {[file exists "${full_rmfile}"]} {
146                delete "${full_rmfile}"
147            }
148        }
149    }
150
151    if ([file exists "$packagemaker"]) {
152
153        ui_debug "Calling $packagemaker for $portname pkg"
154        if {${os.major} >= 9} {
155            if {${package.flat}} {
156                set pkgtarget "10.5"
157                set pkgresources " --scripts ${package.scripts}"
158                set infofile "${workpath}/PackageInfo"
159                write_package_info $infofile
160            } else {
161                set pkgtarget "10.3"
162                set pkgresources " --resources ${package.resources} --title \"$portname-$portversion\""
163                set infofile "${workpath}/Info.plist"
164                write_info_plist $infofile $portname $portversion $portrevision
165            }
166            set cmdline "PMResourceLocale=${language} $packagemaker --root ${destpath} --out ${pkgpath} ${pkgresources} --info $infofile --target $pkgtarget --domain system --id org.macports.$portname"
167            if {${os.major} >= 10} {
168                set v [mp_version_to_apple_version $portepoch $portversion $portrevision]
169                append cmdline " --version $v"
170                append cmdline " --no-relocate"
171            } else {
172                # 10.5 Leopard does not use current language, manually specify
173                append cmdline " -AppleLanguages \"(${language})\""
174            }
175            ui_debug "Running command line: $cmdline"
176            system $cmdline
177
178            if {${package.flat} && ${os.major} >= 10} {
179                # the package we just built is just a component
180                set componentpath "[file rootname ${pkgpath}]-component.pkg"
181                file rename -force ${pkgpath} ${componentpath}
182                # Generate a distribution
183                set productbuild [findBinary productbuild]
184                set cmdline "$productbuild --resources ${package.resources} --identifier org.macports.${portname} --distribution ${workpath}/Distribution --package-path ${package.destpath} ${pkgpath}"
185                ui_debug "Running command line: $cmdline"
186                system $cmdline
187            }
188        } else {
189            write_info_plist ${workpath}/Info.plist $portname $portversion $portrevision
190            write_description_plist ${workpath}/Description.plist $portname $portversion $description
191            system "$packagemaker -build -f ${destpath} -p ${pkgpath} -r ${package.resources} -i ${workpath}/Info.plist -d ${workpath}/Description.plist"
192        }
193
194        file delete ${workpath}/Info.plist \
195                    ${workpath}/PackageInfo \
196                    ${workpath}/Distribution \
197                    ${workpath}/Description.plist
198        file delete -force ${package.resources} \
199                           ${package.scripts}
200
201    } else {
202
203        file mkdir ${pkgpath}/Contents/Resources
204        foreach f [glob -directory ${package.resources} *] {
205            file copy -force -- $f ${pkgpath}/Contents/Resources
206        }
207
208        write_PkgInfo ${pkgpath}/Contents/PkgInfo
209        write_info_plist ${pkgpath}/Contents/Info.plist $portname $portversion $portrevision
210
211        system "[findBinary mkbom $portutil::autoconf::mkbom_path] ${destpath} ${pkgpath}/Contents/Archive.bom"
212        system "cd ${destpath} && [findBinary pax $portutil::autoconf::pax_path] -x cpio -w -z . > ${pkgpath}/Contents/Archive.pax.gz"
213
214        write_description_plist ${pkgpath}/Contents/Resources/Description.plist $portname $portversion $description
215        write_sizes_file ${pkgpath}/Contents/Resources/Archive.sizes ${pkgpath} ${destpath}
216
217    }
218
219    foreach dir {etc var tmp} {
220        if ([file exists "${destpath}/private/$dir"]) {
221            # restore any directories that were moved, to avoid confusing the rest of the ports system.
222            file rename ${destpath}/private/$dir ${destpath}/$dir
223        }
224    }
225    catch {file delete ${destpath}/private}
226
227    return 0
228}
229
230proc portpkg::write_PkgInfo {infofile} {
231    set infofd [open ${infofile} w+]
232    puts $infofd "pmkrpkg1"
233    close $infofd
234}
235
236proc portpkg::xml_escape {s} {
237    regsub -all {&} $s {\&} s
238    regsub -all {<} $s {\&lt;} s
239    regsub -all {>} $s {\&gt;} s
240    return $s
241}
242
243proc portpkg::write_info_plist {infofile portname portversion portrevision} {
244    set portname [xml_escape $portname]
245    set portversion [xml_escape $portversion]
246    set portrevision [xml_escape $portrevision]
247
248    set infofd [open ${infofile} w+]
249    puts $infofd {<?xml version="1.0" encoding="UTF-8"?>
250<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
251<plist version="1.0">
252    }
253    puts $infofd "<dict>
254    <key>CFBundleGetInfoString</key>
255    <string>${portname} ${portversion}</string>
256    <key>CFBundleIdentifier</key>
257    <string>org.macports.${portname}</string>
258    <key>CFBundleName</key>
259    <string>${portname}</string>
260    <key>CFBundleShortVersionString</key>
261    <string>${portversion}</string>
262    <key>IFMajorVersion</key>
263    <integer>${portrevision}</integer>
264    <key>IFMinorVersion</key>
265    <integer>0</integer>
266    <key>IFPkgFlagAllowBackRev</key>
267    <true/>
268    <key>IFPkgFlagAuthorizationAction</key>
269    <string>RootAuthorization</string>
270    <key>IFPkgFlagDefaultLocation</key>
271    <string>/</string>
272    <key>IFPkgFlagInstallFat</key>
273    <false/>
274    <key>IFPkgFlagIsRequired</key>
275    <false/>
276    <key>IFPkgFlagRelocatable</key>
277    <false/>
278    <key>IFPkgFlagRestartAction</key>
279    <string>NoRestart</string>
280    <key>IFPkgFlagRootVolumeOnly</key>
281    <true/>
282    <key>IFPkgFlagUpdateInstalledLanguages</key>
283    <false/>
284    <key>IFPkgFormatVersion</key>
285    <real>0.10000000149011612</real>
286</dict>
287</plist>"
288    close $infofd
289}
290
291proc portpkg::write_description_plist {infofile portname portversion description} {
292    set portname [xml_escape $portname]
293    set portversion [xml_escape $portversion]
294    set description [xml_escape $description]
295
296    set infofd [open ${infofile} w+]
297    puts $infofd {<?xml version="1.0" encoding="UTF-8"?>
298<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
299<plist version="1.0">
300    }
301    puts $infofd "<dict>
302    <key>IFPkgDescriptionDeleteWarning</key>
303    <string></string>
304    <key>IFPkgDescriptionDescription</key>
305    <string>${description}</string>
306    <key>IFPkgDescriptionTitle</key>
307    <string>${portname}</string>
308    <key>IFPkgDescriptionVersion</key>
309    <string>${portversion}</string>
310</dict>
311</plist>"
312    close $infofd
313}
314
315proc portpkg::write_welcome_html {filename portname portepoch portversion portrevision long_description description homepage} {
316    set fd [open ${filename} w+]
317    if {$long_description == ""} {
318        set long_description $description
319    }
320
321    set portname [xml_escape $portname]
322    if {$portepoch != "0"} {
323        set portepoch [xml_escape $portepoch]
324        set portepoch_str "${portepoch}_"
325    } else {
326        set portepoch ""
327        set portepoch_str ""
328    }
329    set portversion [xml_escape $portversion]
330    if {$portrevision != "0"} {
331        set portrevision [xml_escape $portrevision]
332        set portrevision_str "_${portrevision}"
333    } else {
334        set portrevision ""
335        set portrevision_str ""
336    }
337    set long_description [xml_escape $long_description]
338    set description [xml_escape $description]
339    set homepage [xml_escape $homepage]
340
341    puts $fd "
342<html lang=\"en\">
343<head>
344    <meta http-equiv=\"content-type\" content=\"text/html; charset=iso-8859-1\">
345    <title>Install ${portname}</title>
346</head>
347<body>
348<font face=\"Helvetica\"><b>Welcome to the ${portname} for Mac OS X Installer</b></font>
349<p>
350<font face=\"Helvetica\">${long_description}</font>
351<p>"
352
353    if {$homepage != ""} {
354        puts $fd "<font face=\"Helvetica\"><a href=\"${homepage}\">${homepage}</a></font><p>"
355    }
356
357    puts $fd "<font face=\"Helvetica\">This installer guides you through the steps necessary to install ${portname} ${portepoch_str}${portversion}${portrevision_str} for Mac OS X. To get started, click Continue.</font>
358</body>
359</html>"
360
361    close $fd
362}
363
364proc portpkg::write_sizes_file {sizesfile pkgpath destpath} {
365
366    if {[catch {set numFiles [llength [split [exec [findBinary lsbom $portutil::autoconf::lsbom_path] -s ${pkgpath}/Contents/Archive.bom] "\n"]]} result]} {
367        return -code error [format [msgcat::mc "Reading package bom failed: %s"] $result]
368    }
369    if {[catch {set compressedSize [expr [dirSize ${pkgpath}] / 1024]} result]} {
370        return -code error [format [msgcat::mc "Error determining compressed size: %s"] $result]
371    }
372    if {[catch {set installedSize [expr [dirSize ${destpath}] / 1024]} result]} {
373        return -code error [format [msgcat::mc "Error determining installed size: %s"] $result]
374    }
375    if {[catch {set infoSize [file size ${pkgpath}/Contents/Info.plist]} result]} {
376       return -code error [format [msgcat::mc "Error determining plist file size: %s"] $result]
377    }
378    if {[catch {set bomSize [file size ${pkgpath}/Contents/Archive.bom]} result]} {
379        return -code error [format [msgcat::mc "Error determining bom file size: %s"] $result]
380    }
381    incr installedSize $infoSize
382    incr installedSize $bomSize
383
384    set fd [open ${sizesfile} w+]
385    puts $fd "NumFiles $numFiles
386InstalledSize $installedSize
387CompressedSize $compressedSize"
388    close $fd
389}
390
391proc portpkg::write_package_info {infofile} {
392    set infofd [open ${infofile} w+]
393    puts $infofd "
394<pkg-info install-location=\"/\" relocatable=\"false\" auth=\"root\">
395</pkg-info>"
396    close $infofd
397}
398
399proc portpkg::write_distribution {dfile portname portepoch portversion portrevision} {
400    global macosx_deployment_target
401    set portname [xml_escape $portname]
402    if {$portepoch != "0"} {
403        set portepoch [xml_escape $portepoch]
404        set portepoch_str "${portepoch}_"
405    } else {
406        set portepoch ""
407        set portepoch_str ""
408    }
409    set portversion [xml_escape $portversion]
410    if {$portrevision != "0"} {
411        set portrevision [xml_escape $portrevision]
412        set portrevision_str "_${portrevision}"
413    } else {
414        set portrevision ""
415        set portrevision_str ""
416    }
417    set dfd [open $dfile w+]
418    puts $dfd "<?xml version=\"1.0\" encoding=\"utf-8\"?>
419<installer-gui-script minSpecVersion=\"1\">
420    <title>${portname}</title>
421    <options customize=\"never\"/>
422    <allowed-os-versions><os-version min=\"${macosx_deployment_target}\"/></allowed-os-versions>
423    <background file=\"background.tiff\" mime-type=\"image/tiff\" alignment=\"bottomleft\" scaling=\"none\"/>
424    <welcome mime-type=\"text/html\" file=\"Welcome.html\"/>
425    <choices-outline>
426        <line choice=\"default\">
427            <line choice=\"org.macports.${portname}\"/>
428        </line>
429    </choices-outline>
430    <choice id=\"default\"/>
431    <choice id=\"org.macports.${portname}\" visible=\"false\">
432        <pkg-ref id=\"org.macports.${portname}\"/>
433    </choice>
434    <pkg-ref id=\"org.macports.${portname}\">${portname}-${portepoch_str}${portversion}${portrevision_str}-component.pkg</pkg-ref>
435</installer-gui-script>
436"
437    close $dfd
438}
439
440# To create Apple packages, Apple version numbers consist of three
441# period separated integers [1][2].  Munki supports any number of
442# integers [3], so incorporate the port epoch, version and revision
443# numbers in the Apple package version number so that Munki can do
444# upgrades.  The Apple package number consists of the port epoch
445# number followed by the port version number followed by the port
446# revision number.
447#
448# Munki also requires that version numbers only consist of integers
449# and periods.  So replace all non-periods and non-digits in the
450# version number with periods so that any digits following the
451# non-digits can properly version the package.
452#
453# There is an edge case when upstream releases a new version which
454# adds an additional integer to its version number and the Portfile's
455# revision number is reset to 0.  For example, aspell epoch 0,
456# upstream 0.60.6, revision 4 was updated to epoch 0, upstream
457# 0.60.6.1, revision 0, which maps to 0.60.6.4 and 0.60.6.1.0
458# respectively, but the new Apple package version number is less than
459# the old one.  To handle this, all upstream version numbers are
460# mapped to seven period separated integers, appending 0 as necessary.
461# Six was the largest number of integers in all upstream version
462# numbers as of January 2013 [4], so add one to make space for
463# trailing [a-zA-Z] in the upstream version number.  This generates a
464# fixed format version number that will correctly upgrade the package,
465# e.g. 0.60.6.4.0.0.0.0.4 and 0.60.6.1.0.0.0.1 for aspell.
466#
467# [1] https://developer.apple.com/library/mac/#documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/TP40009249-SW1
468# [2] https://developer.apple.com/library/mac/#documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/TP40009249-SW1
469# [3] https://groups.google.com/d/msg/munki-dev/-DCERUz6rrM/zMbY6iimIGwJ
470# [4] http://lists.macosforge.org/pipermail/macports-dev/2013-January/021477.html
471proc portpkg::mp_version_to_apple_version {portepoch portversion portrevision} {
472    # Assume that portepoch and portrevision are non-negative integers
473    # so they do not need to be specially handled like the upstream
474    # version number.
475    set v $portversion
476
477    # Replace all non-period and non-digit characters with a period.
478    regsub -all -- {[^.0-9]+} $v . v
479
480    # Replace two or more consecutive periods with a single period.
481    regsub -all -- {[.]+} $v . v
482
483    # Trim trailing periods.
484    regsub -- {[.]+$} $v {} v
485
486    # Split the string into a list of integers.
487    set vs [split $v {.}]
488
489    # If the upstream version number ends in [a-zA-Z]+, e.g. openssl's
490    # 1.0.1c, then treat the trailing characters as a base 26 number,
491    # mapping 'A' and 'a' to 1, 'B' and 'b' to 2, etc.
492    if {[regexp -- {\d([a-zA-Z]+)} $portversion ignored chars]} {
493        # Get the integer ordinals of 'A' and 'a'.
494        scan "A" %c ord_A
495        scan "a" %c ord_a
496
497        set i 0
498        foreach char [split $chars ""] {
499            scan $char %c ord
500
501            # Treat uppercase and lowercase characters as the same
502            # value.  Ordinal values less then 'a' should have 'A'
503            # subtracted, otherwise subtract 'a'.  Add 1 to the value
504            # so that 'a' and 'A' are mapped to 1, not 0.
505            if {$ord < $ord_a} {
506                set j [expr $ord - $ord_A + 1]
507            } else {
508                set j [expr $ord - $ord_a + 1]
509            }
510            set i [expr 26*$i + $j]
511        }
512        lappend vs $i
513    }
514
515    # Add integers so that the total number of integers in the version
516    # number is seven.
517    while {[llength $vs] < 7} {
518        lappend vs 0
519    }
520
521    # Prepend the epoch and append the revision number.
522    set vs [linsert $vs 0 $portepoch]
523    lappend vs $portrevision
524
525    set v [join $vs {.}]
526
527    return $v
528}
Note: See TracBrowser for help on using the repository browser.