This version differs from the original at http://whpress.com/mythtv/ in the following:
* It uses the new 'configure' script
* It uses the new directory layout (mythtv, myththemes and mythplugins)
* It downloads MySQL 4.1.11 instead of 4.1.9
* It downloads libtiff 3.7.2 instead of 3.7.1
* It is less elegant. perl is not my native language :-)
Eggert
#!/usr/bin/perl ### osx-packager.pl ### Tool for automating frontend builds on Mac OS X. ### Run "osx-packager.pl -man" for full documentation.
use strict;
use Getopt::Long qw(:config auto_abbrev);
use Pod::Usage ();
use Cwd ();
### Configuration settings (stuff that might change more often)
our @comps = ('mythtv', 'myththemes', 'mythplugins');
our @allplugins = ( 'mythweather', 'mythnews', 'mythgame', 'mythmusic',
'mythgallery', 'mythvideo', 'mythdvd', 'mythbrowser', 'mythphone' );
our @plugin_config = ( '--enable-opengl', '--enable-exif' );
our %depend_order = (
'mythtv'
=> [
'freetype',
'lame',
'mysqlclient',
'qt-mt'
],
'mythplugins'
=> [
'tiff',
'exif'
],
);
our %depend = (
'freetype'
=>
{
'url'
=> 'http://unc.dl.sf.net/sourceforge/freetype/freetype-2.1.9.tar.gz',
},
'lame'
=>
{
'url'
=> 'http://unc.dl.sf.net/sourceforge/lame/lame-3.96.1.tar.gz',
'conf'
=> [
'--disable-frontend',
],
},
'mysqlclient'
=>
{
'url'
=> 'http://mysql.mirrors.pair.com/Downloads/MySQL-4.1/mysql-4.1.11.tar.gz',
'conf'
=> [
'--without-debug',
'--without-docs',
'--without-man',
'--without-bench',
'--without-server',
'--without-geometry',
'--without-extra-tools',
],
},
'qt-mt'
=>
{
'url'
=> 'http://sunsite.rediris.es/mirror/Qt/source/qt-mac-free-3.3.3.tar.gz',
'conf-cmd'
=> 'echo yes | ./configure',
'conf'
=> [
'-prefix', '"$PREFIX"',
'-thread',
'-system-zlib',
'-fast',
'-qt-sql-mysql',
'-no-style-cde',
'-no-style-compact',
'-no-style-mac',
'-no-style-motif',
'-no-style-motifplus',
'-no-style-platinum',
'-no-style-sgi',
'-no-ipv6',
'-qt-imgfmt-png',
'-qt-imgfmt-jpeg',
'-no-imgfmt-mng',
'-qt-gif',
'-no-tablet',
'-I"$PREFIX/include/mysql"',
'-L"$PREFIX/lib/mysql"',
],
'post-conf'
=> 'echo "QMAKE_LFLAGS_SHLIB += -single_module" >> src/qt.pro',
'make'
=> [
'sub-src',
'qmake-install',
'moc-install'
],
},
'tiff'
=>
{
'url'
=> 'ftp://ftp.remotesensing.org/pub/libtiff/tiff-3.7.2.tar.gz',
'conf'
=> [
'--disable-cxx', # Workaround for bug in libtiff 3.7.2. Can be removed in 3.7.3 (?). See http://www.asmail.be/msg0054783146.html
],
},
'exif'
=>
{
'url'
=> 'http://unc.dl.sf.net/sourceforge/libexif/libexif-0.5.12.tar.gz',
'conf'
=> [
'--disable-nls',
],
},
);
=head1 NAME
osx-packager.pl - build OS X binary packages for MythTV
=head1 SYNOPSIS
osx-packager.pl [options]
Options:
-help print the usage message
-man print full documentation
-verbose print informative messages during the process
-version <str> custom version suffix (defaults to "cvsYYYYMMDD")
-noversion don't use any version (for building release versions)
-distclean throw away all intermediate files and exit
-thirdclean do a clean rebuild of third party packages
-clean do a clean rebuild of MythTV
-cvstag <str> build specified CVS release, instead of HEAD
-nocvs don't update MythTV CVS before building
-plugins <str> comma-separated list of plugins to include
Available plugins:
mythvideo mythweather mythgallery mythgame
mythdvd mythnews ALL
=head1 DESCRIPTION
This script builds a MythTV frontend and all necessary dependencies, along
with plugins as specified, as a standalone binary package for Mac OS X.
It was designed for building daily CVS snapshots, but can also be used to
create release builds with the '-cvstag' option.
All intermediate files go into an '.osx-packager' directory in the current
working directory. The finished application is named 'MythFrontend.app' and
placed in the current working directory.
=head1 EXAMPLES
Building two snapshots, one with plugins and one without:
osx-packager.pl -clean -plugins mythvideo,mythweather
mv MythFrontend.app MythFrontend-plugins.app
osx-packager.pl -nocvs
mv MythFrontend.app MythFrontend-noplugins.app
Building a 0.17 release build:
osx-packager.pl -distclean
osx-packager.pl -cvstag release-0-17 -noversion
=head1 CREDITS
Written by Jeremiah Morris ([EMAIL PROTECTED])
Special thanks to Nigel Pearson, Jan Ornstedt, Angel Li, and Andre Pang
for help, code, and advice.
=cut
# Parse options
our (%OPT);
Getopt::Long::GetOptions(\%OPT,
'help|?',
'man',
'verbose',
'version=s',
'noversion',
'distclean',
'thirdclean',
'thirdskip',
'clean',
'cvstag=s',
'nocvs',
'plugins=s',
) or Pod::Usage::pod2usage(2);
Pod::Usage::pod2usage(1) if $OPT{'help'};
Pod::Usage::pod2usage('-verbose' => 2) if $OPT{'man'};
# Get version string sorted out
$OPT{'version'} = $OPT{'cvstag'} if ($OPT{'cvstag'} && !$OPT{'version'});
$OPT{'version'} = '' if $OPT{'noversion'};
unless (defined $OPT{'version'})
{
my @lt = gmtime(time);
$OPT{'version'} = sprintf('cvs%04d%02d%02d',
$lt[5] + 1900,
$lt[4] + 1,
$lt[3]);
}
$OPT{'cvstag'} = 'HEAD' unless $OPT{'cvstag'};
# Build our temp directories
our $SCRIPTDIR = Cwd::abs_path(Cwd::getcwd());
if ($SCRIPTDIR =~ /\s/)
{
&Complain(<<END);
Working directory contains spaces
Error: Your current working path:
$SCRIPTDIR
contains one or more spaces. This will break the compilation process,
so the script cannot continue. Please re-run this script from a different
directory (such as /tmp).
The application produced will run from any directory, the no-spaces
rule is only for the build process itself.
END
die;
}
mkdir "$SCRIPTDIR/.osx-packager";
our $PREFIX = "$SCRIPTDIR/.osx-packager/build";
mkdir $PREFIX;
our $SRCDIR = "$SCRIPTDIR/.osx-packager/src";
mkdir $SRCDIR;
# Clean the environment
$ENV{'PATH'} = "$PREFIX/bin:/bin:/usr/bin";
$ENV{'DYLD_LIBRARY_PATH'} = "$PREFIX/lib";
delete $ENV{'CC'};
delete $ENV{'CXX'};
delete $ENV{'CPP'};
delete $ENV{'CXXCPP'};
$ENV{'CFLAGS'} = $ENV{'CXXFLAGS'} = $ENV{'CPPFLAGS'} = "-I$PREFIX/include";
$ENV{'LDFLAGS'} = "-Z -F/System/Library/Frameworks -L/usr/lib -L$PREFIX/lib";
$ENV{'PREFIX'} = $PREFIX;
# set up Qt environment
my $qt_vers = $depend{'qt-mt'}{'url'};
$qt_vers =~ s|^.*/([^/]+)\.tar\.gz$|$1|;
$ENV{'QTDIR'} = "$SRCDIR/$qt_vers";
### Distclean?
if ($OPT{'distclean'})
{
&Syscall([ '/bin/rm', '-f', '-r', $PREFIX ]);
&Syscall([ '/bin/rm', '-f', '-r', $SRCDIR ]);
exit;
}
### Check for app present in target location
our $MFE = "$SCRIPTDIR/MythFrontend.app";
if (-d $MFE)
{
&Complain(<<END);
$MFE already exists
Error: a MythFrontend application exists where we were planning
to build one. Please move this application away before running
this script.
END
exit;
}
### Components and plugins.
my (@build_depends, %seen_depends, @plugins, %plugin_hash);
@plugins = ();
if ($OPT{'plugins'} eq 'ALL')
{
push(@plugins, @allplugins);
}
elsif ($OPT{'plugins'})
{
push(@plugins, split(',', $OPT{'plugins'}));
}
&Verbose("Including plugins:", @plugins);
# Convert @plugins list into list of "--enable-X" and "--disable-X"
undef %plugin_hash;
for (@plugins) { $plugin_hash{$_} = 1; }
foreach my $plugin (@allplugins)
{
if ($plugin_hash{$plugin}) {
push(@plugin_config, '--enable-' . $plugin);
} else {
push(@plugin_config, '--disable-' . $plugin);
}
}
foreach my $comp (@comps)
{
foreach my $dep (@{ $depend_order{$comp} })
{
unless (exists $seen_depends{$dep})
{
push(@build_depends, $dep);
$seen_depends{$dep} = 1;
}
}
}
foreach my $sw (@build_depends)
{
# Get info about this package
my $pkg = $depend{$sw};
my $url = $pkg->{'url'};
my $filename = $url;
$filename =~ s|^.+/([^/]+)$|$1|;
my $dirname = $filename;
$dirname =~ s|\.tar\.gz$||;
chdir($SRCDIR);
# Download and decompress
unless (-e $filename)
{
&Verbose("Downloading $sw");
unless (&Syscall([ '/usr/bin/curl', $url, '>', $filename ],
'munge' => 1))
{
&Syscall([ '/bin/rm', $filename ]) if (-e $filename);
die;
}
}
else
{
&Verbose("Using previously downloaded $sw");
}
if ($OPT{'thirdclean'} && -d $dirname)
{
&Verbose("Removing previous build of $sw");
&Syscall([ '/bin/rm', '-f', '-r', $dirname ]) or die;
}
unless (-d $dirname)
{
&Verbose("Unpacking $sw");
&Syscall([ '/usr/bin/tar', '-xzf', $filename ]) or die;
}
else
{
&Verbose("Using previously unpacked $sw");
}
# Configure
chdir($dirname);
unless (-e '.osx-config')
{
&Verbose("Configuring $sw");
my (@configure, $munge);
if ($pkg->{'conf-cmd'})
{
push(@configure, $pkg->{'conf-cmd'});
$munge = 1;
}
else
{
push(@configure, './configure',
'--prefix=$PREFIX',
'--disable-static',
'--enable-shared');
}
if ($pkg->{'conf'})
{
push(@configure, @{ $pkg->{'conf'} });
}
&Syscall([EMAIL PROTECTED], 'interpolate' => 1, 'munge' => $munge) or die;
if ($pkg->{'post-conf'})
{
&Syscall([ $pkg->{'post-conf'} ], 'munge' => 1) or die;
}
&Syscall([ '/usr/bin/touch', '.osx-config' ]) or die;
}
else
{
&Verbose("Using previously configured $sw");
}
# Build and install
unless (-e '.osx-built')
{
&Verbose("Making $sw");
my (@make);
push(@make, '/usr/bin/make');
if ($pkg->{'make'})
{
push(@make, @{ $pkg->{'make'} });
}
else
{
push(@make, 'all', 'install');
}
&Syscall([EMAIL PROTECTED]) or die;
&Syscall([ '/usr/bin/touch', '.osx-built' ]) or die;
}
else
{
&Verbose("Using previously built $sw");
}
}
### build MythTV
# prepare CVS
my $cvsdir = "$SRCDIR/myth-cvs";
mkdir $cvsdir;
unless (-e "$cvsdir/.cvspass")
{
&Verbose("Creating .cvspass file");
# Create .cvspass file to bypass manual login
my $passfile;
unless (open($passfile, ">$cvsdir/.cvspass"))
{
&Complain("Failed to open .cvspass for writing");
die;
}
print $passfile <<END;
:pserver:[EMAIL PROTECTED]:/var/lib/mythcvs A%a,c,<
END
close $passfile;
}
$ENV{'CVS_PASSFILE'} = "$cvsdir/.cvspass";
# Clean any previously installed libraries
&Verbose("Cleaning previous installs of MythTV");
my @mythlibs = glob "$PREFIX/lib/libmyth*";
if (scalar @mythlibs)
{
&Syscall([ '/bin/rm', @mythlibs ]) or die;
}
foreach my $dir ('include', 'lib', 'share')
{
if (-d "$PREFIX/$dir/mythtv")
{
&Syscall([ '/bin/rm', '-f', '-r', "$PREFIX/$dir/mythtv" ]) or die;
}
}
# Build MythTV and any plugins
foreach my $comp (@comps)
{
my $compdir = "$cvsdir/$comp";
# Get CVS
unless (-d $compdir)
{
&Verbose("Checking out $comp");
chdir $cvsdir;
&Syscall([ '/usr/bin/cvs', '-f',
'-d', ':pserver:[EMAIL PROTECTED]:/var/lib/mythcvs',
'co',
'-r', $OPT{'cvstag'},
$comp ]) or die;
}
elsif (!$OPT{'nocvs'})
{
&Verbose("Updating $comp CVS");
chdir $compdir;
&Syscall([ '/usr/bin/cvs', '-f',
'update' , '-dPR',
'-r', $OPT{'cvstag'} ]) or die;
}
chdir $compdir;
if ($OPT{'clean'} && -e 'Makefile')
{
&Verbose("Cleaning $comp");
&Syscall([ '/usr/bin/make', 'distclean' ]) or die;
}
else
{
# clean the Makefiles, as process requires PREFIX hacking
&CleanMakefiles();
}
# configure and make
if (-e 'configure')
{
&Verbose("Configuring $comp");
my @config = './configure';
push(@config, "--prefix=$PREFIX");
if ($comp eq 'mythplugins') {
push(@config, @plugin_config);
}
&Syscall([ @config ]) or die;
}
&Verbose("Running qmake for $comp");
my @qmake_opts = (
'QMAKE_LFLAGS+=-Wl,-search_paths_first',
'INCLUDEPATH+="' . $PREFIX . '/include"',
'LIBS+=-L/usr/lib -L"' . $PREFIX . '/lib"'
);
&Syscall([ $PREFIX . '/bin/qmake',
'PREFIX=../Resources',
@qmake_opts,
"$comp.pro" ]) or die;
&Verbose("Making $comp");
&Syscall([ '/usr/bin/make' ]) or die;
# install
# This requires a change from the compiled-in relative
# PREFIX to our absolute path of the temp install location.
&CleanMakefiles();
&Verbose("Running qmake for $comp install");
&Syscall([ $PREFIX . '/bin/qmake',
'PREFIX=' . $PREFIX,
@qmake_opts,
"$comp.pro" ]) or die;
&Verbose("Installing $comp");
&Syscall([ '/usr/bin/make',
'install' ]) or die;
}
### Build version string
our $VERS = `find $PREFIX/lib -name 'libmyth-[0-9].[0-9][0-9].[0-9].dylib'`;
chomp $VERS;
$VERS =~ s/^.*\-(.*)\.dylib$/$1/s;
$VERS .= '.' . $OPT{'version'} if $OPT{'version'};
### Assemble application package
if (1)
{
# Get a fresh copy of the app
&Verbose("Building self-contained MythFrontend");
&Syscall([ '/bin/cp', '-R',
"$PREFIX/bin/mythfrontend.app",
$MFE ]) or die;
# write a custom Info.plist
&FrontendPlist($MFE, $VERS);
# Make frameworks from Myth libraries
&Verbose("Installing frameworks into MythFrontend");
my $fw_dir = "$MFE/Contents/Frameworks";
mkdir($fw_dir);
my $dephash = &ProcessDependencies("$MFE/Contents/MacOS/mythfrontend",
glob("$PREFIX/lib/mythtv/*/*.dylib"));
my @deps = values %$dephash;
while (scalar @deps)
{
my $file = &MakeFramework(&FindLibraryFile(shift @deps), $fw_dir);
my $newhash = &ProcessDependencies($file);
foreach my $base (keys %$newhash)
{
next if exists $dephash->{$base};
$dephash->{$base} = $newhash->{$base};
push(@deps, $newhash->{$base});
}
}
# Install themes, filters, etc.
&Verbose("Installing resources into MythFrontend");
mkdir "$MFE/Contents/Resources";
mkdir "$MFE/Contents/Resources/lib";
&Syscall([ '/bin/cp', '-R',
"$PREFIX/lib/mythtv",
"$MFE/Contents/Resources/lib" ]) or die;
mkdir "$MFE/Contents/Resources/share";
&Syscall([ '/bin/cp', '-R',
"$PREFIX/share/mythtv",
"$MFE/Contents/Resources/share" ]) or die;
}
&Verbose("Build complete. Self-contained package is at:\n\n $MFE\n");
### end script
exit 0;
######################################
## MakeFramework copies a dylib into a
## framework bundle.
######################################
sub MakeFramework
{
my ($dylib, $dest) = @_;
my ($base, $vers) = &BaseVers($dylib);
$vers .= '.' . $OPT{'version'} if ($OPT{'version'} && $base =~ /myth/);
&Verbose("Building $base framework");
my $fw_dir = $dest . '/' . $base . '.framework';
&Syscall([ '/bin/mkdir',
'-p',
"$fw_dir/Versions/A/Resources" ]) or die;
&Syscall([ '/bin/cp',
$dylib,
"$fw_dir/Versions/A/$base" ]) or die;
&Syscall([ '/usr/bin/install_name_tool',
'-id',
$base,
"$fw_dir/Versions/A/$base" ]) or die;
symlink('A', "$fw_dir/Versions/Current") or die;
symlink('Versions/Current/Resources', "$fw_dir/Resources") or die;
symlink("Versions/A/$base", "$fw_dir/$base") or die;
&Verbose("Writing Info.plist for $base framework");
my $plist;
unless (open($plist, '>' . "$fw_dir/Versions/A/Resources/Info.plist"))
{
&Complain("Failed to open $base framework's plist for writing");
die;
}
print $plist <<END;
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>$base</string>
<key>CFBundleIdentifier</key>
<string>org.mythtv.macx.$base</string>
<key>CFBundleVersion</key>
<string>$vers</string>
<key>CFBundleSignature</key>
<string>Myth</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>NSHumanReadableCopyright</key>
<string>Packaged for the MythTV project, www.mythtv.org</string>
<key>CFBundleGetInfoString</key>
<string>lib$base-$vers.dylib, packaged for the MythTV project, www.mythtv.org</string>
</dict>
</plist>
END
close($plist);
return "$fw_dir/Versions/A/$base";
} # end MakeFramework
######################################
## FrontendPlist .
######################################
sub FrontendPlist
{
my ($path, $vers) = @_;
&Verbose("Writing Info.plist for MythFrontend");
my $plist;
$path .= '/Contents/Info.plist';
unless (open($plist, ">$path"))
{
&Complain("Could not open $path for writing");
die;
}
print $plist <<END;
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>mythfrontend</string>
<key>CFBundleIconFile</key>
<string>application.icns</string>
<key>CFBundleIdentifier</key>
<string>org.mythtv.macx.mythfrontend</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$vers</string>
<key>CFBundleSignature</key>
<string>Myth</string>
<key>CFBundleVersion</key>
<string>$vers</string>
<key>NSAppleScriptEnabled</key>
<string>NO</string>
<key>CFBundleGetInfoString</key>
<string>$vers, MythTV project, www.mythtv.org</string>
<key>CFBundleName</key>
<string>MythFrontend</string>
<key>NSHumanReadableCopyright</key>
<string>MythTV project, www.mythtv.org</string>
</dict>
</plist>
END
close($plist);
$path =~ s/Info\.plist$/PkgInfo/;
unless (open($plist, ">$path"))
{
&Complain("Could not open $path for writing");
die;
}
print $plist <<END;
APPLMyth
END
close($plist);
} # end FrontendPlist
######################################
## FindLibraryFile locates a dylib.
######################################
sub FindLibraryFile
{
my ($dylib) = @_;
return Cwd::abs_path($dylib) if (-e $dylib);
return Cwd::abs_path("$PREFIX/lib/$dylib") if (-e "$PREFIX/lib/$dylib");
return Cwd::abs_path("$ENV{'QTDIR'}/lib/$dylib") if (-e "$ENV{'QTDIR'}/lib/$dylib");
my @sublibs = glob("$PREFIX/lib/*/$dylib");
return Cwd::abs_path($sublibs[0]) if (scalar @sublibs);
&Complain("Could not find $dylib");
die;
} # end FindLibraryFile
######################################
## ProcessDependencies catalogs and
## rewrites dependencies that will be
## packaged into our app bundle.
######################################
sub ProcessDependencies
{
my (%depfiles);
foreach my $file (@_)
{
&Verbose("Processing shared library dependencies for $file");
my ($filebase) = &BaseVers($file);
my @deps = `otool -L $file`;
shift @deps; # first line just repeats filename
foreach my $dep (@deps)
{
chomp $dep;
$dep =~ s/\s+(.*) \(.*\)$/$1/;
# skip system library locations
next if ($dep =~ m|^/System| ||
$dep =~ m|^/usr/lib|);
my ($base) = &BaseVers($dep);
$depfiles{$base} = $dep;
# Change references while we're here
&Syscall([ '/usr/bin/install_name_tool',
'-change',
$dep,
"[EMAIL PROTECTED]/../Frameworks/$base.framework/$base",
$file ]) or die;
}
}
return \%depfiles;
} # end ProcessDependencies
######################################
## BaseVers splits up a dylib file
## name for framework naming.
######################################
sub BaseVers
{
my ($filename) = @_;
if ($filename =~ m|^(?:.*/)?lib(.*)\-(\d.*)\.dylib$|)
{
return ($1, $2);
}
elsif ($filename =~ m|^(?:.*/)?lib(.*?)\.(\d.*)\.dylib$|)
{
return ($1, $2);
}
elsif ($filename =~ m|^(?:.*/)?lib(.*?)\.dylib$|)
{
return ($1, undef);
}
&Verbose("Could not parse library file name $filename");
return $filename;
} # end BaseVers
######################################
## CleanMakefiles removes every
## Makefile from our MythTV build.
## Necessary when we change the
## PREFIX variable.
######################################
sub CleanMakefiles
{
&Verbose("Cleaning MythTV makefiles");
my @files = map { chomp $_; $_ } `find . -name Makefile`;
if (scalar @files)
{
&Syscall([ '/bin/rm', @files ]) or die;
}
} # end CleanMakefiles
######################################
## Syscall wrappers the Perl "system"
## routine with verbosity and error
## checking.
######################################
sub Syscall
{
my ($arglist, %opts) = @_;
unless (ref $arglist)
{
$arglist = [ $arglist ];
}
if ($opts{'interpolate'})
{
my @args;
foreach my $arg (@$arglist)
{
$arg =~ s/\$PREFIX/$PREFIX/ge;
push(@args, $arg);
}
$arglist = [EMAIL PROTECTED];
}
if ($opts{'munge'})
{
$arglist = [ join(' ', @$arglist) ];
}
&Verbose(@$arglist);
my $ret = system(@$arglist);
if ($ret)
{
&Complain('Failed system call: "', @$arglist,
'" with error code', $ret >> 8);
}
return ($ret == 0);
} # end Syscall
######################################
## Verbose prints messages in verbose
## mode.
######################################
sub Verbose
{
print STDERR '[osx-pkg] ' . join(' ', @_) . "\n"
if $OPT{'verbose'};
} # end Verbose
######################################
## Complain prints messages in any
## verbosity mode.
######################################
sub Complain
{
print STDERR '[osx-pkg] ' . join(' ', @_) . "\n";
} # end Complain
### end file
1;
_______________________________________________ mythtv-dev mailing list [email protected] http://mythtv.org/cgi-bin/mailman/listinfo/mythtv-dev
