Ticket #39629: muniversal-1.0.tcl

File muniversal-1.0.tcl, 38.5 KB (added by neverpanic (Clemens Lang), 11 years ago)

Patch against the muniversal PortGroup executing g-ir-generate and comparing the output

Line 
1# -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=tcl:et:sw=4:ts=4:sts=4
2# $Id: muniversal-1.0.tcl 107667 2013-07-04 04:53:46Z jeremyhu@macports.org $
3#
4# Copyright (c) 2009-2013 The MacPorts Project,
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions are
9# met:
10#
11# 1. Redistributions of source code must retain the above copyright
12#    notice, this list of conditions and the following disclaimer.
13# 2. Redistributions in binary form must reproduce the above copyright
14#    notice, this list of conditions and the following disclaimer in the
15#    documentation and/or other materials provided with the distribution.
16# 3. Neither the name of Apple Computer, Inc. nor the names of its
17#    contributors may be used to endorse or promote products derived from
18#    this software without specific prior written permission.
19#
20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31#
32
33# User variables:
34#         merger_configure_env: associative array of configure.env variables
35#             merger_build_env: associative array of build.env variables
36#          merger_destroot_env: associative array of destroot.env variables
37#                  merger_host: associative array of host values
38#        merger_configure_args: associative array of configure.args
39#            merger_build_args: associative array of build.args
40#         merger_destroot_args: associative array of destroot.args
41#    merger_configure_compiler: associative array of configure.compiler
42#    merger_configure_cppflags: associative array of configure.cppflags
43#      merger_configure_cflags: associative array of configure.cflags
44#    merger_configure_cxxflags: associative array of configure.cxxflags
45#   merger_configure_objcflags: associative array of configure.objcflags
46#     merger_configure_ldflags: associative array of configure.ldflags
47#             merger_arch_flag: if no, -arch xxx will not be appended configure.???flags
48#         merger_arch_compiler: if no, -arch xxx will not be appended to compilers
49#             merger_dont_diff: list of file names for which diff will not work
50#     merger_must_run_binaries: if yes, build platform must be able to run binaries for supported architectures
51#            merger_no_3_archs: if yes, merger will not work correctly if there are three supported architectures
52
53options universal_archs_supported merger_must_run_binaries merger_no_3_archs merger_arch_flag merger_arch_compiler
54default universal_archs_supported {${universal_archs}}
55default merger_must_run_binaries {no}
56default merger_no_3_archs {no}
57default merger_arch_flag {yes}
58default merger_arch_compiler {no}
59
60proc muniversal_arch_flag_supported {args} {
61    global configure.compiler
62    return [regexp {^gcc-4|llvm|apple|clang} ${configure.compiler}]
63}
64
65proc muniversal_get_arch_flag {arch {fortran ""}} {
66    global os.arch
67    # Prefer -arch to -m
68    if {[muniversal_arch_flag_supported] && ${fortran}==""} {
69        set archf "-arch ${arch}"
70    } else {
71        if { ${os.arch}=="i386" && ${arch}=="i386" } {
72            set archf -m32
73        } elseif { ${os.arch}=="i386" && ${arch}=="x86_64" } {
74            set archf -m64
75        } elseif { ${os.arch}=="powerpc" && ${arch}=="ppc" } {
76            set archf -m32
77        } elseif { ${os.arch}=="powerpc" && ${arch}=="ppc64" } {
78            set archf -m64
79        } else {
80            if { ${fortran}=="" } {
81                return -code error "selected compiler can't build for ${arch}"
82            } else {
83                return ""
84            }
85        }
86    }
87    return ${archf}
88}
89
90# set up the merger-post-destroot hook
91
92proc merger_target_provides {ditem args} {
93    global targets
94    # register just the procedure, no pre-/post-
95    # User-code exceptions are caught and returned as a result of the target.
96    # Thus if the user code breaks, dependent targets will not execute.
97    foreach target $args {
98        set origproc [ditem_key $ditem procedure]
99        set ident [ditem_key $ditem name]
100        proc merger-post-$target {args} "
101            variable proc_index
102            set proc_index \[llength \[ditem_key $ditem post\]\]
103            ditem_append $ditem merger-post proc-merger-post-${ident}-${target}-\${proc_index}
104            proc proc-merger-post-${ident}-${target}-\${proc_index} {name} \"
105                if {\\\[catch userproc-merger-post-${ident}-${target}-\${proc_index} result\\\]} {
106                    return -code error \\\$result
107                } else {
108                    return 0
109                }
110            \"
111            makeuserproc userproc-merger-post-${ident}-${target}-\${proc_index} \$args
112        "
113    }
114}
115
116merger_target_provides ${org.macports.destroot} destroot
117
118variant universal {
119    global universal_archs_to_use
120
121    foreach arch ${universal_archs} {
122        configure.universal_cflags-delete    -arch ${arch}
123        configure.universal_cxxflags-delete  -arch ${arch}
124        configure.universal_ldflags-delete   -arch ${arch}
125    }
126
127    eval configure.args-append      ${configure.universal_args}
128    eval configure.cflags-append    ${configure.universal_cflags}
129    eval configure.cxxflags-append  ${configure.universal_cxxflags}
130    eval configure.objcflags-append ${configure.universal_cflags}
131    eval configure.ldflags-append   ${configure.universal_ldflags}
132    eval configure.cppflags-append  ${configure.universal_cppflags}
133
134    # user has specified that build platform must be able to run binaries for supported architectures
135    if { ${merger_must_run_binaries}=="yes" } {
136        if { ${os.arch}=="i386" } {
137            set universal_archs_supported [ldelete ${universal_archs_supported} "ppc64"]
138            if {${os.major} >= 9 && [sysctl hw.cpu64bit_capable] == 0} {
139                set universal_archs_supported [ldelete ${universal_archs_supported} "x86_64"]
140            }
141        } else {
142            set universal_archs_supported [ldelete ${universal_archs_supported} "i386"]
143            set universal_archs_supported [ldelete ${universal_archs_supported} "x86_64"]
144            if {${os.major} >= 9 && [sysctl hw.cpu64bit_capable] == 0} {
145                set universal_archs_supported [ldelete ${universal_archs_supported} "ppc64"]
146            }
147        }
148    }
149
150    # set universal_archs_to_use as the intersection of universal_archs and universal_archs_supported
151    set universal_archs_to_use {}
152    foreach arch ${universal_archs} {
153        set arch_ok no
154        foreach archt ${universal_archs_supported} {
155            if { ${arch}==${archt} } {
156                set arch_ok yes
157            }
158        }
159        if { ${arch_ok}=="yes" } {
160            lappend universal_archs_to_use ${arch}
161        }
162    }
163
164    # if merger_no_3_archs is yes, prune universal_archs_to_use until it only has two elements
165    if { ${merger_no_3_archs}=="yes" } {
166        if { [llength ${universal_archs_to_use}] == 3 } {
167            # first try to remove cross-compiled 64-bit arch
168            if { ${os.arch}=="i386" } {
169                set universal_archs_to_use [ldelete ${universal_archs_to_use} "ppc64"]
170            } else {
171                set universal_archs_to_use [ldelete ${universal_archs_to_use} "x86_64"]
172            }
173        }
174        if { [llength ${universal_archs_to_use}] == 3 } {
175            # next try to remove cross-compiled 32-bit arch
176            if { ${os.arch}=="i386" } {
177                set universal_archs_to_use [ldelete ${universal_archs_to_use} "ppc"]
178            } else {
179                set universal_archs_to_use [ldelete ${universal_archs_to_use} "i386"]
180            }
181        }
182        if { [llength ${universal_archs_to_use}] == 3 } {
183            # at least one arch should have been removed from universal_archs_to_use
184            error "Should Not Happen"
185        }
186    }
187
188    configure {
189        # Fix inability to find nm when cross-compiling (#22224, #23431, #23687, #24477, et al)
190        configure.env-append    NM=/usr/bin/nm
191
192        foreach arch ${universal_archs_to_use} {
193            ui_info "$UI_PREFIX [format [msgcat::mc "Configuring %1\$s for architecture %2\$s"] $name ${arch}]"
194
195            if {![file exists ${worksrcpath}-${arch}]} {
196                copy ${worksrcpath} ${worksrcpath}-${arch}
197            }
198
199            set archf [muniversal_get_arch_flag ${arch}]
200            set archff [muniversal_get_arch_flag ${arch} "fortran"]
201
202            if { ${merger_arch_flag} != "no" } {
203                configure.cflags-append    ${archf}
204                configure.cxxflags-append  ${archf}
205                configure.objcflags-append ${archf}
206                configure.fflags-append    ${archff}
207                configure.fcflags-append   ${archff}
208                configure.f90flags-append  ${archff}
209                configure.ldflags-append   ${archf}
210            }
211
212            if { [info exists merger_configure_env(${arch})] } {
213                configure.env-append  $merger_configure_env(${arch})
214            }
215            if { [info exists merger_configure_cppflags(${arch})] } {
216                configure.cppflags-append  $merger_configure_cppflags(${arch})
217            }
218            if { [info exists merger_configure_cflags(${arch})] } {
219                configure.cflags-append  $merger_configure_cflags(${arch})
220            }
221            if { [info exists merger_configure_cxxflags(${arch})] } {
222                configure.cxxflags-append  $merger_configure_cxxflags(${arch})
223            }
224            if { [info exists merger_configure_objcflags(${arch})] } {
225                configure.objcflags-append  $merger_configure_objcflags(${arch})
226            }
227            if { [info exists merger_configure_ldflags(${arch})] } {
228                configure.ldflags-append  $merger_configure_ldflags(${arch})
229            }
230
231            # Don't set the --host unless we have to.
232            set host ""
233            if { [info exists merger_host($arch)] } {
234                if { $merger_host($arch) != "" } {
235                    set host  --host=$merger_host($arch)
236                }
237            } elseif {[file tail ${configure.cmd}] != "cmake"} {
238                # check if building for a word length we can't run
239                set bits_differ 0
240                if {(${arch}=="x86_64" || ${arch}=="ppc64") &&
241                    (${os.major} < 9 || [sysctl hw.cpu64bit_capable] == 0)} {
242                    set bits_differ 1
243                }
244                # check if building for a completely different arch
245                if {$bits_differ || (${os.arch}=="i386" && (${arch}=="ppc" || ${arch}=="ppc64"))
246                        || (${os.arch}=="powerpc" && (${arch}=="i386" || ${arch}=="x86_64"))
247                        || $macosx_deployment_target != $macosx_version} {
248                    if {$macosx_deployment_target == $macosx_version} {
249                        set hostversion ${os.version}
250                    } else {
251                        set hostversion [expr [lindex [split $macosx_deployment_target .] 1] + 4]
252                    }
253                    switch -- ${arch} {
254                        x86_64  {set host "--host=x86_64-apple-${os.platform}${hostversion}"}
255                        i386    {set host "--host=i686-apple-${os.platform}${hostversion}"}
256                        ppc     {set host "--host=powerpc-apple-${os.platform}${hostversion}"}
257                        ppc64   {set host "--host=powerpc64-apple-${os.platform}${hostversion}"}
258                    }
259                }
260            }
261            if {$host != ""} {
262                configure.args-append  ${host}
263            }
264
265            if { [info exists merger_configure_args(${arch})] } {
266                configure.args-append  $merger_configure_args(${arch})
267            }
268
269            set configure_compiler_save ${configure.compiler}
270            set configure_cc_save       ${configure.cc}
271            set configure_cxx_save      ${configure.cxx}
272            set configure_objc_save     ${configure.objc}
273            set configure_fc_save       ${configure.fc}
274            set configure_f77_save      ${configure.f77}
275            set configure_f90_save      ${configure.f90}
276
277            if { [info exists merger_configure_compiler($arch)] } {
278                configure.compiler  $merger_configure_compiler($arch)
279                configure.cc        [portconfigure::configure_get_compiler cc]
280                configure.cxx       [portconfigure::configure_get_compiler cxx]
281                configure.objc      [portconfigure::configure_get_compiler objc]
282                configure.f77       [portconfigure::configure_get_compiler f77]
283                configure.f90       [portconfigure::configure_get_compiler f90]
284                configure.fc        [portconfigure::configure_get_compiler fc]
285            }
286
287            if { ${merger_arch_compiler} != "no" } {
288                configure.cc   ${configure.cc}   ${archf}
289                configure.cxx  ${configure.cxx}  ${archf}
290                configure.objc ${configure.objc} ${archf}
291                if { ${configure.fc}  != "" } { configure.fc   ${configure.fc}  ${archff} }
292                if { ${configure.f77} != "" } { configure.f77  ${configure.f77} ${archff} }
293                if { ${configure.f90} != "" } { configure.f90  ${configure.f90} ${archff} }
294            }
295
296            set configure_dir_save  ${configure.dir}
297            if { [string match "${worksrcpath}/*" ${configure.dir}] } {
298                # The configure directory is inside the source directory, so put in the new source directory name.
299                eval configure.dir  [string map "${worksrcpath} ${worksrcpath}-${arch}" ${configure.dir}]
300            } else {
301                # The configure directory is outside the source directory, so give it a new name by appending ${arch}.
302                configure.dir  ${configure.dir}-${arch}
303                if { ![file exists ${configure.dir}] } {
304                    file mkdir ${configure.dir}
305                }
306            }
307
308            set autoreconf_dir_save  ${autoreconf.dir}
309            if { [string match "${worksrcpath}/*" ${autoreconf.dir}] } {
310                # The autoreconf directory is inside the source directory, so put in the new source directory name.
311                eval autoreconf.dir  [string map "${worksrcpath} ${worksrcpath}-${arch}" ${autoreconf.dir}]
312            } else {
313                # The autoreconf directory is outside the source directory, so give it a new name by appending ${arch}.
314                autoreconf.dir  ${autoreconf.dir}-${arch}
315                if { ![file exists ${autoreconf.dir}] } {
316                    file mkdir ${autoreconf.dir}
317                }
318            }
319
320            portconfigure::configure_main
321
322            # Undo changes to the configure related variables
323            eval autoreconf.dir ${autoreconf_dir_save}
324            eval configure.dir  ${configure_dir_save}
325            eval configure.compiler ${configure_compiler_save}
326            eval configure.f90  ${configure_f90_save}
327            eval configure.f77  ${configure_f77_save}
328            eval configure.fc   ${configure_fc_save}
329            eval configure.cc   ${configure_cc_save}
330            eval configure.cxx  ${configure_cxx_save}
331            eval configure.objc ${configure_objc_save}
332            if { [info exists merger_configure_args(${arch})] } {
333                configure.args-delete  $merger_configure_args(${arch})
334            }
335            configure.args-delete  ${host}
336            if { [info exists merger_configure_ldflags(${arch})] } {
337                configure.ldflags-delete  $merger_configure_ldflags(${arch})
338            }
339            if { [info exists merger_configure_cxxflags(${arch})] } {
340                configure.cxxflags-delete  $merger_configure_cxxflags(${arch})
341            }
342            if { [info exists merger_configure_objcflags(${arch})] } {
343                configure.objcflags-delete  $merger_configure_objcflags(${arch})
344            }
345            if { [info exists merger_configure_cflags(${arch})] } {
346                configure.cflags-delete  $merger_configure_cflags(${arch})
347            }
348            if { [info exists merger_configure_cppflags(${arch})] } {
349                configure.cppflags-delete  $merger_configure_cppflags(${arch})
350            }
351            if { [info exists merger_configure_env(${arch})] } {
352                configure.env-delete  $merger_configure_env(${arch})
353            }
354            if { ${merger_arch_flag} != "no" } {
355                configure.ldflags-delete   ${archf}
356                configure.f90flags-delete  ${archff}
357                configure.fcflags-delete   ${archff}
358                configure.fflags-delete    ${archff}
359                configure.objcflags-delete ${archf}
360                configure.cxxflags-delete  ${archf}
361                configure.cflags-delete    ${archf}
362            }
363        }
364    }
365
366    build {
367        foreach arch ${universal_archs_to_use} {
368            ui_info "$UI_PREFIX [format [msgcat::mc "Building %1\$s for architecture %2\$s"] $name ${arch}]"
369
370            if { [info exists merger_build_env(${arch})] } {
371                build.env-append  $merger_build_env(${arch})
372            }
373            if { [info exists merger_build_args(${arch})] } {
374                build.args-append  $merger_build_args(${arch})
375            }
376            set build_dir_save  ${build.dir}
377            if { [string match "${worksrcpath}/*" ${build.dir}] } {
378                # The build directory is inside the source directory, so put in the new source directory name.
379                eval build.dir  [string map "${worksrcpath} ${worksrcpath}-${arch}" ${build.dir}]
380            } else {
381                # The build directory is outside the source directory, so give it a new name by appending ${arch}.
382                build.dir  ${build.dir}-${arch}
383                if { ![file exists ${build.dir}] } {
384                    file mkdir ${build.dir}
385                }
386            }
387
388            portbuild::build_main
389
390            eval build.dir  ${build_dir_save}
391            if { [info exists merger_build_args(${arch})] } {
392                build.args-delete $merger_build_args(${arch})
393            }
394            if { [info exists merger_build_env(${arch})] } {
395                build.env-delete  $merger_build_env(${arch})
396            }
397        }
398    }
399
400    destroot {
401        foreach arch ${universal_archs_to_use} {
402            ui_info "$UI_PREFIX [format [msgcat::mc "Staging %1\$s into destroot for architecture %2\$s"] $name ${arch}]"
403            copy ${destroot} ${workpath}/destroot-${arch}
404            set destdirSave ${destroot.destdir}
405            eval destroot.destdir  [string map "${destroot} ${workpath}/destroot-${arch}" ${destroot.destdir}]
406
407            if { [info exists merger_destroot_env(${arch})] } {
408                destroot.env-append  $merger_destroot_env(${arch})
409            }
410            if { [info exists merger_destroot_args(${arch})] } {
411                destroot.args-append  $merger_destroot_args(${arch})
412            }
413            set destroot_dir_save ${destroot.dir}
414            if { [string match "${worksrcpath}/*" ${destroot.dir}] } {
415                # The destroot directory is inside the source directory, so put in the new source directory name.
416                eval destroot.dir  [string map "${worksrcpath} ${worksrcpath}-${arch}" ${destroot.dir}]
417            } else {
418                # The destroot directory is outside the source directory, so give it a new name by appending ${arch}.
419                destroot.dir  ${destroot.dir}-${arch}
420                if { ![file exists ${destroot.dir}] } {
421                    file mkdir ${destroot.dir}
422                }
423            }
424
425            portdestroot::destroot_main
426
427            destroot.dir  ${destroot_dir_save}
428            if { [info exists merger_destroot_args(${arch})] } {
429                destroot.args-delete $merger_destroot_args(${arch})
430            }
431            if { [info exists merger_destroot_env(${arch})] } {
432                destroot.env-delete  $merger_destroot_env(${arch})
433            }
434            eval destroot.destdir ${destdirSave}
435        }
436        delete ${destroot}
437
438        # execute merger-post-destroot, if it exists
439
440        set ditem ${org.macports.destroot}
441        set procedure [ditem_key $ditem merger-post]
442        if {$procedure != ""} {
443            set targetname [ditem_key $ditem name]
444            ui_debug "Executing org.macports.merger-post-destroot"
445            set result [catch { $procedure $targetname } errstr]
446            # Save variables in order to re-throw the same error code.
447            set errcode $::errorCode
448            set errinfo $::errorInfo
449            if {$result != 0} {
450                set portname $subport
451                ui_error "$targetname for port $portname returned: $errstr"
452                ui_debug "Error code: $errcode"
453                ui_debug "Backtrace: $errinfo"
454                return $result
455            }
456        }
457
458        # Merge ${base1}/${prefixDir} and ${base2}/${prefixDir} into dir ${base}/${prefixDir}
459        #        arch1, arch2: names to prepend to files if a diff merge of two files is forbidden by merger_dont_diff
460        #    merger_dont_diff: list of files for which /usr/bin/diff ${diffFormat} will not merge correctly
461        #          diffFormat: format used by diff to merge two text files
462        proc merge2Dir {base1 base2 base prefixDir arch1 arch2 merger_dont_diff diffFormat} {
463            global prefix
464
465            set dir1  ${base1}/${prefixDir}
466            set dir2  ${base2}/${prefixDir}
467            set dir   ${base}/${prefixDir}
468
469            xinstall -d -m 0755 ${dir}
470
471            foreach fl [glob -directory ${dir2} -tails -nocomplain *] {
472                if { ![muniversal_file_or_symlink_exists ${dir1}/${fl}] } {
473                    # File only exists in ${dir1}
474                    ui_debug "universal: merge: ${prefixDir}/${fl} only exists in ${base2}"
475                    copy ${dir2}/${fl} ${dir}
476                }
477            }
478            foreach fl [glob -directory ${dir1} -tails -nocomplain *] {
479                if { ![muniversal_file_or_symlink_exists ${dir2}/${fl}] } {
480                    # File only exists in ${dir2}
481                    ui_debug "universal: merge: ${prefixDir}/${fl} only exists in ${base1}"
482                    copy ${dir1}/${fl} ${dir}
483                } else {
484                    # File exists in ${dir1} and ${dir2}
485                    ui_debug "universal: merge: merging ${prefixDir}/${fl} from ${base1} and ${base2}"
486
487                    # Ensure files are of same type
488                    if { [file type ${dir1}/${fl}]!=[file type ${dir2}/${fl}] } {
489                        error "${dir1}/${fl} and ${dir2}/${fl} are of different types"
490                    }
491
492                    if { [file type ${dir1}/${fl}]=="link" } {
493                        # Files are links
494                        ui_debug "universal: merge: ${prefixDir}/${fl} is a link"
495
496                        # Ensure links don't point to different things
497                        if { [file readlink ${dir1}/${fl}]==[file readlink ${dir2}/${fl}] } {
498                            copy ${dir1}/${fl} ${dir}
499                        } else {
500                            error "${dir1}/${fl} and ${dir2}/${fl} point to different targets (can't merge them)"
501                        }
502                    } elseif { [file isdirectory ${dir1}/${fl}] } {
503                        # Files are directories (but not links), so recursively call function
504                        merge2Dir ${base1} ${base2} ${base} ${prefixDir}/${fl} ${arch1} ${arch2} ${merger_dont_diff} ${diffFormat}
505                    } else {
506                        # Files are neither directories nor links
507                        if { ! [catch {system "/usr/bin/cmp \"${dir1}/${fl}\" \"${dir2}/${fl}\" && /bin/cp -v \"${dir1}/${fl}\" \"${dir}\""}] } {
508                            # Files are byte by byte the same
509                            ui_debug "universal: merge: ${prefixDir}/${fl} is identical in ${base1} and ${base2}"
510                        } else {
511                            # Actually try to merge the files
512                            # First try lipo, then libtool
513                            if { ! [catch {system "/usr/bin/lipo -create \"${dir1}/${fl}\" \"${dir2}/${fl}\" -output \"${dir}/${fl}\""}] } {
514                                # lipo worked
515                                ui_debug "universal: merge: lipo created ${prefixDir}/${fl}"
516                            } elseif { ! [catch {system "/usr/bin/libtool \"${dir1}/${fl}\" \"${dir2}/${fl}\" -o \"${dir}/${fl}\""}] } {
517                                # libtool worked
518                                ui_debug "universal: merge: libtool created ${prefixDir}/${fl}"
519                            } else {
520                                # lipo and libtool have failed, so assume they are text files to be merged
521                                set dontdiff no
522                                foreach dont ${merger_dont_diff} {
523                                    if { ${dont}=="${prefixDir}/${fl}" } {
524                                        set dontdiff yes
525                                    }
526                                }
527                                if { ${dontdiff}==yes } {
528                                    # user has specified that diff does not work
529                                    # attempt to give each file a unique name and create a new file which includes one of the original depending on the arch
530
531                                    set fh [open ${dir}/${arch1}-${fl} w 0644]
532                                    puts ${fh} "#include \"${arch1}-${fl}\""
533                                    close ${fh}
534
535                                    set fh [open ${dir}/${arch2}-${fl} w 0644]
536                                    puts ${fh} "#include \"${arch2}-${fl}\""
537                                    close ${fh}
538
539                                    ui_debug "universal: merge: created ${prefixDir}/${fl} to include ${prefixDir}/${arch1}-${fl} ${prefixDir}/${arch1}-${fl}"
540
541                                    system "/usr/bin/diff -d ${diffFormat} \"${dir}/${arch1}-${fl}\" \"${dir}/${arch2}-${fl}\" > \"${dir}/${fl}\"; test \$? -le 1"
542
543                                    copy -force ${dir1}/${fl} ${dir}/${arch1}-${fl}
544                                    copy -force ${dir2}/${fl} ${dir}/${arch2}-${fl}
545                                } else {
546                                    set known_file "no"
547
548                                    # Text file on which diff will not give correct results.
549                                    switch -glob ${fl} {
550                                        *.mod {
551                                            # .mod files from Fortran modules.
552                                            # Create a sepcial module directory for each architecture.
553                                            # To find these modules, GFortran might require -M or -J.
554                                            set known_file "yes"
555                                            file mkdir ${dir}/mods32
556                                            file mkdir ${dir}/mods64
557                                            if { ${arch1}=="i386" || ${arch1}=="ppc" } {
558                                                copy ${dir1}/${fl} ${dir}/mods32
559                                                copy ${dir2}/${fl} ${dir}/mods64
560                                            } else {
561                                                copy ${dir2}/${fl} ${dir}/mods32
562                                                copy ${dir1}/${fl} ${dir}/mods64
563                                            }
564                                        }
565                                        *.la -
566                                        *.pc -
567                                        *-config {
568                                            return -code error "${prefixDir}/${fl} differs in ${base1} and ${base2} and cannot be merged"
569                                        }
570                                    }
571
572                                    if { ${known_file}=="no" } {
573                                        if { ! [catch {system "/usr/bin/diff -dw ${diffFormat} \"${dir1}/${fl}\" \"${dir2}/${fl}\" > \"${dir}/${fl}\"; test \$? -le 1"} ] } {
574                                            # diff worked
575                                            ui_debug "universal: merge: used diff to create ${prefixDir}/${fl}"
576                                        } else {
577                                            # File created by diff is invalid
578                                            delete ${dir}/${fl}
579
580                                            # nothing has worked so far.
581                                            switch -glob ${fl} {
582                                                *.typelib {
583                                                    # Sometimes garbage ends up in ignored trailing bytes
584                                                    # https://trac.macports.org/ticket/39629
585                                                    # Compare the g-ir-generate output to see if the contents differ
586                                                    set tempdir [mkdtemp "/tmp/muniversal.XXXXXXXX"]
587                                                    set tempfile1 "${tempdir}/${arch1}-[file rootname ${fl}]"
588                                                    set tempfile2 "${tempdir}/${arch2}-[file rootname ${fl}]"
589                                                    system "GI_TYPELIB_PATH='[file dirname "${dir1}/${fl}"]' ${prefix}/bin/g-ir-generate \"${dir1}/${fl}\" > \"${tempfile1}\""
590                                                    system "GI_TYPELIB_PATH='[file dirname "${dir2}/${fl}"]' ${prefix}/bin/g-ir-generate \"${dir2}/${fl}\" > \"${tempfile2}\""
591                                                    set identical "no"
592                                                    if {![catch {system "/usr/bin/cmp -s \"${tempfile1}\" \"${tempfile2}\""}]} {
593                                                        # files are identical
594                                                        ui_debug "universal: merge: ${prefixDir}/${fl} differs in ${base1} and ${base2} but the contents are the same"
595                                                        set identical "yes"
596                                                        copy ${dir1}/${fl} ${dir}
597                                                    }
598                                                    delete ${tempfile1} ${tempfile2} ${tempdir}
599                                                    if {${identical} eq "no"} {
600                                                        return -code error "${prefixDir}/${fl} differs in ${base1} and ${base2} and cannot be merged"
601                                                    }
602                                                }
603                                                *.jar {
604                                                    # jar files can be different because of timestamp
605                                                    ui_debug "universal: merge: ${prefixDir}/${fl} differs in ${base1} and ${base2}; assume timestamp difference"
606                                                    copy ${dir1}/${fl} ${dir}
607                                                }
608                                                *.elc {
609                                                    # elc files can be different because they record when and where they were built.
610                                                    ui_debug "universal: merge: ${prefixDir}/${fl} differs in ${base1} and ${base2}; assume trivial difference"
611                                                    copy ${dir1}/${fl} ${dir}
612                                                }
613                                                *.gz -
614                                                *.bz2 {
615                                                    # compressed files can differ due to entropy
616                                                    switch -glob ${fl} {
617                                                        *.gz {
618                                                            set cat /usr/bin/gzcat
619                                                        }
620                                                        *.bz2 {
621                                                            set cat /usr/bin/bzcat
622                                                        }
623                                                    }
624                                                    set tempdir [mkdtemp "/tmp/muniversal.XXXXXXXX"]
625                                                    set tempfile1 "${tempdir}/${arch1}-[file rootname ${fl}]"
626                                                    set tempfile2 "${tempdir}/${arch2}-[file rootname ${fl}]"
627                                                    system "${cat} \"${dir1}/${fl}\" > \"${tempfile1}\""
628                                                    system "${cat} \"${dir2}/${fl}\" > \"${tempfile2}\""
629                                                    set identical "no"
630                                                    if { ! [catch {system "/usr/bin/cmp -s \"${tempfile1}\" \"${tempfile2}\""}] } {
631                                                        # files are identical
632                                                        ui_debug "universal: merge: ${prefixDir}/${fl} differs in ${base1} and ${base2} but the contents are the same"
633                                                        set identical "yes"
634                                                        copy ${dir1}/${fl} ${dir}
635                                                    }
636                                                    if { ${identical}=="no" } {
637                                                        switch -glob ${fl} {
638                                                            *.el.gz {
639                                                                # Emacs lisp files should be same across architectures
640                                                                # the emacs package (and perhaps others) records the date of automatically generated el files
641                                                                ui_debug "universal: merge: ${prefixDir}/${fl} differs in ${base1} and ${base2}; assume trivial difference"
642                                                                set identical "yes"
643                                                                copy ${dir1}/${fl} ${dir}
644                                                            }
645                                                        }
646                                                    }
647                                                    delete ${tempfile1} ${tempfile2} ${tempdir}
648                                                    if {${identical}=="no"} {
649                                                        return -code error "${prefixDir}/${fl} differs in ${base1} and ${base2} and cannot be merged"
650                                                    }
651                                                }
652                                                default {
653                                                    return -code error "${prefixDir}/${fl} differs in ${base1} and ${base2} and cannot be merged"
654                                                }
655                                            }
656                                        }
657                                    }
658                                }
659                            }
660                        }
661                    }
662                }
663            }
664        }
665
666        # /usr/bin/diff can merge two C/C++ files
667        # See http://www.gnu.org/software/diffutils/manual/html_mono/diff.html#If-then-else
668        # See http://www.gnu.org/software/diffutils/manual/html_mono/diff.html#Detailed%20If-then-else
669        set diffFormatProc {--old-group-format='#if (defined(__ppc__) || defined(__ppc64__))
670 %<#endif
671' \
672--new-group-format='#if defined (__i386__) || defined(__x86_64__)
673%>#endif
674' \
675--unchanged-group-format='%=' \
676--changed-group-format='#if (defined(__ppc__) || defined(__ppc64__))
677%<#else
678%>#endif
679'}
680
681        set diffFormatM "-D __LP64__"
682
683        if { ![info exists merger_dont_diff] } {
684            set merger_dont_diff {}
685        }
686
687        merge2Dir  ${workpath}/destroot-ppc      ${workpath}/destroot-ppc64 ${workpath}/destroot-powerpc  ""  ppc ppc64    ${merger_dont_diff}  ${diffFormatM}
688        merge2Dir  ${workpath}/destroot-i386     ${workpath}/destroot-x86_64 ${workpath}/destroot-intel   ""  i386 x86_64  ${merger_dont_diff}  ${diffFormatM}
689        merge2Dir  ${workpath}/destroot-powerpc  ${workpath}/destroot-intel ${workpath}/destroot          ""  powerpc x86  ${merger_dont_diff}  ${diffFormatProc}
690    }
691
692    test {
693        foreach arch ${universal_archs_to_use} {
694            # Rosetta does not translate G5 instructions
695            # PowerPC systems can't translate Intel instructions
696            if { (${os.arch}=="i386" && ${arch}!="ppc64") || (${os.arch}=="powerpc" && ${arch}!="i386" && ${arch}!="x86_64") } {
697                ui_info "$UI_PREFIX [format [msgcat::mc "Testing %1\$s for architecture %2\$s"] $name ${arch}]"
698                set test_dir_save ${test.dir}
699                if { [string match "${worksrcpath}/*" ${test.dir}] } {
700                    # The test directory is inside the source directory, so put in the new source directory name.
701                    eval test.dir  [string map "${worksrcpath} ${worksrcpath}-${arch}" ${test.dir}]
702                } else {
703                    # The test directory is outside the source directory, so give it a new name by appending ${arch}.
704                    test.dir  ${test.dir}-${arch}
705                    if { ![file exists ${test.dir}] } {
706                        file mkdir ${test.dir}
707                    }
708                }
709
710                porttest::test_main
711
712                test.dir ${test_dir_save}
713            }
714        }
715    }
716}
717
718# [muniversal_file_or_symlink_exists ${f}] tells you if ${f} exists. And unlike
719# [file exists ${f}], if used on a symlink, [muniversal_file_or_symlink_exists ${f}]
720# tells you about the symlink, not what it points to.
721proc muniversal_file_or_symlink_exists {f} {
722    # If [file type ${f}] throws an error, ${f} doesn't exist.
723    if {[catch {file type ${f}}]} {
724        return 0
725    }
726    # Otherwise, it does.
727    return 1
728}