source: trunk/base/src/port1.0/portdestroot.tcl

Last change on this file was 143068, checked in by cal@…, 3 years ago

base: stop recording mtimes and names when compressing manpages

The manpage modification times get stored into a header of the gzip format when
compressing them. This leads to non-reproducible builds, because the file
modification times of those manpages differ between builds.

Of course the file modification times themselves are an issue that still needs
to be fixed because the generated tarballs will not be reproducible otherwise,
but this change itself is still worthwhile.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 17.8 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# portdestroot.tcl
3# $Id: portdestroot.tcl 143068 2015-12-02 21:54:18Z cal@macports.org $
4#
5# Copyright (c) 2002 - 2003 Apple Inc.
6# Copyright (c) 2004 - 2005 Robert Shaw <rshaw@opendarwin.org>
7# Copyright (c) 2004-2005, 2007-2013 The MacPorts Project
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 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 portdestroot 1.0
36package require portutil 1.0
37
38set org.macports.destroot [target_new org.macports.destroot portdestroot::destroot_main]
39target_provides ${org.macports.destroot} destroot
40target_requires ${org.macports.destroot} main fetch checksum extract patch configure build
41target_prerun ${org.macports.destroot} portdestroot::destroot_start
42target_postrun ${org.macports.destroot} portdestroot::destroot_finish
43
44namespace eval portdestroot {
45    # Save old umask
46    variable oldmask
47}
48
49# define options
50options destroot.target destroot.destdir destroot.clean destroot.keepdirs destroot.umask \
51        destroot.violate_mtree destroot.asroot destroot.delete_la_files \
52        startupitem.autostart startupitem.create startupitem.executable \
53        startupitem.init startupitem.install startupitem.location \
54        startupitem.logevents startupitem.logfile startupitem.name \
55        startupitem.netchange startupitem.pidfile startupitem.plist \
56        startupitem.requires startupitem.restart startupitem.start \
57        startupitem.stop startupitem.type startupitem.uniquename
58commands destroot
59
60# Set defaults
61default destroot.asroot no
62default destroot.dir {${build.dir}}
63default destroot.cmd {${build.cmd}}
64default destroot.pre_args {[portdestroot::destroot_getargs]}
65default destroot.target install
66default destroot.post_args {${destroot.destdir}}
67default destroot.destdir {DESTDIR=${destroot}}
68default destroot.nice {${buildnicevalue}}
69default destroot.umask {$system_options(destroot_umask)}
70default destroot.clean no
71default destroot.keepdirs ""
72default destroot.violate_mtree no
73default destroot.delete_la_files {${delete_la_files}}
74
75default startupitem.autostart   no
76default startupitem.executable  ""
77default startupitem.init        ""
78default startupitem.install     {$system_options(startupitem_install)}
79default startupitem.location    LaunchDaemons
80default startupitem.logevents   no
81default startupitem.logfile     ""
82default startupitem.name        {${subport}}
83default startupitem.netchange   no
84default startupitem.pidfile     ""
85default startupitem.plist       {${startupitem.uniquename}.plist}
86default startupitem.requires    ""
87default startupitem.restart     ""
88default startupitem.start       ""
89default startupitem.stop        ""
90default startupitem.type        {$system_options(startupitem_type)}
91default startupitem.uniquename  {org.macports.${startupitem.name}}
92
93set_ui_prefix
94
95proc portdestroot::destroot_getargs {args} {
96    if {(([option build.type] eq "default" && [option os.platform] ne "freebsd") || \
97         ([option build.type] eq "gnu")) \
98        && [regexp "^(/\\S+/|)(g|gnu|)make(\\s+.*|)$" [option destroot.cmd]]} {
99        # Print "Entering directory" lines for better log debugging
100        return "-w [option destroot.target]"
101    }
102
103    return "[option destroot.target]"
104}
105
106proc portdestroot::destroot_start {args} {
107    global UI_PREFIX prefix subport porturl destroot os.platform destroot.clean portsharepath \
108           destroot.umask destroot.asroot euid egid \
109           applications_dir frameworks_dir
110    variable oldmask
111
112    ui_notice "$UI_PREFIX [format [msgcat::mc "Staging %s into destroot"] ${subport}]"
113
114    # start gsoc08-privileges
115    if { [getuid] == 0 && [geteuid] != 0 } {
116    # if started with sudo but have dropped the privileges
117        ui_debug "Can't run destroot under sudo without elevated privileges (due to mtree)."
118        ui_debug "Run destroot without sudo to avoid root privileges."
119        ui_debug "Going to escalate privileges back to root."
120        seteuid $euid
121        setegid $egid
122        ui_debug "euid changed to: [geteuid]. egid changed to: [getegid]."
123    }
124
125    if { [tbool destroot.asroot] && [getuid] != 0 } {
126        return -code error "You cannot run this port without root privileges. You need to re-run with 'sudo port'.";
127    }
128
129    # end gsoc08-privileges
130
131    set oldmask [umask ${destroot.umask}]
132    set mtree [findBinary mtree ${portutil::autoconf::mtree_path}]
133
134    if { ${destroot.clean} == "yes" } {
135        delete "${destroot}"
136    }
137
138    file mkdir "${destroot}"
139    if { ${os.platform} == "darwin" } {
140        system "cd \"${destroot}\" && ${mtree} -e -U -f [file join ${portsharepath} install macosx.mtree]"
141        file mkdir "${destroot}${applications_dir}"
142        file mkdir "${destroot}${frameworks_dir}"
143    }
144    file mkdir "${destroot}${prefix}"
145    system "cd \"${destroot}${prefix}\" && ${mtree} -e -U -f [file join ${portsharepath} install prefix.mtree]"
146}
147
148proc portdestroot::destroot_main {args} {
149    command_exec destroot
150    return 0
151}
152
153proc portdestroot::destroot_finish {args} {
154    global UI_PREFIX destroot prefix subport startupitem.create destroot.violate_mtree \
155           applications_dir frameworks_dir destroot.keepdirs destroot.delete_la_files \
156           os.platform os.version
157    variable oldmask
158
159    # Create startup-scripts/items
160    if {[tbool startupitem.create]} {
161        package require portstartupitem 1.0
162        portstartupitem::startupitem_create
163    }
164
165    foreach fileToDelete {share/info/dir lib/charset.alias} {
166        if {[file exists "${destroot}${prefix}/${fileToDelete}"]} {
167            ui_debug "Deleting stray ${fileToDelete} file."
168            file delete "${destroot}${prefix}/${fileToDelete}"
169        }
170    }
171
172    # Prevent overlinking due to glibtool .la files: https://trac.macports.org/ticket/38010
173    ui_debug "Fixing glibtool .la files in destroot for ${subport}"
174    set la_file_list [list]
175    fs-traverse -depth fullpath ${destroot} {
176        if {[file extension $fullpath] eq ".la" && ([file type $fullpath] eq "file" || [file type $fullpath] eq "link")} {
177            if {[file type $fullpath] eq "link" && [file pathtype [file link $fullpath]] ne "relative"} {
178                # prepend $destroot to target of absolute symlinks
179                set checkpath ${destroot}[file link $fullpath]
180            } else {
181                set checkpath $fullpath
182            }
183            # Make sure it is from glibtool ... "a libtool library file" will appear in the first line
184            if {![catch {set fp [open $checkpath]}]} {
185                if {[gets $fp line] > 0 && [string first "a libtool library file" $line] != -1} {
186                    lappend la_file_list $fullpath
187                }
188            } else {
189                ui_debug "Failed to open $checkpath"
190            }
191            catch {close $fp}
192        }
193    }
194    foreach fullpath $la_file_list {
195        if {${destroot.delete_la_files}} {
196            ui_debug "Removing [file tail $fullpath]"
197            file delete -force ${fullpath}
198        } elseif {[file type $fullpath] eq "file"} {
199            ui_debug "Clearing dependency_libs in [file tail $fullpath]"
200            reinplace "/dependency_libs/ s/'.*'/''/" ${fullpath}
201        }
202    }
203
204    # Prune empty directories in ${destroot}
205    foreach path ${destroot.keepdirs} {
206        if {![file isdirectory ${path}]} {
207            xinstall -m 0755 -d ${path}
208        }
209        if {![file exists ${path}/.turd_${subport}]} {
210            xinstall -c -m 0644 /dev/null ${path}/.turd_${subport}
211        }
212    }
213    fs-traverse -depth dir ${destroot} {
214        if {[file type $dir] eq "directory"} {
215            catch {file delete $dir}
216        }
217    }
218
219    if {![file isdirectory ${destroot}]} {
220        ui_error "No files have been installed in the destroot directory!"
221        ui_error "Please make sure that this software supports\
222                  'make install DESTDIR=\${destroot}' or implement an\
223                  alternative destroot mechanism in the Portfile."
224        ui_error "Files might have been installed directly into your system,\
225                  check before proceeding."
226        return -code error "Staging $subport into destroot failed"
227    }
228
229    # Compress all manpages with gzip (instead)
230    set manpath "${destroot}${prefix}/share/man"
231    set gzip [findBinary gzip ${portutil::autoconf::gzip_path}]
232    set gunzip "$gzip -d"
233    set bunzip2 "[findBinary bzip2 ${portutil::autoconf::bzip2_path}] -d"
234    if {[file isdirectory ${manpath}] && [file type ${manpath}] eq "directory"} {
235        ui_info "$UI_PREFIX [format [msgcat::mc "Compressing man pages for %s"] ${subport}]"
236        set found 0
237        set manlinks [list]
238        foreach mandir [readdir "${manpath}"] {
239            if {![regexp {^(cat|man)(.)$} ${mandir} match ignore manindex]} { continue }
240            set mandirpath [file join ${manpath} ${mandir}]
241            if {[file isdirectory ${mandirpath}] && [file type ${mandirpath}] eq "directory"} {
242                ui_debug "Scanning ${mandir}"
243                foreach manfile [readdir ${mandirpath}] {
244                    set manfilepath [file join ${mandirpath} ${manfile}]
245                    if {[file isfile ${manfilepath}] && [file type ${manfilepath}] eq "file"} {
246                        if {[regexp "^(.*\[.\]${manindex}\[a-z\]*)\[.\]gz\$" ${manfile} gzfile manfile]} {
247                            set found 1
248                            system "cd ${manpath} && \
249                            $gunzip -f [file join ${mandir} ${gzfile}] && \
250                            $gzip -9vnf [file join ${mandir} ${manfile}]"
251                        } elseif {[regexp "^(.*\[.\]${manindex}\[a-z\]*)\[.\]bz2\$" ${manfile} bz2file manfile]} {
252                            set found 1
253                            system "cd ${manpath} && \
254                            $bunzip2 -f [file join ${mandir} ${bz2file}] && \
255                            $gzip -9vnf [file join ${mandir} ${manfile}]"
256                        } elseif {[regexp "\[.\]${manindex}\[a-z\]*\$" ${manfile}]} {
257                            set found 1
258                            system "cd ${manpath} && \
259                            $gzip -9vnf [file join ${mandir} ${manfile}]"
260                        }
261                        set gzmanfile ${manfile}.gz
262                        set gzmanfilepath [file join ${mandirpath} ${gzmanfile}]
263                        if {[file exists ${gzmanfilepath}]} {
264                            set desired 00444
265                            set current [file attributes ${gzmanfilepath} -permissions]
266                            if {$current != $desired} {
267                                ui_info "[file join ${mandir} ${gzmanfile}]: changing permissions from $current to $desired"
268                                file attributes ${gzmanfilepath} -permissions $desired
269                            }
270                        }
271                    } elseif {[file type ${manfilepath}] eq "link"} {
272                        lappend manlinks [file join ${mandir} ${manfile}]
273                    }
274                }
275            }
276        }
277        if {$found == 1} {
278            # check man page links and rename/repoint them if necessary
279            foreach manlink $manlinks {
280                set manlinkpath [file join $manpath $manlink]
281                # if link destination is not gzipped, check it
282                set manlinksrc [file readlink $manlinkpath]
283                if {![regexp "\[.\]gz\$" ${manlinksrc}]} {
284                    set mandir [file dirname $manlink]
285                    set mandirpath [file join $manpath $mandir]
286                    set pwd [pwd]
287                    if {[catch {_cd $mandirpath} err]} {
288                        puts $err
289                        return
290                    }
291                    # if link source is an absolute path, check for it under destroot
292                    set mls_check "$manlinksrc"
293                    if {[file pathtype $mls_check] eq "absolute"} {
294                        set mls_check "${destroot}${mls_check}"
295                    }
296                    # if gzipped destination exists, fix link
297                    if {[file isfile ${mls_check}.gz]} {
298                        # if actual link name does not end with gz, rename it
299                        if {![regexp "\[.\]gz\$" ${manlink}]} {
300                            ui_debug "renaming link: $manlink to ${manlink}.gz"
301                            file rename $manlinkpath ${manlinkpath}.gz
302                            set manlink ${manlink}.gz
303                            set manlinkpath [file join $manpath $manlink]
304                        }
305                        # repoint the link
306                        ui_debug "repointing link: $manlink from $manlinksrc to ${manlinksrc}.gz"
307                        file delete $manlinkpath
308                        ln -s "${manlinksrc}.gz" "${manlinkpath}"
309                    }
310                    _cd $pwd
311                }
312            }
313        } else {
314            ui_debug "No man pages found to compress."
315        }
316    }
317
318    # test for violations of mtree
319    if { ${destroot.violate_mtree} != "yes" } {
320        ui_debug "checking for mtree violations"
321        set mtree_violation "no"
322
323        set prefixPaths [list bin etc include lib libexec sbin share src var www Applications Developer Library]
324
325        set pathsToCheck [list /]
326        while {[llength $pathsToCheck] > 0} {
327            set pathToCheck [lshift pathsToCheck]
328            foreach file [glob -nocomplain -directory $destroot$pathToCheck .* *] {
329                if {[file tail $file] eq "." || [file tail $file] eq ".."} {
330                    continue
331                }
332                if {[string equal -length [string length $destroot] $destroot $file]} {
333                    # just double-checking that $destroot is a prefix, as is appropriate
334                    set dfile [file join / [string range $file [string length $destroot] end]]
335                } else {
336                    throw MACPORTS "Unexpected filepath `${file}' while checking for mtree violations"
337                }
338                if {$dfile eq $prefix} {
339                    # we've found our prefix
340                    foreach pfile [glob -nocomplain -tails -directory $file .* *] {
341                        if {$pfile eq "." || $pfile eq ".."} {
342                            continue
343                        }
344                        if {$pfile ni $prefixPaths} {
345                            ui_warn "violation by [file join $dfile $pfile]"
346                            set mtree_violation "yes"
347                        }
348                    }
349                } elseif {[string equal -length [expr {[string length $dfile] + 1}] $dfile/ $prefix]} {
350                    # we've found a subpath of our prefix
351                    lpush pathsToCheck $dfile
352                } else {
353                    set dir_allowed no
354                    # these files are (at least potentially) outside of the prefix
355                    foreach dir "$applications_dir $frameworks_dir /Library/LaunchAgents /Library/LaunchDaemons /Library/StartupItems" {
356                        if {[string equal -length [expr {[string length $dfile] + 1}] $dfile/ $dir]} {
357                            # it's a prefix of one of the allowed paths
358                            set dir_allowed yes
359                            break
360                        }
361                    }
362                    if {$dir_allowed} {
363                        lpush pathsToCheck $dfile
364                    } else {
365                        # not a prefix of an allowed path, so it's either the path itself or a violation
366                        switch -- $dfile \
367                            $applications_dir - \
368                            $frameworks_dir - \
369                            /Library/LaunchAgents - \
370                            /Library/LaunchDaemons - \
371                            /Library/StartupItems { ui_debug "port installs files in $dfile" } \
372                            default {
373                                ui_warn "violation by $dfile"
374                                set mtree_violation "yes"
375                            }
376                    }
377                }
378            }
379        }
380
381        # abort here only so all violations can be observed
382        if { ${mtree_violation} != "no" } {
383            ui_warn "[format [msgcat::mc "%s violates the layout of the ports-filesystems!"] [option subport]]"
384            ui_warn "Please fix or indicate this misbehavior (if it is intended), it will be an error in future releases!"
385            # error "mtree violation!"
386        }
387    } else {
388        ui_warn "[format [msgcat::mc "%s installs files outside the common directory structure."] [option subport]]"
389    }
390
391    # Restore umask
392    umask $oldmask
393
394    return 0
395}
Note: See TracBrowser for help on using the repository browser.