forked from caldwell/build-emacs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
combine-and-package
executable file
·184 lines (161 loc) · 8.77 KB
/
combine-and-package
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
#!/usr/bin/env ruby
# Copyright © 2014-2023 David Caldwell
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
require 'optparse'
require 'fileutils'
require 'find'
require 'rubygems'
require 'json'
$script_base = File.expand_path(File.dirname(__FILE__))
def script_base(*path)
File.join($script_base, *path)
end
require script_base('verbose-shell.rb')
Vsh = VerboseShell
def combine(tars, out_app, bin_arch)
base = nil
emacsen = tars.map { |tarball_path|
dir = File.dirname tarball_path
tarball = File.basename tarball_path
tarball =~ /^(Emacs-([\d.]+(?:-\d+)?))()-([\d.]+)-([^-]+).tar/ or
tarball =~ /^(Emacs-pretest-([\d.]+(?:-(?:rc)?\d+)*))()-([\d.]+)-([^-]+).tar/ or
tarball =~ /^(Emacs-(20\d\d-\d\d-\d\d_\d\d-\d\d-\d\d-([^-]+)))-([\d.]+)-([^-]+).tar/ or abort "Couldn't parse version from tarball (#{tarball})"
base ||= $1
abort "bases don't match: #{tarball} vs #{base}" unless base == $1
{ :tar => tarball, :version => $2, :git_rev => $3, :mac_os => Gem::Version.new($4), :arch => $5,
:dir => dir, :app => File.join(dir,'Emacs.app') }
}.sort {|a,b| a[:mac_os] <=> b[:mac_os] }
Vsh.rm_rf(out_app)
emacsen.each do |emacs|
Vsh.rm_rf(emacs[:app])
FileUtils.cd(emacs[:dir]) {
Vsh.system(*(%W"tar xf #{emacs[:tar]}"))
}
# The 'codesign' app (from XCode 6.0) dies when it sees something like 'bin-i386-10.5' in the MacOS
# directory. I believe it thinks the directory is a framework or some other kind of bundle and it dies
# when the contents don't match up with what it expects. I've filed a bug with Apple (18340424) but in the
# meantime, if we remove the dots from the name then codesign doesn't get confused.
arch_version = emacs[:arch] + '-' + emacs[:mac_os].to_s.gsub('.','_')
if !File.exists? out_app
Vsh.cp_r(emacs[:app], out_app, :preserve => true)
Vsh.rm_rf(File.join(out_app, 'Contents/MacOS/bin'))
Vsh.rm_rf(File.join(out_app, "Contents/MacOS/lib-#{arch_version}"))
Vsh.rm_rf(File.join(out_app, 'Contents/MacOS/libexec'))
end
Vsh.cp( File.join(emacs[:app], 'Contents/MacOS/Emacs'),
File.join(out_app, "Contents/MacOS/Emacs-#{arch_version}"))
Vsh.cp( File.join(emacs[:app], 'Contents/MacOS/Emacs.pdmp'), # As of 5dd2d50f3d5e65b85c87da86e2e8a6d087fe5767 this is now in libexec
File.join(out_app, "Contents/MacOS/Emacs-#{arch_version}.pdmp")) if File.exists?(File.join(emacs[:app], "Contents/MacOS/Emacs.pdmp"))
Vsh.cp_r(File.join(emacs[:app], 'Contents/MacOS/bin'),
File.join(out_app, "Contents/MacOS/bin-#{arch_version}"))
Vsh.cp_r(File.join(emacs[:app], "Contents/MacOS/lib-#{arch_version}"),
File.join(out_app, "Contents/MacOS/lib-#{arch_version}")) if File.exists?(File.join(emacs[:app], "Contents/MacOS/lib-#{arch_version}"))
Vsh.cp_r(File.join(emacs[:app], 'Contents/MacOS/libexec'),
File.join(out_app, "Contents/MacOS/libexec-#{arch_version}"))
Vsh.mv( File.join(out_app, "Contents/MacOS/libexec-#{arch_version}/Emacs.pdmp"), # FIXME: See if we can configure a custom libexec w/arch_version `C-h v exec-directory`
File.join(out_app, "Contents/MacOS/Emacs-#{arch_version}.pdmp")) if File.exists?(File.join(out_app, "Contents/MacOS/libexec-#{arch_version}/Emacs.pdmp"))
if bin_arch && bin_arch.gsub('.','_') == arch_version
Vsh.ln_s("bin-#{arch_version}",
File.join(out_app, 'Contents/MacOS/bin'))
Vsh.ln_s("lib-#{arch_version}",
File.join(out_app, 'Contents/MacOS/lib')) if File.exists?("lib-#{arch_version}")
Vsh.ln_s("libexec-#{arch_version}",
File.join(out_app, 'Contents/MacOS/libexec'))
end
end
Vsh.cp(script_base('launch'), File.join(out_app, "Contents/MacOS/Emacs"))
Vsh.cp(script_base('launch.rs'), File.join(out_app, "Contents/MacOS/launch.rs"))
Vsh.cp(script_base('launch-nw'), File.join(out_app, "Contents/MacOS/emacs-nw"))
add_nightly_version_to_info_plist(out_app, emacsen[0][:git_rev]) if emacsen[0][:git_rev] != ''
base
end
def add_nightly_version_to_info_plist(app, git_rev)
info = JSON.parse(Vsh.capture(*%W"plutil -convert json #{app}/Contents/Info.plist -o -").to_s)
info["GitRevision"] = git_rev
IO.popen(%W"plutil -convert xml1 - -o #{app}/Contents/Info.plist", "w") { |io| io.write(info.to_json) }
end
def code_sign(app, signer)
# Old OSes can't handle --deep code signing.
if Gem::Version.new(`sw_vers -productVersion`) < Gem::Version.new('10.8.0')
Vsh.system *%W"codesign --sign #{signer} #{app}"
else
# HACK: Work around problem with Mac OS X's codesign script detection. It doesn't detect "#! /usr/bin/perl" correctly. :-(
Find.find(File.join(app, "Contents/MacOS")) do |path|
next unless File.file?(path) && File.executable?(path)
if IO.read(path, 4) == '#! /'
puts "Fixing '#! /' in #{path}"
IO.write(path, IO.read(path).sub!(%r'^#! /','#!/'))
end
end
Vsh.system(*%W"codesign -f --deep --sign #{signer} --options=runtime --entitlements #{script_base('emacs-entitlements.plist')} #{app}")
end
end
def notarize(dmg, keychain_profile, keychain)
keychain_opts = ['--keychain-profile', keychain_profile] + (keychain ? ['--keychain', keychain] : [])
submission = JSON.parse(out=Vsh.capture(*%W"xcrun notarytool submit --output-format json", *keychain_opts, dmg).to_s)
puts "submission: #{out}" if Vsh.verbose
Vsh.system(*%W"xcrun notarytool wait --progress", *keychain_opts, submission['id'])
Vsh.system(*%W"xcrun stapler staple", *(Vsh.verbose ? ['-v'] : []), dmg)
rescue => e
Vsh.system(*%W"xcrun notarytool log", *keychain_opts, submission['id'])
raise "Notarization failed: #{e}"
end
def osascript(script)
Vsh.system 'osascript', *script.split(/\n/).map { |line| ['-e', line] }.flatten
end
def create_finder_comment(file, comment)
osascript <<-END
tell application "Finder"
set comment of file (POSIX file "#{file}") to "#{comment.gsub(/"/,'\\"')}"
end tell
END
end
def compile_icon(out, in_base)
# Git (or anything really) can't store resource forks. So compile the resource fork from scratch:
Vsh.system *%W"Rez -o #{out} #{in_base+'.r'}"
create_finder_comment(File.absolute_path(out), IO.read(in_base+'.comment')) if File.exists?(in_base+'.comment')
# Set the finder info bit that says the file has a custom icon.
Vsh.system *%W"xattr -x -w com.apple.FinderInfo #{"00 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"} #{out}"
end
def make_dmg(out_dmg, app)
compile_icon(script_base('emacs-dmg/More.../Alternative Icon'), script_base('emacs-dmg/icon'))
compile_icon(script_base('emacs-dmg/More.../Yosemite Icon'), script_base('emacs-dmg/yosemite-icon'))
Vsh.system *(['bash'] + (Vsh.verbose ? ['-x'] : []) +
[script_base('emacs-dmg/make-dmg'), out_dmg, 'Emacs', app] +
%W[Applications Heroes.rtf More... background.png dot-DS_Store].map{|f| script_base('emacs-dmg', f) })
end
code_signer = nil
keychain_profile = nil
keychain_path = nil
bin_arch = nil
(opts=OptionParser.new do |opts|
opts.banner = "Usage:\n\t#{$0} [options] <EMACS_TARBALL>..."
opts.on("-v", "--verbose", "Turn up the verbosity") { |v| Vsh.verbose = true }
opts.on("-s", "--sign IDENTITY", "Code sign the resulting app using IDENTITY") { |c| code_signer = c }
opts.on("-p", "--keychain-profile NAME", "Keychain profile name for Notarization [1]") {|kp| keychain_profile = kp }
opts.on( "--keychain PATH", "Path to keychain with profile for Notarization") {|k| keychain_path = k }
opts.on("-a", "--bin-arch ARCH", "Make link from plain bin & libexec to ARCH") { |a| bin_arch = a }
opts.on_tail("-h", "--help") { puts opts; puts <<-HELP; exit }
[1] See `xcrun notarytool store-credentials --help` for more info on how
to create a keychain profile.
HELP
end).parse!
emacsen = ARGV.dup
opts.abort("Need at least one tarball\n\n"+opts.help) if emacsen.length < 1
out_app = 'combined/Emacs.app'
Vsh.mkdir_p 'combined'
base = combine(emacsen, out_app, bin_arch)
code_sign(out_app, code_signer) if code_signer
make_dmg("#{base}-universal.dmg", out_app)
notarize("#{base}-universal.dmg", keychain_profile, keychain_path) if keychain_profile