Ticket #2312: osx-packager.pl

File osx-packager.pl, 36.6 KB (added by pdbailey@…, 13 years ago)

2nd attempt at playback, after patch applied (patch also attached)

Line 
1#!/usr/bin/perl
2
3### osx-packager.pl $Id$
4### Tool for automating frontend builds on Mac OS X.
5### Run "osx-packager.pl -man" for full documentation.
6
7use strict;
8use Getopt::Long qw(:config auto_abbrev);
9use Pod::Usage ();
10use Cwd ();
11
12
13open(SVNVER, '>./ftp/revision.txt');
14
15
16### Configuration settings (stuff that might change more often)
17
18# We try to auto-locate the Subversion client binaries.
19# If they are not in your path, you should use the second line.
20#
21our $svn = '/usr/local/bin/svn';
22#our $svn = '/Volumes/Users/nigel/bin/svn';
23
24# This script used to always delete the installed include and lib dirs.
25# That probably ensures a safe build, but when rebuilding adds minutes to
26# the total build time, and prevents us skipping some parts of a full build
27#
28our $cleanLibs = 1;
29
30# By default, only the frontend is built (i.e. no backend or transcoding)
31#
32our $backend = 0;
33our $jobtools = 0;
34
35# Parallel makes?
36#
37#$ENV{'DISTCC_HOSTS'}   = "localhost 192.168.0.6";
38#$ENV{'DISTCC_VERBOSE'} = '1';
39
40# For faster downloads, change this to a local mirror.
41#
42our $sourceforge = 'http://umn.dl.sourceforge.net';
43
44# At the moment, there is mythtv plus these two
45our @components = ( 'myththemes', 'mythplugins' );
46
47# The OS X programs that we are likely to be interested in.
48our @targetsJT = ( 'MythCommFlag',  'MythJobQueue');
49our @targetsBE = ( 'MythBackend',   'MythFillDatabase',
50                   'MythTranscode', 'MythTV-Setup');
51
52# Patches for MythTV source
53our %patches = (
54);
55
56our %depend_order = (
57  'mythtv'
58  =>  [
59        'freetype',
60        'lame',
61        'mysqlclient',
62        'qt-mt',
63        # Not needed since SVN -r8965. Kept here for 0.19 builds
64        #'dvdnav'
65      ],
66  'mythplugins'
67  =>  [
68        'tiff',
69        'exif',
70        'dvdcss',
71        # MythDVD used to use this to build mtd.
72        # Not needed since SVN -r8965. Kept here for 0.19 builds
73        # 'dvdread',
74# MythMusic needs these seven things:
75#        'libmad',
76#        'libid3tag',
77#        'libogg',
78#        'vorbis',
79#        'flac',
80# These CD ones don't compile on OS X. I doubt that they ever will.
81# MythMusic probably needs to have native OS X code to assess CDs
82#        'cddaparanoia',
83#        'cdaudio'
84      ],
85);
86
87our %depend = (
88
89  'freetype'
90  =>
91  {
92    'url'
93    =>  "$sourceforge/sourceforge/freetype/freetype-2.1.10.tar.gz",
94  },
95
96  # SVN head doesn't need this, but 0.19-fixes and earlier do
97  'dvdnav' =>
98  {
99    'url' => "$sourceforge/sourceforge/dvd/libdvdnav-0.1.10.tar.gz",
100  },
101
102  'lame'
103  =>
104  {
105    'url'
106    =>  "$sourceforge/sourceforge/lame/lame-3.96.1.tar.gz",
107    'conf'
108    =>  [
109          '--disable-frontend',
110        ],
111  },
112
113  'cdaudio' =>
114  {
115    'url' => "$sourceforge/sourceforge/libcdaudio/libcdaudio-0.99.12.tar.gz"
116  },
117
118  'libmad' =>
119  {
120    'url' => 'ftp://ftp.mars.org/pub/mpeg/libmad-0.15.0b.tar.gz'
121  },
122
123  'libid3tag' =>
124  {
125    'url' => 'ftp://ftp.mars.org/pub/mpeg/libid3tag-0.15.0b.tar.gz'
126  },
127
128  'cddaparanoia' =>
129  {
130    'url' => 'http://www.buserror.net/cdparanoia/cdparanoia-osx-5.tar.gz',
131  },
132
133  'libogg' =>
134  {
135    'url' => 'http://downloads.xiph.org/releases/ogg/libogg-1.1.2.tar.gz'
136  },   
137
138  'vorbis' =>
139  {
140    'url' => 'http://downloads.xiph.org/releases/vorbis/libvorbis-1.1.1.tar.gz'
141  },
142
143  'flac' =>
144  {
145    'url' => "$sourceforge/sourceforge/flac/flac-1.1.2.tar.gz"
146  },
147
148  'dvdcss'
149  =>
150  {
151    'url'
152    =>  'ftp://ftp.fu-berlin.de/unix/linux/mirrors/gentoo/distfiles/libdvdcss-1.2.8.tar.bz2',
153  },
154
155  'dvdread'
156  =>
157  {
158    'url'
159    =>  'http://www.dtek.chalmers.se/groups/dvd/dist/libdvdread-0.9.4.tar.gz',
160    'conf'
161    =>  [
162          '--with-libdvdcss',
163        ],
164  },
165
166 
167  'mysqlclient'
168  =>
169  {
170    'url'
171    => 'http://mirrors.dedipower.com/www.mysql.com/Downloads/MySQL-4.1/mysql-4.1.21.tar.gz',
172    'conf'
173    =>  [
174          '--without-debug',
175          '--without-docs',
176          '--without-man',
177          '--without-bench',
178          '--without-server',
179          '--without-geometry',
180          '--without-extra-tools',
181        ],
182  },
183 
184  'qt-mt'
185  =>
186#   {
187#     'url'
188#     =>  'http://ftp.iasi.roedu.net/mirrors/ftp.trolltech.com/qt/sources/qt-mac-opensource-src-4.0.1.tar.gz',
189#     'conf-cmd'
190#     =>  'echo yes | ./configure',
191#     'conf'
192#     =>  [
193#           '-prefix', '"$PREFIX"',
194#           '-system-zlib',
195#           '-fast',
196#           '-qt-sql-mysql',
197#           '-qt-libpng',
198#           '-qt-libjpeg',
199#           '-qt-gif',
200#           '-platform macx-g++',
201#           '-no-tablet',
202#           '-I"$PREFIX/include/mysql"',
203#           '-L"$PREFIX/lib/mysql"',
204#         ],
205#     'post-conf'
206#     =>  'echo "QMAKE_LFLAGS_SHLIB += -single_module" >> src/qt.pro',
207#     'make'
208#     =>  [
209#           'sub-src'
210#         ],
211#   },
212  {
213    'url'
214    =>  'http://ftp.iasi.roedu.net/mirrors/ftp.trolltech.com/qt/source/qt-mac-free-3.3.6.tar.gz',
215    'conf-cmd'
216    =>  'echo yes | ./configure',
217    'conf'
218    =>  [
219          '-prefix', '"$PREFIX"',
220          '-thread',
221          '-system-zlib',
222          '-fast',
223          '-qt-sql-mysql',
224          '-no-style-cde',
225          '-no-style-compact',
226          '-no-style-motif',
227          '-no-style-motifplus',
228          '-no-style-platinum',
229          '-no-style-sgi',
230          '-no-ipv6',
231          '-qt-imgfmt-png',
232          '-qt-imgfmt-jpeg',
233          '-no-imgfmt-mng',
234          '-qt-gif',
235          '-no-tablet',
236          '-I"$PREFIX/include/mysql"',
237          '-L"$PREFIX/lib/mysql"',
238        ],
239    'post-conf'
240    =>  'echo "QMAKE_LFLAGS_SHLIB += -single_module" >> src/qt.pro',
241    'make'
242    =>  [
243          'sub-src',
244          'qmake-install',
245          'moc-install'
246        ],
247  },
248 
249  'tiff'
250  =>
251  {
252    'url' => 'http://dl.maptools.org/dl/libtiff/tiff-3.8.2.tar.gz'
253  },
254 
255  'exif'
256  =>
257  {
258    'url'  => "$sourceforge/sourceforge/libexif/libexif-0.6.13.tar.bz2",
259    'conf' => [ '--disable-nls' ],
260    'post-conf' => 'echo "install-apidocs:
261install-apidocs-internals:
262" >> doc/Makefile.in'
263  }
264);
265
266
267=head1 NAME
268
269osx-packager.pl - build OS X binary packages for MythTV
270
271=head1 SYNOPSIS
272
273 osx-packager.pl [options]
274 
275 Options:
276   -help            print the usage message
277   -man             print full documentation
278   -verbose         print informative messages during the process
279   -version <str>   custom version suffix (defaults to "svnYYYYMMDD")
280   -noversion       don't use any version (for building release versions)
281   -distclean       throw away all intermediate files and exit
282   -thirdclean      do a clean rebuild of third party packages
283   -thirdskip       don't rebuild the third party packages
284   -mythtvskip      don't rebuild/install mythtv
285   -pluginskip      don't rebuild/install mythplugins
286   -themeskip       don't install the extra themes from myththemes
287   -clean           do a clean rebuild of MythTV
288   -svnbranch <str> build a specified Subversion branch,   instead of HEAD
289   -svnrev <str>    build a specified Subversion revision, instead of HEAD
290   -svntag <str>    build a specified release, instead of Subversion HEAD
291   -nohead          don't update to HEAD revision of MythTV before building
292   -usehdimage      perform build inside of a case-sensitive disk image
293   -enable-backend  build the backend server as well as the frontend
294   -enable-jobtools build commflag/jobqueue  as well as the frontend
295   -plugins <str>   comma-separated list of plugins to include
296                      Available plugins:
297   mythbrowser mythcontrols mythdvd mythflix mythgallery mythgame
298   mythmusic mythnews mythphone mythvideo mythweather
299
300=head1 DESCRIPTION
301
302This script builds a MythTV frontend and all necessary dependencies, along
303with plugins as specified, as a standalone binary package for Mac OS X.
304
305It was designed for building daily CVS (now Subversion) snapshots,
306but can also be used to create release builds with the '-svntag' option.
307
308All intermediate files go into an '.osx-packager' directory in the current
309working directory. The finished application is named 'MythFrontend.app' and
310placed in the current working directory.
311
312=head1 EXAMPLES
313
314Building two snapshots, one with plugins and one without:
315
316  osx-packager.pl -clean -plugins mythvideo,mythweather
317  mv MythFrontend.app MythFrontend-plugins.app
318  osx-packager.pl -nohead
319  mv MythFrontend.app MythFrontend-noplugins.app
320
321Building a 0.17 release build:
322
323  osx-packager.pl -distclean
324  osx-packager.pl -svntag release-0-17 -noversion
325
326Building a "fixes" branch:
327
328  osx-packager.pl -distclean
329  osx-packager.pl -svnbranch release-0-18-fixes
330
331=head1 CREDITS
332
333Written by Jeremiah Morris (jm@whpress.com)
334
335Special thanks to Nigel Pearson, Jan Ornstedt, Angel Li, and Andre Pang
336for help, code, and advice.
337
338Small modifications made by Bas Hulsken (bhulsken@hotmail.com) to allow building current svn, and allow lirc (if installed properly on before running script). The modifications are crappy, and should probably be revised by someone who can actually code in perl. However it works for the moment, and I wanted to share with other mac frontend experimenters!
339
340=cut
341
342# Parse options
343our (%OPT);
344Getopt::Long::GetOptions(\%OPT,
345                         'help|?',
346                         'man',
347                         'verbose',
348                         'version=s',
349                         'noversion',
350                         'distclean',
351                         'thirdclean',
352                         'thirdskip',
353                         'mythtvskip',
354                         'pluginskip',
355                         'themeskip',
356                         'clean',
357                         'svnbranch=s',
358                         'svnrev=s',
359                         'svntag=s',
360                         'nocvs', # This is obsolete, but should stay a while
361                         'nohead',
362                         'usehdimage',
363                         'enable-backend',
364                         'enable-jobtools',
365                         'plugins=s',
366                        ) or Pod::Usage::pod2usage(2);
367Pod::Usage::pod2usage(1) if $OPT{'help'};
368Pod::Usage::pod2usage('-verbose' => 2) if $OPT{'man'};
369
370if ( $OPT{'enable-backend'} )
371{   $backend = 1  }
372
373if ( $OPT{'clean'} )
374{   $cleanLibs = 1  }
375
376if ( $OPT{'enable-jobtools'} )
377{   $jobtools = 1  }
378
379# Get version string sorted out
380if ($OPT{'svntag'} && !$OPT{'version'})
381{
382  $OPT{'version'} = $OPT{'svntag'};
383  $OPT{'version'} =~ s/-r *//;
384  $OPT{'version'} =~ s/--revision *//;
385}
386$OPT{'version'} = '' if $OPT{'noversion'};
387unless (defined $OPT{'version'})
388{
389  my @lt = gmtime(time);
390  $OPT{'version'} = sprintf('svn%04d%02d%02d',
391                            $lt[5] + 1900,
392                            $lt[4] + 1,
393                            $lt[3]);
394}
395
396# Fold nocvs to nohead
397$OPT{'nohead'} = 1 if $OPT{'nocvs'};
398
399# Build our temp directories
400our $SCRIPTDIR = Cwd::abs_path(Cwd::getcwd());
401if ($SCRIPTDIR =~ /\s/)
402{
403  &Complain(<<END);
404Working directory contains spaces
405
406Error: Your current working path:
407
408   $SCRIPTDIR
409
410contains one or more spaces. This will break the compilation process,
411so the script cannot continue. Please re-run this script from a different
412directory (such as /tmp).
413
414The application produced will run from any directory, the no-spaces
415rule is only for the build process itself.
416
417END
418  die;
419}
420
421our $WORKDIR = "$SCRIPTDIR/.osx-packager";
422mkdir $WORKDIR;
423
424# Do we need to force a case-sensitive disk image?
425if (!$OPT{usehdimage} && !CaseSensitiveFilesystem())
426{
427  Verbose("Forcing -usehdimage due to case-insensitive filesystem");
428  $OPT{usehdimage} = 1;
429}
430
431if ($OPT{usehdimage})
432{
433  Verbose("Creating a case-sensitive device for the build");
434  MountHDImage();
435}
436
437our $PREFIX = "$WORKDIR/build";
438mkdir $PREFIX;
439
440our $SRCDIR = "$WORKDIR/src";
441mkdir $SRCDIR;
442
443# configure mythplugins, and mythtv, etc
444our %conf = (
445  'mythplugins'
446  =>  [
447        '--prefix=' . $PREFIX,
448        '--enable-opengl',
449        '--disable-mythbrowser',
450        '--enable-mythdvd',
451        '--enable-vcd',
452        '--disable-transcode',
453        '--enable-mythgallery',
454        '--enable-exif',
455        '--enable-new-exif',
456        '--disable-mythgame',
457        '--disable-mythmusic',
458        '--enable-mythnews',
459        '--disable-mythphone',
460        '--enable-mythweather',
461        '--disable-mytharchive',
462      ],
463  'myththemes'
464  =>  [
465        '--prefix=' . $PREFIX,
466      ],
467 'mythtv'
468 =>  [
469       '--disable-distcc',
470       '--disable-firewire',
471       '--enable-mmx',
472       '--arch=pentium-m',
473       '--enable-memalign-hack',
474 #      '--enable-mac-accel',
475       '--extra-cxxflags=-msse -msse2 -msse3',
476       '--prefix=' . $PREFIX,
477     ],
478
479);
480
481# configure mythplugins, and mythtv, etc
482our %makecleanopt = (
483  'mythplugins'
484  =>  [
485        'distclean',
486      ],
487);
488
489# Source code version.pro needs to call subversion binary
490#
491use File::Basename;
492our $svnpath = dirname $svn;
493
494# Clean the environment
495$ENV{'PATH'} = "$PREFIX/bin:/sw/bin:/bin:/usr/bin:$svnpath";
496$ENV{'DYLD_LIBRARY_PATH'} = "$PREFIX/lib";
497$ENV{'LD_LIBRARY_PATH'} = "/usr/local/lib";
498$ENV{'PKG_CONFIG_PATH'} = "$PREFIX/lib/pkgconfig:";
499delete $ENV{'CC'};
500delete $ENV{'CXX'};
501delete $ENV{'CPP'};
502delete $ENV{'CXXCPP'};
503$ENV{'CFLAGS'} = $ENV{'CXXFLAGS'} = $ENV{'CPPFLAGS'} = "-I$PREFIX/include";
504$ENV{'LDFLAGS'} = "-Z -F/System/Library/Frameworks -L/usr/lib -L$PREFIX/lib";
505$ENV{'PREFIX'} = $PREFIX;
506
507# set up Qt environment
508my $qt_vers = $depend{'qt-mt'}{'url'};
509$qt_vers =~ s|^.*/([^/]+)\.tar\.gz$|$1|;
510$ENV{'QTDIR'} = "$SRCDIR/$qt_vers";
511
512# If environment is setup to use distcc, take advantage of it
513our $standard_make = '/usr/bin/make';
514our $parallel_make = $standard_make;
515
516if ( $ENV{'DISTCC_HOSTS'} )
517{
518  my @hosts = split m/\s+/, $ENV{'DISTCC_HOSTS'};
519  my $numhosts = $#hosts + 1;
520  &Verbose("Using $numhosts build hosts:", join ', ', @hosts);
521  $parallel_make .= ' -j' . $numhosts * 2;
522}
523
524
525### Distclean?
526if ($OPT{'distclean'})
527{
528  &Syscall([ '/bin/rm', '-f', '-r', $PREFIX ]);
529  &Syscall([ '/bin/rm', '-f', '-r', $SRCDIR ]);
530  exit;
531}
532
533### Check for app present in target location
534our $MFE = "$SCRIPTDIR/MythFrontend.app";
535if (-d $MFE)
536{
537  &Complain(<<END);
538$MFE already exists
539
540Error: a MythFrontend application exists where we were planning
541to build one. Please move this application away before running
542this script.
543
544END
545  exit;
546}
547
548### Third party packages
549my (@build_depends, %seen_depends);
550my @comps = ('mythtv', @components);
551&Verbose("Including components:", @comps);
552
553foreach my $comp (@comps)
554{
555  foreach my $dep (@{ $depend_order{$comp} })
556  {
557    unless (exists $seen_depends{$dep})
558    {
559      push(@build_depends, $dep);
560      $seen_depends{$dep} = 1;
561    }
562  }
563}
564foreach my $sw (@build_depends)
565{
566  # Get info about this package
567  my $pkg = $depend{$sw};
568  my $url = $pkg->{'url'};
569  my $filename = $url;
570  $filename =~ s|^.+/([^/]+)$|$1|;
571  my $dirname = $filename;
572  $dirname =~ s|\.tar\.gz$||;
573  $dirname =~ s|\.tar\.bz2$||;
574
575  chdir($SRCDIR);
576 
577  # Download and decompress
578  unless (-e $filename)
579  {
580    &Verbose("Downloading $sw");
581    unless (&Syscall([ '/usr/bin/curl', $url, '>', $filename ],
582                     'munge' => 1))
583    {
584      &Syscall([ '/bin/rm', $filename ]) if (-e $filename);
585      die;
586    }
587  }
588  else
589  {
590    &Verbose("Using previously downloaded $sw");
591  }
592 
593  if ( -d $dirname )
594  {
595   if ( $OPT{'thirdclean'} )
596   {
597    &Verbose("Removing previous build of $sw");
598    &Syscall([ '/bin/rm', '-f', '-r', $dirname ]) or die;
599   }
600
601   if ( $OPT{'thirdskip'} )
602   {
603    &Verbose("Using previous build of $sw");
604    next;
605   }
606
607    &Verbose("Using previously unpacked $sw");
608  }
609  else
610  {
611    &Verbose("Unpacking $sw");
612    if ( substr($filename,-3) eq ".gz" )
613    {
614      &Syscall([ '/usr/bin/tar', '-xzf', $filename ]) or die;
615    }
616    elsif ( substr($filename,-4) eq ".bz2" )
617    {
618      &Syscall([ '/usr/bin/tar', '-xjf', $filename ]) or die;
619    }
620  }
621 
622  # Configure
623  chdir($dirname);
624  unless (-e '.osx-config')
625  {
626    &Verbose("Configuring $sw");
627    my (@configure, $munge);
628   
629    if ($pkg->{'conf-cmd'})
630    {
631      push(@configure, $pkg->{'conf-cmd'});
632      $munge = 1;
633    }
634    else
635    {
636      push(@configure, './configure',
637                       '--prefix=$PREFIX',
638                       '--disable-static',
639                       '--enable-shared');
640    }
641    if ($pkg->{'conf'})
642    {
643      push(@configure, @{ $pkg->{'conf'} });
644    }
645    &Syscall(\@configure, 'interpolate' => 1, 'munge' => $munge) or die;
646    if ($pkg->{'post-conf'})
647    {
648      &Syscall([ $pkg->{'post-conf'} ], 'munge' => 1) or die;
649    }
650    &Syscall([ '/usr/bin/touch', '.osx-config' ]) or die;
651  }
652  else
653  {
654    &Verbose("Using previously configured $sw");
655  }
656 
657  # Build and install
658  unless (-e '.osx-built')
659  {
660    &Verbose("Making $sw");
661    my (@make);
662   
663    push(@make, $standard_make);
664    if ($pkg->{'make'})
665    {
666      push(@make, @{ $pkg->{'make'} });
667    }
668    else
669    {
670      push(@make, 'all', 'install');
671    }
672    &Syscall(\@make) or die;
673    &Syscall([ '/usr/bin/touch', '.osx-built' ]) or die;
674  }
675  else
676  {
677    &Verbose("Using previously built $sw");
678  }
679}
680
681
682### build MythTV
683
684# Clean any previously installed libraries
685if ( $cleanLibs )
686{
687  if ( $OPT{'mythtvskip'} )
688  {
689    &Complain("Cannot skip building mythtv src if also cleaning");
690    exit;
691  }
692&Verbose("Cleaning previous installs of MythTV");
693my @mythlibs = glob "$PREFIX/lib/libmyth*";
694if (scalar @mythlibs)
695{
696  &Syscall([ '/bin/rm', @mythlibs ]) or die;
697}
698foreach my $dir ('include', 'lib', 'share')
699{
700  if (-d "$PREFIX/$dir/mythtv")
701  {
702    &Syscall([ '/bin/rm', '-f', '-r', "$PREFIX/$dir/mythtv" ]) or die;
703  }
704}
705}
706
707my $svndir = "$SRCDIR/myth-svn";
708mkdir $svndir;
709
710# Deal with Subversion branches, revisions and tags:
711my $svnrepository = 'http://svn.mythtv.org/svn/';
712my @svnrevision   = ();
713
714if ( $OPT{'svnbranch'} )
715{
716  $svnrepository .= 'branches/' . $OPT{'svnbranch'} . '/';
717}
718elsif ( $OPT{'svntag'} )
719{
720  $svnrepository .= 'tags/' . $OPT{'svntag'} . '/';
721}
722elsif ( $OPT{'svnrev'} )
723{
724  $svnrepository .= 'trunk/';
725
726  # This arg. could be '1234', '-r 1234', or '--revision 1234'
727  # If the user just specified a number, add appropriate flag:
728  if ( $OPT{'svnrev'} =~ m/^\d+$/ )
729  {
730    push @svnrevision, '--revision';
731  }
732
733  push @svnrevision, $OPT{'svnrev'};
734}
735elsif ( ! $OPT{'nohead'} )
736{
737  # Lookup and use the HEAD revision so we are guaranteed consistent source
738  my $cmd = "$svn log $svnrepository --revision HEAD --xml | grep revision";
739  &Verbose($cmd);
740  my $rev = `$cmd`;
741  $rev =~ s/[^[:digit:]]//gs;
742
743  #spit out the revision number
744  print SVNVER $rev;
745
746  $svnrepository .= 'trunk/';
747  @svnrevision = ('--revision', $rev);
748}
749
750# Retrieve source
751if (! $OPT{'nohead'})
752{
753  # Empty subdirectory 'config' sometimes causes checkout problems
754  &Syscall(['rm', '-fr', $svndir . '/mythtv/config']);
755  Verbose("Checking out source code");
756  &Syscall([ $svn, 'co', @svnrevision,
757            map($svnrepository . $_, @comps), $svndir ]) or die;
758}
759else
760{
761  &Syscall("mkdir -p $svndir/mythtv/config")
762}
763
764# Deal with user-supplied skip arguments
765if ( $OPT{'mythtvskip'} )
766{   @comps = grep(!m/mythtv/,      @comps)   }
767if ( $OPT{'pluginskip'} )
768{   @comps = grep(!m/mythplugins/, @comps)   }
769if ( $OPT{'themeskip'} )
770{   @comps = grep(!m/myththemes/,  @comps)   }
771
772if ( ! @comps )
773{
774  &Complain("Nothing to build! Too many ...skip arguments?");
775  exit;
776}
777
778# Build MythTV and any plugins
779foreach my $comp (@comps)
780{
781  my $compdir = "$svndir/$comp/" ;
782
783  chdir $compdir;
784
785  if ( ! -e "$comp.pro" )
786  {
787    &Complain("$compdir/$comp.pro does not exist.",
788              'You must be building really old source?');
789    next;
790  }
791 
792  if ($comp eq 'mythtv')
793  {
794    # MythTV has an empty subdirectory 'config' that causes problems for me:
795    &Syscall('touch config/config.pro');
796  }
797
798  if ($OPT{'clean'} && -e 'Makefile')
799  {
800    &Verbose("Cleaning $comp");
801    &Syscall([ $standard_make, 'distclean' ]) or die;
802  }
803  else
804  {
805    # clean the Makefiles, as process requires PREFIX hacking
806    &CleanMakefiles();
807  }
808 
809  # Apply any nasty mac-specific patches
810  if ($patches{$comp})
811  {
812    &Syscall([ "echo '$patches{$comp}' | patch -p0 --forward" ]);
813  }
814 
815  # configure and make
816  if ( $makecleanopt{$comp} && -e 'Makefile' )
817  {
818    my @makecleancom= $standard_make;
819    push(@makecleancom, @{ $makecleanopt{$comp} }) if $makecleanopt{$comp};
820    &Syscall([ @makecleancom ]) or die;
821  }
822  if (-e 'configure')
823  {
824    &Verbose("Configuring $comp");
825    my @config = './configure';
826    push(@config, @{ $conf{$comp} }) if $conf{$comp};
827    if ( $comp eq 'mythtv' && $backend )
828    {
829      push @config, '--enable-backend'
830    }
831    if ( $comp eq 'mythtv' && ! $ENV{'DISTCC_HOSTS'} )
832    {
833      push @config, '--disable-distcc'
834    }
835    &Syscall([ @config ]) or die;
836  }
837  &Verbose("Running qmake for $comp");
838  my @qmake_opts = (
839    'QMAKE_LFLAGS+=-Wl,-search_paths_first',
840    'INCLUDEPATH+="' . $PREFIX . '/include"',
841    'LIBS+=-L/usr/lib -L"' . $PREFIX . '/lib"'
842   );
843  &Syscall([ $PREFIX . '/bin/qmake',
844             'PREFIX=../Resources',
845             @qmake_opts,
846             "$comp.pro" ]) or die;
847
848  if ($comp eq 'mythtv')
849  {
850    # Remove/add Nigel's frontend building speedup hack
851    &DoSpeedupHacks('programs/programs.pro', 'mythfrontend mythtv');
852  }
853 
854  &Verbose("Making $comp");
855  &Syscall([ $parallel_make ]) or die;
856  # install
857  # This requires a change from the compiled-in relative
858  # PREFIX to our absolute path of the temp install location.
859  &CleanMakefiles();
860  &Verbose("Running qmake for $comp install");
861  &Syscall([ $PREFIX . '/bin/qmake',
862             'PREFIX=' . $PREFIX,
863             @qmake_opts,
864             "$comp.pro" ]) or die;
865  &Verbose("Installing $comp");
866  &Syscall([ $standard_make,
867             'install' ]) or die;
868
869  if ($cleanLibs && $comp eq 'mythtv')
870  {
871    # If we cleaned the libs, make install will have recopied them,
872    # which means any dynamic libraries that the static libraries depend on
873    # are newer than the table of contents. Hence we need to regenerate it:
874    my @mythlibs = glob "$PREFIX/lib/libmyth*.a";
875    if (scalar @mythlibs)
876    {
877      &Verbose("Running ranlib on reinstalled static libraries");
878      foreach my $lib (@mythlibs)
879      { &Syscall("ranlib $lib") or die }
880    }
881  }
882}
883
884### Build version string
885our $VERS = `find $PREFIX/lib -name 'libmyth-[0-9].[0-9][0-9].[0-9].dylib'`;
886chomp $VERS;
887$VERS =~ s/^.*\-(.*)\.dylib$/$1/s;
888$VERS .= '.' . $OPT{'version'} if $OPT{'version'};
889
890### Create each package.
891### Note that this is a bit of a waste of disk space,
892### because there are now multiple copies of each library.
893my @targets = ('MythFrontend', 'MythTV');
894
895if ( $jobtools )
896{   push @targets, @targetsJT   }
897
898if ( $backend )
899{   push @targets, @targetsBE   }
900
901foreach my $target ( @targets )
902{
903  my $finalTarget = "$SCRIPTDIR/$target.app";
904  my $builtTarget = $target;
905  $builtTarget =~ tr/[A-Z]/[a-z]/;
906
907  # Get a fresh copy of the app
908  &Verbose("Building self-contained $target");
909  &Syscall([ 'rm', '-fr', $finalTarget ]) or die;
910  &RecursiveCopy("$PREFIX/bin/$builtTarget.app", $finalTarget);
911 
912  # write a custom Info.plist
913  &GeneratePlist($target, $builtTarget, $finalTarget, $VERS);
914 
915  # Make frameworks from Myth libraries
916  &Verbose("Installing frameworks into $target");
917  &PackagedExecutable($finalTarget, $builtTarget);
918 
919 if ( $target eq "MythFrontend" or $target =~ m/^MythTV/ )
920 {
921  # Install themes, filters, etc.
922  &Verbose("Installing resources into $target");
923  mkdir "$finalTarget/Contents/Resources";
924  mkdir "$finalTarget/Contents/Resources/lib";
925  &RecursiveCopy("$PREFIX/lib/mythtv",
926                 "$finalTarget/Contents/Resources/lib");
927  mkdir "$finalTarget/Contents/Resources/share";
928  &RecursiveCopy("$PREFIX/share/mythtv",
929                 "$finalTarget/Contents/Resources/share");
930 }
931
932  if ( $target eq "MythFrontend" )
933  {
934     my $mtd = "$svndir/mythplugins/mythdvd/mtd/mtd.app/Contents/MacOS/mtd";
935     if ( -e $mtd )
936     {
937       &Verbose("Installing $mtd into $target");
938       &Syscall([ 'cp', $mtd, "$finalTarget/Contents/MacOS" ]) or die;
939       &AddFakeBinDir($finalTarget);
940     }
941  }
942}
943
944if ( $backend )
945{
946  my $BE = "$SCRIPTDIR/MythBackend.app";
947
948  # The backend gets all the useful binaries it might call:
949  foreach my $binary ( 'mythjobqueue', 'mythcommflag', 'mythtranscode' )
950  {
951    my $SRC  = "$PREFIX/bin/$binary.app/Contents/MacOS/$binary";
952    if ( -e $SRC )
953    {
954      &Syscall([ '/bin/cp', $SRC, "$BE/Contents/MacOS" ]) or die;
955      &PackagedExecutable($BE, $binary);
956      &AddFakeBinDir($BE);
957    }
958  }
959}
960
961if ( $jobtools )
962{
963  # JobQueue also gets some binaries it might call:
964  my $JQ   = "$SCRIPTDIR/MythJobQueue.app";
965  my $DEST = "$JQ/Contents/MacOS";
966  my $SRC  = "$PREFIX/bin/mythcommflag.app/Contents/MacOS/mythcommflag";
967
968  &Syscall([ '/bin/cp', $SRC, $DEST ]) or die;
969  &PackagedExecutable($JQ, 'mythcommflag');
970
971  $SRC  = "$PREFIX/bin/mythtranscode.app/Contents/MacOS/mythtranscode";
972  if ( -e $SRC )
973  {
974      &Syscall([ '/bin/cp', $SRC, $DEST ]) or die;
975      &PackagedExecutable($JQ, 'mythtranscode');
976      &AddFakeBinDir($JQ);
977  }
978}
979
980if ($OPT{usehdimage})
981{
982    Verbose("Dismounting case-sensitive build device");
983    UnmountHDImage();
984}
985
986&Verbose("Build complete. Self-contained package is at:\n\n    $MFE\n");
987
988
989### end script
990exit 0;
991
992
993######################################
994## RecursiveCopy copies a directory tree, stripping out .svn
995## directories and properly managing static libraries.
996######################################
997
998sub RecursiveCopy
999{
1000    my ($src, $dst) = @_;
1001
1002    # First copy absolutely everything
1003    &Syscall([ '/bin/cp', '-R', "$src", "$dst"]) or die;
1004
1005    # Then strip out any .svn directories
1006    my @files = map { chomp $_; $_ } `find $dst -name .svn`;
1007    if (scalar @files)
1008    {
1009        &Syscall([ '/bin/rm', '-f', '-r', @files ]);
1010    }
1011
1012    # And make sure any static libraries are properly relocated.
1013    my @libs = map { chomp $_; $_ } `find $dst -name "lib*.a"`;
1014    if (scalar @libs)
1015    {
1016        &Syscall([ 'ranlib', '-s', @libs ]);
1017    }
1018}
1019
1020######################################
1021## Given an application package $finalTarget and an executable
1022## $builtTarget that has been copied into it, PackagedExecutable
1023## makes sure the package contains all the library dependencies as
1024## frameworks and that all the paths internal to the executable have
1025## been adjusted appropriately.
1026######################################
1027
1028sub PackagedExecutable($$)
1029{
1030    my ($finalTarget, $builtTarget) = @_;
1031
1032    my $fw_dir = "$finalTarget/Contents/Frameworks";
1033    mkdir $fw_dir;
1034
1035    my $dephash
1036        = &ProcessDependencies("$finalTarget/Contents/MacOS/$builtTarget",
1037                               glob "$PREFIX/lib/mythtv/*/*.dylib");
1038    my @deps = values %$dephash;
1039    while (scalar @deps)
1040    {
1041        my $dep = shift @deps;
1042
1043        my $file = &MakeFramework(&FindLibraryFile($dep), $fw_dir);
1044        if ( $file )
1045        {
1046            my $newhash = &ProcessDependencies($file);
1047            foreach my $base (keys %$newhash)
1048            {
1049                next if exists $dephash->{$base};
1050                $dephash->{$base} = $newhash->{$base};
1051                push(@deps, $newhash->{$base});
1052            }
1053        }
1054    }
1055}
1056
1057
1058######################################
1059## MakeFramework copies a dylib into a
1060## framework bundle.
1061######################################
1062
1063sub MakeFramework
1064{
1065  my ($dylib, $dest) = @_;
1066 
1067  my ($base, $vers) = &BaseVers($dylib);
1068  $vers .= '.' . $OPT{'version'} if ($OPT{'version'} && $base =~ /myth/);
1069  my $fw_dir = $dest . '/' . $base . '.framework';
1070
1071  return '' if ( -e $fw_dir );
1072
1073  &Verbose("Building $base framework");
1074 
1075  &Syscall([ '/bin/mkdir',
1076             '-p',
1077             "$fw_dir/Versions/A/Resources" ]) or die;
1078  &Syscall([ '/bin/cp',
1079             $dylib,
1080             "$fw_dir/Versions/A/$base" ]) or die;
1081
1082  &Syscall([ '/usr/bin/install_name_tool',
1083             '-id',
1084             $base,
1085             "$fw_dir/Versions/A/$base" ]) or die;
1086 
1087  symlink('A', "$fw_dir/Versions/Current") or die;
1088  symlink('Versions/Current/Resources', "$fw_dir/Resources") or die;
1089  symlink("Versions/A/$base", "$fw_dir/$base") or die;
1090 
1091  &Verbose("Writing Info.plist for $base framework");
1092  my $plist;
1093  unless (open($plist, '>' . "$fw_dir/Versions/A/Resources/Info.plist"))
1094  {
1095    &Complain("Failed to open $base framework's plist for writing");
1096    die;
1097  }
1098  print $plist <<END;
1099<?xml version="1.0" encoding="UTF-8"?>
1100<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1101<plist version="1.0">
1102<dict>
1103  <key>CFBundleName</key>
1104  <string>$base</string>
1105  <key>CFBundleIdentifier</key>
1106  <string>org.mythtv.macx.$base</string>
1107  <key>CFBundleVersion</key>
1108  <string>$vers</string>
1109  <key>CFBundleSignature</key>
1110  <string>Myth</string>
1111  <key>CFBundlePackageType</key>
1112  <string>FMWK</string>
1113  <key>NSHumanReadableCopyright</key>
1114  <string>Packaged for the MythTV project, www.mythtv.org</string>
1115  <key>CFBundleGetInfoString</key>
1116  <string>lib$base-$vers.dylib, packaged for the MythTV project, www.mythtv.org</string>
1117</dict>
1118</plist>
1119END
1120  close($plist);
1121 
1122  return "$fw_dir/Versions/A/$base";
1123} # end MakeFramework
1124
1125
1126######################################
1127## GeneratePlist .
1128######################################
1129
1130sub GeneratePlist
1131{
1132  my ($name, $binary, $path, $vers) = @_;
1133 
1134  &Verbose("Writing Info.plist for $name");
1135  my $plist;
1136  $path .= '/Contents/Info.plist';
1137  unless (open($plist, ">$path"))
1138  {
1139    &Complain("Could not open $path for writing");
1140    die;
1141  }
1142  print $plist <<END;
1143<?xml version="1.0" encoding="UTF-8"?>
1144<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1145<plist version="1.0">
1146<dict>
1147  <key>CFBundleExecutable</key>
1148  <string>$binary</string>
1149  <key>CFBundleIconFile</key>
1150  <string>application.icns</string>
1151  <key>CFBundleIdentifier</key>
1152  <string>org.mythtv.macx.$binary</string>
1153  <key>CFBundleInfoDictionaryVersion</key>
1154  <string>6.0</string>
1155  <key>CFBundlePackageType</key>
1156  <string>APPL</string>
1157  <key>CFBundleShortVersionString</key>
1158  <string>$vers</string>
1159  <key>CFBundleSignature</key>
1160  <string>Myth</string>
1161  <key>CFBundleVersion</key>
1162  <string>$vers</string>
1163  <key>NSAppleScriptEnabled</key>
1164  <string>NO</string>
1165  <key>CFBundleGetInfoString</key>
1166  <string>$vers, MythTV project, www.mythtv.org</string>
1167  <key>CFBundleName</key>
1168  <string>$name</string>
1169  <key>NSHumanReadableCopyright</key>
1170  <string>MythTV project, www.mythtv.org</string>
1171</dict>
1172</plist>
1173END
1174  close($plist);
1175 
1176  $path =~ s/Info\.plist$/PkgInfo/;
1177  unless (open($plist, ">$path"))
1178  {
1179    &Complain("Could not open $path for writing");
1180    die;
1181  }
1182  print $plist <<END;
1183APPLMyth
1184END
1185  close($plist);
1186}
1187
1188######################################
1189## FindLibraryFile locates a dylib.
1190######################################
1191
1192sub FindLibraryFile
1193{
1194  my ($dylib) = @_;
1195 
1196  return Cwd::abs_path($dylib) if (-e $dylib);
1197  return Cwd::abs_path("$PREFIX/lib/$dylib") if (-e "$PREFIX/lib/$dylib");
1198  return Cwd::abs_path("$ENV{'QTDIR'}/lib/$dylib") if (-e "$ENV{'QTDIR'}/lib/$dylib");
1199  my @sublibs = glob("$PREFIX/lib/*/$dylib");
1200  return Cwd::abs_path($sublibs[0]) if (scalar @sublibs);
1201  &Complain("Could not find $dylib");
1202  die;
1203} # end FindLibraryFile
1204
1205
1206######################################
1207## ProcessDependencies catalogs and
1208## rewrites dependencies that will be
1209## packaged into our app bundle.
1210######################################
1211
1212sub ProcessDependencies
1213{
1214  my (%depfiles);
1215 
1216  foreach my $file (@_)
1217  {
1218    &Verbose("Processing shared library dependencies for $file");
1219    my ($filebase) = &BaseVers($file);
1220   
1221    my $cmd = "otool -L $file";
1222    &Verbose($cmd);
1223    my @deps = `$cmd`;
1224    shift @deps;  # first line just repeats filename
1225    &Verbose("Dependencies for $file = @deps");
1226    foreach my $dep (@deps)
1227    {
1228      chomp $dep;
1229
1230      # otool returns lines like:
1231      #    libblah-7.dylib   (compatibility version 7, current version 7)
1232      # but we only want the file part
1233      $dep =~ s/\s+(.*) \(.*\)$/$1/;
1234
1235      # Paths like /usr/lib/libstdc++ contain quantifiers that must be escaped
1236      $dep =~ s/([+*?])/\\$1/;
1237     
1238      # otool sometimes lists the framework as depending on itself
1239      next if ($file =~ m,/Versions/A/$dep,);
1240
1241      # Any dependency which is already package relative can be ignored
1242      next if $dep =~ m/\@executable_path/;
1243
1244      # skip system library locations
1245      next if ($dep =~ m|^/System|  ||
1246               $dep =~ m|^/usr/lib|);
1247     
1248      my ($base) = &BaseVers($dep);
1249      $depfiles{$base} = $dep;
1250     
1251      # Change references while we're here
1252      &Syscall([ '/usr/bin/install_name_tool',
1253                 '-change',
1254                 $dep,
1255                 "\@executable_path/../Frameworks/$base.framework/$base",
1256                 $file ]) or die;
1257    }
1258  }
1259  return \%depfiles;
1260} # end ProcessDependencies
1261
1262
1263######################################
1264## BaseVers splits up a dylib file
1265## name for framework naming.
1266######################################
1267
1268sub BaseVers
1269{
1270  my ($filename) = @_;
1271 
1272  if ($filename =~ m|^(?:.*/)?lib(.*)\-(\d.*)\.dylib$|)
1273  {
1274    return ($1, $2);
1275  }
1276  elsif ($filename =~ m|^(?:.*/)?lib(.*?)\.(\d.*)\.dylib$|)
1277  {
1278    return ($1, $2);
1279  }
1280  elsif ($filename =~ m|^(?:.*/)?lib(.*?)\.dylib$|)
1281  {
1282    return ($1, undef);
1283  }
1284 
1285  &Verbose("Not a library file: $filename");
1286  return $filename;
1287} # end BaseVers
1288 
1289
1290######################################
1291## CleanMakefiles removes every
1292## Makefile from our MythTV build.
1293## Necessary when we change the
1294## PREFIX variable.
1295######################################
1296
1297sub CleanMakefiles
1298{
1299  &Verbose("Cleaning MythTV makefiles");
1300  my @files = map { chomp $_; $_ } `find . -name Makefile`;
1301  if (scalar @files)
1302  {
1303    &Syscall([ '/bin/rm', @files ]) or die;
1304  }
1305} # end CleanMakefiles
1306
1307
1308######################################
1309## Syscall wrappers the Perl "system"
1310## routine with verbosity and error
1311## checking.
1312######################################
1313
1314sub Syscall
1315{
1316  my ($arglist, %opts) = @_;
1317 
1318  unless (ref $arglist)
1319  {
1320    $arglist = [ $arglist ];
1321  }
1322  if ($opts{'interpolate'})
1323  {
1324    my @args;
1325    foreach my $arg (@$arglist)
1326    {
1327      $arg =~ s/\$PREFIX/$PREFIX/ge;
1328      push(@args, $arg);
1329    }
1330    $arglist = \@args;
1331  }
1332  if ($opts{'munge'})
1333  {
1334    $arglist = [ join(' ', @$arglist) ];
1335  }
1336  # clean out any null arguments
1337  $arglist = [ map $_, @$arglist ];
1338  &Verbose(@$arglist);
1339  my $ret = system(@$arglist);
1340  if ($ret)
1341  {
1342    &Complain('Failed system call: "', @$arglist,
1343              '" with error code', $ret >> 8);
1344  }
1345  return ($ret == 0);
1346} # end Syscall
1347
1348
1349######################################
1350## Verbose prints messages in verbose
1351## mode.
1352######################################
1353
1354sub Verbose
1355{
1356  print STDERR '[osx-pkg] ' . join(' ', @_) . "\n"
1357      if $OPT{'verbose'};
1358} # end Verbose
1359
1360
1361######################################
1362## Complain prints messages in any
1363## verbosity mode.
1364######################################
1365
1366sub Complain
1367{
1368  print STDERR '[osx-pkg] ' . join(' ', @_) . "\n";
1369} # end Complain
1370
1371
1372######################################
1373## Manage usehdimage disk image
1374######################################
1375
1376sub MountHDImage
1377{
1378    if (!HDImageDevice())
1379    {
1380        if (! -e "$SCRIPTDIR/.osx-packager.dmg")
1381        {
1382            Syscall(['hdiutil', 'create', '-size', '1300m',
1383                     "$SCRIPTDIR/.osx-packager.dmg", '-volname',
1384                     'MythTvPackagerHDImage', '-fs', 'UFS', '-quiet']);
1385        }
1386
1387        Syscall(['hdiutil', 'mount', "$SCRIPTDIR/.osx-packager.dmg",
1388                 '-mountpoint', $WORKDIR, '-quiet']);
1389    }
1390
1391    # configure defaults to /tmp and OSX barfs when mv crosses
1392    # filesystems so tell configure to put temp files on the image
1393
1394    $ENV{TMPDIR} = $WORKDIR . "/tmp";
1395    mkdir $ENV{TMPDIR};
1396}
1397
1398sub UnmountHDImage
1399{
1400    my $device = HDImageDevice();
1401    if ($device)
1402    {
1403        &Syscall(['hdiutil', 'detach', $device, '-force']);
1404    }
1405}
1406
1407sub HDImageDevice
1408{
1409    my @dev = split ' ', `/sbin/mount | grep $WORKDIR`;
1410    $dev[0];
1411}
1412
1413sub CaseSensitiveFilesystem
1414{
1415  my $funky = $SCRIPTDIR . "/.osx-packager.FunkyStuff";
1416  my $unfunky = substr($funky, 0, -10) . "FUNKySTuFF";
1417
1418  unlink $funky if -e $funky;
1419  `touch $funky`;
1420  my $sensitivity = ! -e $unfunky;
1421  unlink $funky;
1422
1423  return $sensitivity;
1424}
1425
1426
1427######################################
1428## Remove or add Nigel's speedup hacks
1429######################################
1430
1431sub DoSpeedupHacks($$)
1432{
1433  my ($file, $subdirs) = @_;
1434
1435  &Verbose("Removing Nigel's hacks from file $file");
1436
1437  open(IN,  $file)         or die;
1438  open(OUT, ">$file.orig") or die;
1439  while ( <IN> )
1440  {
1441    if ( m/^# Nigel/ )  # Skip
1442    {  last  }
1443    print OUT;
1444  }
1445  if ( ! $backend && ! $jobtools )
1446  {
1447    # Nigel's hack to speedup building
1448    print OUT "# Nigel\'s speedup hack:\n";
1449    print OUT "SUBDIRS = $subdirs\n";
1450  }
1451  close IN; close OUT;
1452  rename("$file.orig", $file);
1453}
1454
1455#######################################################
1456## Parts of MythTV try to call helper apps like this:
1457## gContext->GetInstallPrefix() + "/bin/mythtranscode";
1458## which means we need a bin directory.
1459#######################################################
1460
1461sub AddFakeBinDir($)
1462{
1463  my ($target) = @_;
1464
1465  &Syscall(['ln', '-sf', '../MacOS', "$target/Contents/Resources/bin"]);
1466}
1467
1468### end file
14691;