Commit 2ae28e7c authored by Gabriel Devillers's avatar Gabriel Devillers
Browse files

Packaging on macOS

aka "Bundling", "creating a bundle"

Also works if brandind != "VeloView"
parent 8c710cbf
on run argv
set image_name to item 1 of argv
tell application "Finder"
tell disk image_name
-- wait for the image to finish mounting
set open_attempts to 0
repeat while open_attempts < 4
try
open
delay 1
set open_attempts to 5
close
on error errStr number errorNumber
set open_attempts to open_attempts + 1
delay 10
end try
end repeat
delay 5
-- open the image the first time and save a DS_Store with just
-- background and icon setup
open
set current view of container window to icon view
set theViewOptions to the icon view options of container window
set background picture of theViewOptions to file ".background:background.tif"
set arrangement of theViewOptions to not arranged
set icon size of theViewOptions to 128
delay 5
close
-- next setup the position of the app and Applications symlink
-- plus hide all the window decoration
open
update without registering applications
tell container window
set sidebar width to 0
set statusbar visible to false
set toolbar visible to false
set the bounds to { 400, 100, 900, 465 }
set position of item "@veloview_appname@" to { 133, 200 }
set position of item "Applications" to { 378, 200 }
end tell
update without registering applications
delay 5
close
-- one last open and close so you can see everything looks correct
open
delay 5
close
end tell
delay 1
end tell
end run
#!/usr/bin/env python
# Copyright 2013 Velodyne Acoustics, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#App="$1" # argument is the application to fixup
#LibrariesPrefix="Contents/Libraries"
#echo ""
#echo "Fixing up $App"
#echo "All required frameworks/libraries will be placed under $App/$LibrariesPrefix"
#echo ""
#echo "----------------------------"
#echo "Locating all executables and dylibs already in the package ... "
#
## the sed-call removes the : Mach-O.. suffix that "file" generates
#executables=`find $App | xargs file | grep -i "Mach-O.*executable" | sed "s/:.*//" | sort`
#
#echo "----------------------------"
#echo "Found following executables:"
#for i in $executables; do
# echo $i
#done
#
## for each executable, find any external library.
#
#
#libraries=`find $App | xargs file | grep -i "Mach-O.*shared library" | sed "s/:.*//" | sort`
#
## command to find all external libraries referrenced in package:
## find paraview.app | xargs file | grep "Mach-O" | sed "s/:.*//" | xargs otool -l | grep " name" | sort | uniq | sed "s/name\ //" | grep -v "@executable"
#
## find non-system libs
## find paraview.app | xargs file | grep "Mach-O" | sed "s/:.*//" | xargs otool -l | grep " name" | sort | uniq | sed "s/name\ //" | grep -v "@executable" | grep -v "/System/" | grep -v "/usr/lib/"
import commands
import sys
import os.path
import re
import shutil
class Library(object):
def __init__(self):
# This is the actual path to a physical file
self.RealPath = None
# This is the id for shared library.
self.Id = None
self.__depencies = None
pass
def __hash__(self):
return self.RealPath.__hash__()
def __eq__(self, other):
return self.RealPath == other.RealPath
def __repr__(self):
return "Library(%s : %s)" % (self.Id, self.RealPath)
def dependencies(self, exepath):
if self.__depencies:
return self.__depencies
collection = set()
for dep in _getdependencies(self.RealPath):
if not isexcluded(dep):
collection.add(Library.createFromReference(dep, exepath))
self.__depencies = collection
return self.__depencies
def copyToApp(self, app, fakeCopy=False):
if _isframework(self.RealPath):
m = re.match(r'(.*)/(\w+\.framework)/(.*)', self.RealPath)
# FIXME: this could be optimized to only copy the particular version.
if not fakeCopy:
print "Copying %s/%s ==> %s" % (m.group(1), m.group(2), ".../Contents/Frameworks/")
dirdest = os.path.join(os.path.join(app, "Contents/Frameworks/"), m.group(2))
filedest = os.path.join(dirdest, m.group(3))
shutil.copytree(os.path.join(m.group(1), m.group(2)), dirdest, symlinks=True)
self.Id = "@executable_path/../Frameworks/%s" % (os.path.join(m.group(2), m.group(3)))
#print self.Id, dirdest, filedest
if not fakeCopy:
commands.getoutput('install_name_tool -id "%s" %s' % (self.Id, filedest))
else:
if not fakeCopy:
print "Copying %s ==> %s" % (self.RealPath, ".../Contents/Libraries/%s" % os.path.basename(self.RealPath))
shutil.copy(self.RealPath, os.path.join(app, "Contents/Libraries"))
self.Id = "@executable_path/../Libraries/%s" % os.path.basename(self.RealPath)
if not fakeCopy:
commands.getoutput('install_name_tool -id "%s" %s' % (self.Id,
os.path.join(app, "Contents/Libraries/%s" % os.path.basename(self.RealPath))))
@classmethod
def createFromReference(cls, ref, exepath):
path = ref.replace("@executable_path", exepath)
if not os.path.exists(path):
path = _find(ref)
return cls.createFromPath(path)
@classmethod
def createFromPath(cls, path):
if not os.path.exists(path):
raise RuntimeError, "%s is not a filename" % path
lib = Library()
lib.RealPath = os.path.realpath(path)
lib.Id = _getid(path)
return lib
def _getid(lib):
"""Returns the id for the library"""
val = commands.getoutput("otool -D %s" % lib)
m = re.match(r"[^:]+:\s*([^\s]+)", val)
if m:
return m.group(1)
raise RuntimeError, "Could not determine id for %s" % lib
def _getdependencies(path):
val = commands.getoutput('otool -l %s| grep " name" | sort | uniq | sed "s/name\ //" | sed "s/(offset.*)//"' % path)
return val.split()
def isexcluded(id):
if re.match(r"^/System/Library", id):
return True
if re.match(r"^/usr/lib", id):
return True
if re.match(r"^/usr/local", id):
return True
if re.match(r"^libz.1.dylib", id):
return True
return False
def _isframework(path):
if re.match(".*\.framework.*", path):
return True
def _find(ref):
name = os.path.basename(ref)
for loc in SearchLocations:
output = commands.getoutput('find "%s" -name "%s"' % (loc, name)).strip()
if output:
return output
return ref
SearchLocations = []
if __name__ == "__main__":
App = sys.argv[1]
SearchLocations = [sys.argv[2]]
SearchLocations.append (sys.argv[3])
if len(sys.argv) > 4:
QtPluginsDir = sys.argv[4]
else:
QtPluginsDir = None
LibrariesPrefix = "Contents/Libraries"
print "------------------------------------------------------------"
print "Fixing up ",App
print "All required frameworks/libraries will be placed under %s/%s" % (App, LibrariesPrefix)
print ""
executables = commands.getoutput('find %s -type f| xargs file | grep -i "Mach-O.*executable" | sed "s/:.*//" | sort' % App)
executables = executables.split()
print "------------------------------------------------------------"
print "Found executables : "
for exe in executables:
print " %s/%s" % (os.path.basename(App) ,os.path.relpath(exe, App))
print ""
# Find libraries inside the package already.
libraries = commands.getoutput('find %s -type f | xargs file | grep -i "Mach-O.*shared library" | sed "s/:.*//" | sort' % App)
libraries = libraries.split()
print "Found %d libraries within the package." % len(libraries)
# Find external libraries. Any libraries referred to with @.* relative paths are treated as already in the package.
# ITS NOT THIS SCRIPT'S JOB TO FIX BROKEN INSTALL RULES.
external_libraries = commands.getoutput(
'find %s | xargs file | grep "Mach-O" | sed "s/:.*//" | xargs otool -l | grep " name" | sort | uniq | sed "s/name\ //" | grep -v "@" | sed "s/ (offset.*)//"' % App)
mLibraries = set()
for lib in external_libraries.split():
if not isexcluded(lib):
print "Processing ", lib
mLibraries.add(Library.createFromReference(lib, "%s/Contents/MacOS/foo" % App))
print "Found %d direct external dependencies." % len(mLibraries)
def recursive_dependency_scan(base, to_scan):
dependencies = set()
for lib in to_scan:
dependencies.update(lib.dependencies("%s/Contents/MacOS" % App))
dependencies -= base
# Now we have the list of non-packaged dependencies.
dependencies_to_package = set()
for dep in dependencies:
if not isexcluded(dep.RealPath):
dependencies_to_package.add(dep)
if len(dependencies_to_package) > 0:
new_base = base | dependencies_to_package
dependencies_to_package |= recursive_dependency_scan(new_base, dependencies_to_package)
return dependencies_to_package
return dependencies_to_package
indirect_mLibraries = recursive_dependency_scan(mLibraries, mLibraries)
print "Found %d indirect external dependencies." % (len(indirect_mLibraries))
print ""
mLibraries.update(indirect_mLibraries)
print "------------------------------------------------------------"
install_name_tool_command = []
for dep in mLibraries:
old_id = dep.Id
dep.copyToApp(App)
new_id = dep.Id
install_name_tool_command += ["-change", '"%s"' % old_id, '"%s"' % new_id]
print ""
install_name_tool_command = " ".join(install_name_tool_command)
# If Qt Plugins dir is specified, copies those in right now.
# We need to fix paths on those too.
# Currently, we are not including plugins in the external dependency search.
if QtPluginsDir:
print "------------------------------------------------------------"
print "Copying Qt plugins "
print " %s ==> .../Contents/Plugins" % QtPluginsDir
commands.getoutput('cp -R "%s/" "%s/Contents/Plugins"' % (QtPluginsDir, App))
print "------------------------------------------------------------"
print "Running 'install_name_tool' to fix paths to copied files."
print ""
# Run the command for all libraries and executables.
# The --separator for file allows helps use locate the file name accurately.
binaries_to_fix = commands.getoutput('find %s -type f | xargs file --separator ":--:" | grep -i ":--:.*Mach-O" | sed "s/:.*//" | sort | uniq ' % App).split()
result = ""
for dep in binaries_to_fix:
commands.getoutput('chmod u+w "%s"' % dep)
# print "Fixing '%s'" % dep
commands.getoutput('install_name_tool %s "%s"' % (install_name_tool_command, dep))
commands.getoutput('chmod a-w "%s"' % dep)
#!/usr/bin/python
# Copyright 2013 Velodyne Acoustics, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import commands
import re
print "Fixing bundle for Mountain Lion for Application: ", sys.argv[1]
App = sys.argv[1]
references_to_fix = commands.getoutput("""find %s | xargs file | grep "Mach-O" | sed "s/:.*//" | xargs otool -l | grep " name" | sort | uniq | sed "s/name\ //" | grep "@executable_path" | awk '{print $1}'""" % App)
#print "references_to_fix: ", references_to_fix
install_name_tool_command = ""
for ref in references_to_fix.split():
match = re.match("^@executable_path/../Libraries/(.*)$", ref)
if match:
print " -> %s" % ref
install_name_tool_command += " -change \"%s\" \"@executable_path/%s\"" % (ref, match.group(1))
if len(install_name_tool_command) == 0:
print "No library path need to be adjust"
else:
binaries_to_fix = commands.getoutput('find %s -type f | xargs file --separator ":--:" | grep -i ":--:.*Mach-O"| grep -v Framework | sed "s/:.*//" | sort | uniq ' % App).split()
for dep in binaries_to_fix:
commands.getoutput('chmod u+w "%s"' % dep)
#print "Fixing '%s'" % dep
commands.getoutput('install_name_tool %s "%s"' % (install_name_tool_command, dep))
commands.getoutput('install_name_tool -id "@executable_path/%s" "%s"' % ( dep.split("/")[-1] , dep))
commands.getoutput('chmod a-w "%s"' % dep)
commands.getoutput("cd %s/Contents/MacOS && ln -s ../Libraries/*.dylib ." % App)
commands.getoutput("cd %s/Contents/bin && ln -s ../Libraries/*.dylib ." % App)
#!/usr/bin/env python
# Copyright 2013 Velodyne Acoustics, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#---------------------------------------------------------------------------------
# This script expect a path to an application bundle as argument and will
# shorten the libraries names in order to reduce the dylib loader path
# so the Mountain Lion bug could be overcome.
#
# This script has been written to fix the ParaView application bundle.
#---------------------------------------------------------------------------------
import commands
import sys
import os
import os.path
from os import listdir
from os.path import isfile, join
import re
import shutil
#---------------------------------------------------------------------------------
path_bundle_to_fix = sys.argv[1]
library_name_mapping = {}
hash_table = "abcdefghijklmnopqrstuvwxyz0123456789"
hash_table_size = len(hash_table)
#---------------------------------------------------------------------------------
def writeMapping(dir):
fo = open(dir + "/LibraryMapping.txt", "w")
for key in library_name_mapping.iterkeys():
fo.write(key + ":" + library_name_mapping[key] + "\n")
fo.close()
#---------------------------------------------------------------------------------
def readMapping(dir):
global library_name_mapping
try:
f = open(dir + "/LibraryMapping.txt", "r")
for line in f:
oldNew = line.split(":")
if len(oldNew) == 2:
library_name_mapping[oldNew[0]] = oldNew[1][0:-1]
f.close()
except:
pass
#---------------------------------------------------------------------------------
def removeLinks(dir):
commands.getoutput("cd %s && find . -type l -maxdepth 1 -exec rm {} \;" % dir)
#---------------------------------------------------------------------------------
def numberToString(number):
result = ""
while(number > hash_table_size):
result = hash_table[number%hash_table_size] + result
number = number / hash_table_size
result = hash_table[number%hash_table_size] + result
return result
#---------------------------------------------------------------------------------
def getExtension(fileName):
return fileName[fileName.rfind("."):]
#---------------------------------------------------------------------------------
def getNewFileName(fileName):
global library_name_mapping
if fileName.find(".dylib") != -1:
id = len(library_name_mapping)
new_name = numberToString(id)# + getExtension(fileName)
library_name_mapping[fileName] = new_name
else:
# No renaming
library_name_mapping[fileName] = fileName
#---------------------------------------------------------------------------------
def renameLibraries(dir):
for f in listdir(dir):
sys.stdout.write('.')
getNewFileName(f)
print
#---------------------------------------------------------------------------------
def fixInternalLibraryPath(dir):
print "Fixing libraries in", dir
for f in listdir(dir):
fullPath = dir + "/" + f
libs = commands.getoutput("otool -L %s | grep executable_path | awk '{print $1}'" % fullPath).split()
changeName = " -id @executable_path/" + f
for lib in libs:
libname = lib[lib.rfind('/')+1:]
if library_name_mapping.has_key(libname):
sys.stdout.write('.')
changeName += " -change " + lib + " @executable_path/" + library_name_mapping[libname]
commands.getoutput('chmod u+w "%s"' % fullPath)
commands.getoutput('install_name_tool %s "%s"' % (changeName, fullPath))
commands.getoutput('chmod a-w "%s"' % fullPath)
if not library_name_mapping.has_key(f):
continue
newFullPath = dir + "/" + library_name_mapping[f]
os.rename(fullPath, newFullPath)
print "\n"
#---------------------------------------------------------------------------------
def updateFrameworkPath(dir, dest):
global library_name_mapping
print "Moving frameworks from", dir, "to", dest
frameworks = commands.getoutput('find %s -type f | xargs file --separator ":--:" | grep -i ":--:.*Mach-O" | sed "s/:.*//" | sort | uniq' % dir).split()
for f in frameworks:
fname = f[f.rfind('/')+1:]
library_name_mapping[fname] = fname
fullPath = f
newFullPath = dest + "/" + fname
os.rename(fullPath, newFullPath)
print " -", fname
#---------------------------------------------------------------------------------
def fixExecutables(dir, copyLibs=True):
for f in listdir(dir):
print "Fixing executable", f
fullPath = dir + "/" + f
libs = commands.getoutput("otool -L %s | grep executable_path | awk '{print $1}'" % fullPath).split()
changeName = ""
for lib in libs:
libname = lib[lib.rfind('/')+1:]
if library_name_mapping.has_key(libname):
sys.stdout.write('.')
changeName += " -change " + lib + " @executable_path/" + library_name_mapping[libname]
commands.getoutput('chmod u+w "%s"' % fullPath)
commands.getoutput('install_name_tool %s "%s"' % (changeName, fullPath))
commands.getoutput('chmod a-w "%s"' % fullPath)
print
if copyLibs:
commands.getoutput("cd %s && ln -s ../Libraries/* ." % dir)
#---------------------------------------------------------------------------------
print "Patch bundle", path_bundle_to_fix
lib_dir = path_bundle_to_fix + "/Contents/Libraries"
frameworks_dir = path_bundle_to_fix + "/Contents/Frameworks"
bin_dir = path_bundle_to_fix + "/Contents/bin"
pv_dir = path_bundle_to_fix + "/Contents/MacOS"
plugins_dir = path_bundle_to_fix + "/Contents/Plugins"
# Clean up previous bundle fix
print "Remove links."
removeLinks(lib_dir)
removeLinks(bin_dir)
removeLinks(pv_dir)
# Move frameworks to libraries
#updateFrameworkPath(frameworks_dir, lib_dir)
# Rename libraries to shorten their path names
readMapping(lib_dir)
if len(library_name_mapping) == 0:
sys.stdout.write("Rename libraries")
renameLibraries(lib_dir)
writeMapping(lib_dir)
else:
print "Read previous mapping definition of", len(library_name_mapping), "libraries."
# Update libraries to point to the new lib names
fixInternalLibraryPath(lib_dir)
# Update framework refs
frameworks = commands.getoutput('find %s -type f | xargs file --separator ":--:" | grep -i ":--:.*Mach-O" | sed "s/:.*//" | sort | uniq' % frameworks_dir).split()
for f in frameworks:
fixExecutables(f[0:f.rfind("/")], False)
# Update plugins refs
fixExecutables(plugins_dir, False)
# Update executable to point to the new lib names
fixExecutables(bin_dir)
fixExecutables(pv_dir)
#!/usr/bin/env python
# Copyright 2013 Velodyne Acoustics, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This is simple script that can be used to fixup a library (dylib or so)
# Usage:
# ./fixup_plugin.py <full path to lib to fix or dir with libraries to fix> "key=val" ["key=val" ...]
# This is simply replaces any referece to 'key' with 'val' in the libraries referred to
# by the plugin lib to fix. 'key' can be a Python regular expression.
# The order of the key=value pairs is significant. The expressions are
# tested in the order specified.
import commands
import sys
import os.path
import re
import shutil
from fixup_bundle import *
plugin_dir = sys.argv[1]
prefix_map = {}
prefix_keys_in_priority_order = []
for arg in sys.argv[2:]:
key, value = arg.split("=")
prefix_map[key] = value
prefix_keys_in_priority_order.append(key)
libs_to_fix = commands.getoutput('find %s -type f | xargs file --separator ":--:" | grep -i ":--:.*Mach-O" | sed "s/:.*//" | sort | uniq ' % plugin_dir).split()