dmg2mar 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. #!/usr/bin/perl -w
  2. #
  3. # This script converts all dmg files from the current directory and
  4. # listed in the sha256sums-unsigned-build.txt file to full update
  5. # mar files. After code signing the dmg files, this script can be used
  6. # to update the mar files.
  7. #
  8. # A recent version of p7zip is required to extract the dmg files, such
  9. # as 15.14. The version in Debian Jessie (9.20) is not recent enough.
  10. # It is possible to install the p7zip-full package from Debian testing,
  11. # or build p7zip from sources:
  12. # $ p7zipdir=/some_directory/p7zip
  13. # $ mkdir $p7zipdir
  14. # $ cd $p7zipdir
  15. # $ wget http://snapshot.debian.org/archive/debian/20160417T044336Z/pool/main/p/p7zip/p7zip_15.14.1%2Bdfsg.orig.tar.xz
  16. # $ echo 'e9e696e2fa77b00445a4d85fa07506debeae01943fdc1bee1472152d7d1386af p7zip_15.14.1+dfsg.orig.tar.xz' | sha256sum -c
  17. # $ wget http://snapshot.debian.org/archive/debian/20160515T161830Z/pool/main/p/p7zip/p7zip_15.14.1%2Bdfsg-2.debian.tar.xz
  18. # $ echo 'f4db6803535fc30b6ae9db5aabfd9f57a851c6773d72073847ec5e3731b7af37 p7zip_15.14.1+dfsg-2.debian.tar.xz' | sha256sum -c
  19. # $ tar xvf p7zip_15.14.1+dfsg-2.debian.tar.xz
  20. # $ tar xvf p7zip_15.14.1+dfsg.orig.tar.xz
  21. # $ cd p7zip_15.14.1/
  22. # $ for patch in $(cat ../debian/patches/series ); do patch -p1 < ../debian/patches/$patch; done
  23. # $ make 7z
  24. # $ mkdir $p7zipdir/bin
  25. # $ echo '#!/bin/sh' > $p7zipdir/bin/7z
  26. # $ echo "export LD_LIBRARY_PATH=$PWD/bin" >> $p7zipdir/bin/7z
  27. # $ echo "exec $PWD/bin/7z "'"$@"' >> $p7zipdir/bin/7z
  28. # $ chmod +x $p7zipdir/bin/7z
  29. # $ export "PATH=$p7zipdir/bin:$PATH"
  30. use strict;
  31. use IO::CaptureOutput qw(capture_exec);
  32. use File::Slurp;
  33. use File::Find;
  34. use Parallel::ForkManager;
  35. use Cwd;
  36. # If the application is not TorBrowser (for instance, TorMessenger)
  37. # set the application name in the TOR_APPNAME_BUNDLE_OSX,
  38. # TOR_APPNAME_DMGFILE and TOR_APPNAME_MARFILE environment variables
  39. my $appname = $ENV{TOR_APPNAME_BUNDLE_OSX} // 'Tor Browser';
  40. my $appname_dmg = $ENV{TOR_APPNAME_DMGFILE} // 'TorBrowser';
  41. my $appname_mar = $ENV{TOR_APPNAME_MARFILE} // 'tor-browser';
  42. sub exit_error {
  43. print STDERR "Error: ", $_[0], "\n";
  44. chdir '/';
  45. exit (exists $_[1] ? $_[1] : 1);
  46. }
  47. sub osname {
  48. my ($osname) = capture_exec('uname', '-s');
  49. my ($arch) = capture_exec('uname', '-m');
  50. chomp($osname, $arch);
  51. if ($osname eq 'Linux' && $arch eq 'x86_64') {
  52. return 'linux64';
  53. }
  54. if ($osname eq 'Linux' && $arch =~ m/^i.86$/) {
  55. return 'linux32';
  56. }
  57. exit_error 'Unknown OS';
  58. }
  59. my $martools_tmpdir;
  60. sub extract_martools {
  61. my $osname = osname;
  62. my $marzip = getcwd . "/mar-tools-$osname.zip";
  63. $martools_tmpdir = File::Temp->newdir();
  64. my $old_cwd = getcwd;
  65. chdir $martools_tmpdir;
  66. my (undef, undef, $success) = capture_exec('unzip', $marzip);
  67. chdir $old_cwd;
  68. exit_error "Error extracting $marzip" unless $success;
  69. $ENV{PATH} = "$martools_tmpdir/mar-tools:$ENV{PATH}";
  70. if ($ENV{LD_LIBRARY_PATH}) {
  71. $ENV{LD_LIBRARY_PATH} .= ":$martools_tmpdir/mar-tools";
  72. } else {
  73. $ENV{LD_LIBRARY_PATH} = "$martools_tmpdir/mar-tools";
  74. }
  75. $ENV{MAR} = "$martools_tmpdir/mar-tools/mar";
  76. $ENV{MSBDIFF} = "$martools_tmpdir/mar-tools/mbsdiff";
  77. }
  78. sub get_nbprocs {
  79. return $ENV{NUM_PROCS} if defined $ENV{NUM_PROCS};
  80. if (-f '/proc/cpuinfo') {
  81. return scalar grep { m/^processor\s+:\s/ } read_file '/proc/cpuinfo';
  82. }
  83. return 4;
  84. }
  85. sub get_dmg_files_from_sha256sums {
  86. exit_error "Missing sha256sums-unsigned-build.txt file"
  87. unless -f 'sha256sums-unsigned-build.txt';
  88. my @files;
  89. foreach my $line (read_file('sha256sums-unsigned-build.txt')) {
  90. my (undef, $filename) = split ' ', $line;
  91. next unless $filename;
  92. chomp $filename;
  93. next unless $filename =~ m/^$appname_dmg-(.+)-macos_(.+)\.dmg$/;
  94. push @files, { filename => $filename, version => $1, lang => $2 };
  95. }
  96. return @files;
  97. }
  98. sub convert_files {
  99. my ($channel) = @_;
  100. my $pm = Parallel::ForkManager->new(get_nbprocs);
  101. $pm->run_on_finish(
  102. sub {
  103. exit_error "Failed while running $_[2]" unless $_[1] == 0;
  104. print "Finished $_[2]\n";
  105. });
  106. foreach my $file (get_dmg_files_from_sha256sums) {
  107. # The 'ja' locale is a special case: it is called 'ja-JP-mac'
  108. # internally on OSX, but the dmg file still uses 'ja' to avoid
  109. # confusing users.
  110. my $mar_lang = $file->{lang} eq 'ja' ? 'ja-JP-mac' : $file->{lang};
  111. my $output = "$appname_mar-macos-$file->{version}_$mar_lang.mar";
  112. my $step_name = "$file->{filename} -> $output";
  113. print "Starting $step_name\n";
  114. $pm->start($step_name) and next;
  115. my $tmpdir = File::Temp->newdir();
  116. my (undef, $err, $success) = capture_exec('7z', 'x', "-o$tmpdir",
  117. $file->{filename});
  118. exit_error "Error extracting $file->{filename}: $err" unless $success;
  119. # 7z does not currently extract file permissions from the dmg files
  120. # so we also extract the old mar file to copy the permissions
  121. # https://trac.torproject.org/projects/tor/ticket/20210
  122. my $tmpdir_oldmar = File::Temp->newdir();
  123. my $oldmar = getcwd . '/' . $output;
  124. exit_error "Error extracting $output"
  125. unless system('mar', '-C', $tmpdir_oldmar, '-x', $oldmar) == 0;
  126. my $appdir = "$tmpdir/$appname/$appname.app";
  127. exit_error "Missing directory $appdir" unless -d $appdir;
  128. my $wanted = sub {
  129. my $file = $File::Find::name;
  130. $file =~ s{^$appdir/}{};
  131. if (-f "$tmpdir_oldmar/$file") {
  132. my (undef, undef, $mode) = stat("$tmpdir_oldmar/$file");
  133. chmod $mode, $File::Find::name;
  134. return;
  135. }
  136. chmod 0644, $File::Find::name if -f $File::Find::name;
  137. chmod 0755, $File::Find::name if -d $File::Find::name;
  138. };
  139. find($wanted, $appdir);
  140. unlink $output;
  141. local $ENV{MOZ_PRODUCT_VERSION} = $file->{version};
  142. local $ENV{MAR_CHANNEL_ID} = "torbrowser-torproject-$channel";
  143. local $ENV{TMPDIR} = $tmpdir;
  144. (undef, $err, $success) = capture_exec('make_full_update.sh', '-q',
  145. $output, $appdir);
  146. exit_error "Error updating $output: $err" unless $success;
  147. exit_error "make_full_update.sh failed. $output does not exist."
  148. unless -f $output;
  149. $pm->finish;
  150. }
  151. $pm->wait_all_children;
  152. }
  153. sub remove_incremental_mars {
  154. exit_error "Missing sha256sums-unsigned-build.incrementals.txt file"
  155. unless -f 'sha256sums-unsigned-build.incrementals.txt';
  156. foreach my $line (read_file('sha256sums-unsigned-build.incrementals.txt')) {
  157. my (undef, $filename) = split ' ', $line;
  158. chomp $filename;
  159. next unless $filename =~ m/^$appname_mar-macos.+\.incremental\.mar$/;
  160. next unless -f $filename;
  161. print "Removing $filename\n";
  162. unlink $filename;
  163. }
  164. }
  165. # Set LC_ALL=C to avoid reproducibility issues when creating mar files
  166. $ENV{LC_ALL} = 'C';
  167. exit_error "Please specify update channel" unless @ARGV == 1;
  168. my $channel = $ARGV[0];
  169. extract_martools;
  170. convert_files $channel;
  171. remove_incremental_mars;