1#!/bin/bash
2
3# Copyright (c) 2012 The Chromium Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7# Copies a framework to its new home, "unversioning" it.
8#
9# Normally, frameworks are versioned bundles.  The contents of a framework are
10# stored in a versioned directory within the bundle, and symbolic links
11# provide access to the actual code and resources.  See
12# http://developer.apple.com/mac/library/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/FrameworkAnatomy.html
13#
14# The symbolic links usually found in frameworks create problems.  Symbolic
15# links are excluded from code signatures.  That means that it's possible to
16# remove or retarget a symbolic link within a framework without affecting the
17# seal.  In Chrome's case, the outer .app bundle contains a framework where
18# all application code and resources live.  In order for the signature on the
19# .app to be meaningful, it encompasses the framework.  Because framework
20# resources are accessed through the framework's symbolic links, this
21# arrangement results in a case where the resources can be altered without
22# affecting the .app signature's validity.
23#
24# Indirection through symbolic links also carries a runtime performance
25# penalty on open() operations, although open() typically completes so quickly
26# that this is not considered a major performance problem.
27#
28# To resolve these problems, the frameworks that ship within Chrome's .app
29# bundle are unversioned.  Unversioning is simple: instead of using the
30# original outer .framework directory as the framework that ships within the
31# .app, the inner versioned directory is used.  Instead of accessing bundled
32# resources through symbolic links, they are accessed directly.  In normal
33# situations, the only hard-coded use of the versioned directory is by dyld,
34# when loading the framework's code, but this is handled through a normal
35# Mach-O load command, and it is easy to adjust the load command to point to
36# the unversioned framework code rather than the versioned counterpart.
37#
38# The resulting framework bundles aren't strictly conforming, but they work
39# as well as normal versioned framework bundles.
40#
41# An option to skip running install_name_tool is available. By passing -I as
42# the first argument to this script, install_name_tool will be skipped. This
43# is only suitable for copied frameworks that will not be linked against, or
44# when install_name_tool will be run on any linker output when something is
45# linked against the copied framework. This option exists to allow signed
46# frameworks to pass through without subjecting them to any modifications that
47# would break their signatures.
48
49set -e
50
51RUN_INSTALL_NAME_TOOL=1
52if [ $# -eq 3 ] && [ "${1}" = "-I" ] ; then
53  shift
54  RUN_INSTALL_NAME_TOOL=
55fi
56
57if [ $# -ne 2 ] ; then
58  echo "usage: ${0} [-I] FRAMEWORK DESTINATION_DIR" >& 2
59  exit 1
60fi
61
62# FRAMEWORK should be a path to a versioned framework bundle, ending in
63# .framework.  DESTINATION_DIR is the directory that the unversioned framework
64# bundle will be copied to.
65
66FRAMEWORK="${1}"
67DESTINATION_DIR="${2}"
68
69FRAMEWORK_NAME="$(basename "${FRAMEWORK}")"
70if [ "${FRAMEWORK_NAME: -10}" != ".framework" ] ; then
71  echo "${0}: ${FRAMEWORK_NAME} does not end in .framework" >& 2
72  exit 1
73fi
74FRAMEWORK_NAME_NOEXT="${FRAMEWORK_NAME:0:$((${#FRAMEWORK_NAME} - 10))}"
75
76# Find the current version.
77VERSIONS="${FRAMEWORK}/Versions"
78CURRENT_VERSION_LINK="${VERSIONS}/Current"
79CURRENT_VERSION_ID="$(readlink "${VERSIONS}/Current")"
80CURRENT_VERSION="${VERSIONS}/${CURRENT_VERSION_ID}"
81
82# Make sure that the framework's structure makes sense as a versioned bundle.
83if [ ! -e "${CURRENT_VERSION}/${FRAMEWORK_NAME_NOEXT}" ] ; then
84  echo "${0}: ${FRAMEWORK_NAME} does not contain a dylib" >& 2
85  exit 1
86fi
87
88DESTINATION="${DESTINATION_DIR}/${FRAMEWORK_NAME}"
89
90# Copy the versioned directory within the versioned framework to its
91# destination location.
92mkdir -p "${DESTINATION_DIR}"
93rsync -acC --delete --exclude Headers --exclude PrivateHeaders \
94    --include '*.so' "${CURRENT_VERSION}/" "${DESTINATION}"
95
96if [[ -n "${RUN_INSTALL_NAME_TOOL}" ]]; then
97  # Adjust the Mach-O LC_ID_DYLIB load command in the framework.  This does not
98  # change the LC_LOAD_DYLIB load commands in anything that may have already
99  # linked against the framework.  Not all frameworks will actually need this
100  # to be changed.  Some frameworks may already be built with the proper
101  # LC_ID_DYLIB for use as an unversioned framework.  Xcode users can do this
102  # by setting LD_DYLIB_INSTALL_NAME to
103  # $(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(WRAPPER_NAME)/$(PRODUCT_NAME)
104  # If invoking ld via gcc or g++, pass the desired path to -Wl,-install_name
105  # at link time.
106  FRAMEWORK_DYLIB="${DESTINATION}/${FRAMEWORK_NAME_NOEXT}"
107  LC_ID_DYLIB_OLD="$(otool -l "${FRAMEWORK_DYLIB}" |
108                         grep -A10 "^ *cmd LC_ID_DYLIB$" |
109                         grep -m1 "^ *name" |
110                         sed -Ee 's/^ *name (.*) \(offset [0-9]+\)$/\1/')"
111  VERSION_PATH="/Versions/${CURRENT_VERSION_ID}/${FRAMEWORK_NAME_NOEXT}"
112  LC_ID_DYLIB_NEW="$(echo "${LC_ID_DYLIB_OLD}" |
113                     sed -Ee "s%${VERSION_PATH}$%/${FRAMEWORK_NAME_NOEXT}%")"
114
115  if [ "${LC_ID_DYLIB_NEW}" != "${LC_ID_DYLIB_OLD}" ] ; then
116    install_name_tool -id "${LC_ID_DYLIB_NEW}" "${FRAMEWORK_DYLIB}"
117  fi
118fi
119