dmg2mar 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  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. chomp $filename;
  92. next unless $filename =~ m/^$appname_dmg-(.+)-osx64_(.+)\.dmg$/;
  93. push @files, { filename => $filename, version => $1, lang => $2 };
  94. }
  95. return @files;
  96. }
  97. sub convert_files {
  98. my ($channel) = @_;
  99. my $pm = Parallel::ForkManager->new(get_nbprocs);
  100. $pm->run_on_finish(sub { print "Finished $_[2]\n" });
  101. foreach my $file (get_dmg_files_from_sha256sums) {
  102. # The 'ja' locale is a special case: it is called 'ja-JP-mac'
  103. # internally on OSX, but the dmg file still uses 'ja' to avoid
  104. # confusing users.
  105. my $mar_lang = $file->{lang} eq 'ja' ? 'ja-JP-mac' : $file->{lang};
  106. my $output = "$appname_mar-osx64-$file->{version}_$mar_lang.mar";
  107. my $step_name = "$file->{filename} -> $output";
  108. print "Starting $step_name\n";
  109. $pm->start($step_name) and next;
  110. my $tmpdir = File::Temp->newdir();
  111. my (undef, $err, $success) = capture_exec('7z', 'x', "-o$tmpdir",
  112. $file->{filename});
  113. exit_error "Error extracting $file->{filename}: $err" unless $success;
  114. # 7z does not currently extract file permissions from the dmg files
  115. # so we also extract the old mar file to copy the permissions
  116. # https://trac.torproject.org/projects/tor/ticket/20210
  117. my $tmpdir_oldmar = File::Temp->newdir();
  118. my $oldmar = getcwd . '/' . $output;
  119. exit_error "Error extracting $output"
  120. unless system('mar', '-C', $tmpdir_oldmar, '-x', $oldmar) == 0;
  121. my $wanted = sub {
  122. my $file = $File::Find::name;
  123. $file =~ s{^$tmpdir/$appname\.app/}{};
  124. if (-f "$tmpdir_oldmar/$file") {
  125. my (undef, undef, $mode) = stat("$tmpdir_oldmar/$file");
  126. chmod $mode, $File::Find::name;
  127. return;
  128. }
  129. chmod 0644, $File::Find::name if -f $File::Find::name;
  130. chmod 0755, $File::Find::name if -d $File::Find::name;
  131. };
  132. find($wanted, "$tmpdir/$appname.app");
  133. unlink $output;
  134. local $ENV{MOZ_PRODUCT_VERSION} = $file->{version};
  135. local $ENV{MAR_CHANNEL_ID} = "torbrowser-torproject-$channel";
  136. local $ENV{TMPDIR} = $tmpdir;
  137. (undef, $err, $success) = capture_exec('make_full_update.sh', '-q',
  138. $output, "$tmpdir/$appname.app");
  139. exit_error "Error updating $output: $err" unless $success;
  140. exit_error "make_full_update.sh failed. $output does not exist."
  141. unless -f $output;
  142. $pm->finish;
  143. }
  144. $pm->wait_all_children;
  145. }
  146. sub remove_incremental_mars {
  147. exit_error "Missing sha256sums-unsigned-build.incrementals.txt file"
  148. unless -f 'sha256sums-unsigned-build.incrementals.txt';
  149. foreach my $line (read_file('sha256sums-unsigned-build.incrementals.txt')) {
  150. my (undef, $filename) = split ' ', $line;
  151. chomp $filename;
  152. next unless $filename =~ m/^$appname_mar-osx64.+\.incremental\.mar$/;
  153. next unless -f $filename;
  154. print "Removing $filename\n";
  155. unlink $filename;
  156. }
  157. }
  158. # Set LC_ALL=C to avoid reproducibility issues when creating mar files
  159. $ENV{LC_ALL} = 'C';
  160. exit_error "Please specify update channel" unless @ARGV == 1;
  161. my $channel = $ARGV[0];
  162. extract_martools;
  163. convert_files $channel;
  164. remove_incremental_mars;