| #!/bin/bash -p |
| |
| # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| # This is expected to be invoked by the Pulse build step. It takes care of |
| # doing the code signing and building the needed disk images. All needed |
| # support files are assumed to live next to this script on disk. |
| # |
| # usage: generate_dmgs output_dir input_dir codesign_keychain codesign_id \ |
| # omahaproxy_url [live_dmg_dir] |
| # |
| # Disk images are placed in output_dir. input_dir is the directory containing |
| # unzipped packaging data. omahaproxy_url will be consulted to determine the |
| # current live versions and URLs where they can be downloaded. live_dmg_dir |
| # is no longer used but is accepted on the command line to allow the script |
| # that calls this one to continue passing that argument to older versions of |
| # this script. |
| |
| set -eu |
| |
| # Environment sanitization. Set a known-safe PATH. Clear environment variables |
| # that might impact the interpreter's operation. The |bash -p| invocation |
| # on the #! line takes the bite out of BASH_ENV, ENV, and SHELLOPTS (among |
| # other features), but clearing them here ensures that they won't impact any |
| # shell scripts used as utility programs. SHELLOPTS is read-only and can't be |
| # unset, only unexported. |
| export PATH="/usr/bin:/bin:/usr/sbin:/sbin" |
| unset BASH_ENV CDPATH ENV GLOBIGNORE IFS POSIXLY_CORRECT |
| export -n SHELLOPTS |
| |
| ME="$(basename "${0}")" |
| readonly ME |
| PACKAGING_DIR="$(dirname "${0}")" |
| readonly PACKAGING_DIR |
| |
| readonly PRODUCT_NAME="Google Chrome" |
| readonly APP_NAME="${PRODUCT_NAME}.app" |
| DMG_PRODUCT_NAME="$(sed -e "s/ //" <<< "${PRODUCT_NAME}")" |
| readonly DMG_PRODUCT_NAME |
| |
| readonly APP_ID_KEY="CFBundleIdentifier" |
| readonly APP_VERSION_KEY="CFBundleShortVersionString" |
| readonly KS_VERSION_KEY="KSVersion" |
| readonly KS_CHANNEL_KEY="KSChannelID" |
| readonly KS_BRAND_KEY="KSBrandID" |
| readonly KS_ID_KEY="KSProductID" |
| readonly BP_VERSION_KEY="BreakpadVersion" |
| |
| readonly RSYNC_FLAGS=("-Clprt" "--include" "*.so" "--copy-unsafe-links") |
| readonly CURL_FLAGS=("--fail" "--location" "--silent" "--show-error") |
| |
| readonly OMAHAPROXY_PLATFORM="mac" |
| readonly MIN_DIFF_VERSION="6.0.434.0" |
| |
| # CH, BR, and NF are parallel arrays. An element at the same index in any of |
| # these arrays corresponds to the element at the same index in the others. |
| # CH is used for Keystone channel codes; BR is used for Keystone brand codes; |
| # NF is used for name fragments appended to the disk image name. |
| declare -a CH BR NF |
| |
| # Stable channel, generic, with no brand code. |
| CH+=(""); BR+=(""); NF+=("") |
| |
| # Stable channel with brand codes. |
| # Pure organic installs. |
| CH+=(""); BR+=("GGRO"); NF+=("Stable-PureOrganic") |
| |
| # google.xx marketing channels (house ads, toast promo, etc.) |
| CH+=(""); BR+=("GGRM"); NF+=("Stable-GoogleMarketing") |
| |
| # Off-network marketing installs (not originating from google.xx marketing.) |
| CH+=(""); BR+=("CHFA"); NF+=("Stable-OffNetworkMarketing") |
| |
| # Distribution deals. |
| CH+=(""); BR+=("MACD"); NF+=("Stable-DistributionDeals") |
| |
| # Beta, Dev, and Canary channels, no brand codes. |
| CH+=("beta"); BR+=(""); NF+=("Beta") |
| CH+=("dev"); BR+=(""); NF+=("Dev") |
| CH+=("canary"); BR+=(""); NF+=("Canary") |
| |
| readonly CH BR NF |
| |
| g_temp_dir= |
| |
| err() { |
| local error="${1}" |
| |
| echo "${ME}: ${error}" >& 2 |
| } |
| |
| declare -a g_cleanup |
| cleanup() { |
| local status=${?} |
| |
| trap - EXIT |
| trap '' HUP INT QUIT TERM |
| |
| if [[ ${status} -ge 128 ]]; then |
| err "Caught signal $((${status} - 128))" |
| fi |
| |
| if [[ "${#g_cleanup[@]}" -gt 0 ]]; then |
| rm -rf "${g_cleanup[@]}" |
| fi |
| |
| exit ${status} |
| } |
| |
| # Returns 0 if ${version1} and ${version2} are equal. Returns 1 if ${version1} |
| # is less than ${version2}. Returns 2 if ${version1} is greater than |
| # ${version2}. Returns 3 if ${version1} or ${version2} don't meet the expected |
| # format. A piece-wise comparison is performed. |
| readonly VERSION_RE="^([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)\$" |
| version_compare() { |
| local version1="${1}" |
| local version2="${2}" |
| |
| if ! [[ "${version1}" =~ ${VERSION_RE} ]]; then |
| return 3 |
| fi |
| |
| local version1_components=("${BASH_REMATCH[1]}" |
| "${BASH_REMATCH[2]}" |
| "${BASH_REMATCH[3]}" |
| "${BASH_REMATCH[4]}") |
| |
| if ! [[ "${version2}" =~ ${VERSION_RE} ]]; then |
| return 3 |
| fi |
| |
| local version2_components=("${BASH_REMATCH[1]}" |
| "${BASH_REMATCH[2]}" |
| "${BASH_REMATCH[3]}" |
| "${BASH_REMATCH[4]}") |
| |
| local i |
| for i in "${!version1_components[@]}"; do |
| local version1_component="${version1_components[${i}]}" |
| local version2_component="${version2_components[${i}]}" |
| |
| if [[ ${version1_component} -lt ${version2_component} ]]; then |
| # version1 is less than version2. |
| return 1 |
| fi |
| if [[ ${version1_component} -gt ${version2_component} ]]; then |
| # version1 is greater than version2. |
| return 2 |
| fi |
| done |
| |
| # The version numbers are equal. |
| return 0 |
| } |
| |
| is_version_gt() { |
| local version1="${1}" |
| local version2="${2}" |
| |
| if version_compare "${version1}" "${version2}"; then |
| # They're equal. |
| return 1 |
| elif [[ ${?} -eq 2 ]]; then |
| return 0 |
| fi |
| |
| return 1 |
| } |
| |
| is_version_ge() { |
| local version1="${1}" |
| local version2="${2}" |
| |
| if version_compare "${version1}" "${version2}"; then |
| # They're equal. |
| return 0 |
| elif [[ ${?} -eq 2 ]]; then |
| return 0 |
| fi |
| |
| return 1 |
| } |
| |
| # As of Mac OS X 10.8, defaults (and NSUserDefaults and CFPreferences) |
| # normally communicates with cfprefsd to read and write plists. Changes to a |
| # plist file aren't necessarily reflected immediately via this API family when |
| # not made through this API family, because cfprefsd may return cached data |
| # from a former on-disk version of a plist file instead of reading the current |
| # version from disk. The old behavior can be restored by setting the |
| # __CFPREFERENCES_AVOID_DAEMON environment variable, although extreme care |
| # should be used because portions of the system that use this API family |
| # normally and thus use cfprefsd and its cache will become unsynchronized with |
| # the on-disk state. |
| # |
| # This function is provided to set __CFPREFERENCES_AVOID_DAEMON when calling |
| # "defaults" and thus avoid cfprefsd and its on-disk cache, and is intended |
| # only to be used to read values from and write values to Info.plist files, |
| # which are not preferences. The use of "defaults" for this purpose has always |
| # been questionable, but there's no better option to interact with plists from |
| # shell scripts. Definitely don't use defaults_disk to read or write |
| # preference plists. |
| # |
| # In avoiding cfprefsd, this function is a "defaults" that always operates on |
| # the disk. |
| defaults_disk() { |
| __CFPREFERENCES_AVOID_DAEMON=1 defaults "${@}" |
| } |
| |
| build_dmg() { |
| if [[ ${#} -ne 8 ]]; then |
| err "build_dmg: wrong number of arguments" |
| exit 1 |
| fi |
| |
| local output_dir="${1}" |
| local half_signed_app_path="${2}" |
| local codesign_keychain="${3}" |
| local codesign_id="${4}" |
| local app_version="${5}" |
| local channel_id="${6}" |
| local brand_id="${7}" |
| local name_fragment="${8}" |
| |
| # If the name fragment isn't empty, add a dash in front of it. |
| local dash_fragment="${name_fragment}" |
| if [[ -n "${dash_fragment}" ]]; then |
| dash_fragment="-${dash_fragment}" |
| fi |
| |
| local dmg_product_name="${DMG_PRODUCT_NAME}" |
| local product_name="${PRODUCT_NAME}" |
| |
| # Canary: The disk image should use GoogleChromeCanary as the product name |
| # instead of putting the fragment after the version number. |
| if [[ "${channel_id}" = "canary" ]]; then |
| product_name="${product_name} ${name_fragment}" |
| dmg_product_name="${dmg_product_name}${name_fragment}" |
| dash_fragment="" |
| fi |
| |
| local dmg_name="${dmg_product_name}-${app_version}${dash_fragment}.dmg" |
| |
| # Even with --verbosity 0, and even when it succeeds, hdiutil (via pkg-dmg) |
| # prints a newline to stderr. That looks pretty weird. Make it evident that |
| # things are progressing by printing a message for each disk image. |
| err "creating ${dmg_name}" |
| |
| local variant_dir="${g_temp_dir}/variant" |
| mkdir "${variant_dir}" |
| local variant_app_path="${variant_dir}/${APP_NAME}" |
| |
| # Copy the half-signed application to a variant-specific directory. |
| rsync "${RSYNC_FLAGS[@]}" "${half_signed_app_path}/" "${variant_app_path}" |
| |
| local app_plist="${variant_app_path}/Contents/Info" |
| local ks_plist="${app_plist}" |
| |
| # Apply the channel and brand code changes. |
| if [[ -z "${channel_id}" ]]; then |
| defaults_disk delete "${ks_plist}" "${KS_CHANNEL_KEY}" 2> /dev/null || true |
| else |
| defaults_disk write "${ks_plist}" "${KS_CHANNEL_KEY}" \ |
| -string "${channel_id}" |
| fi |
| |
| # See build/mac/tweak_info_plist.py and chrome/browser/mac/keystone_glue.mm. |
| local tag_suffixes="$(defaults_disk read "${ks_plist}" | \ |
| sed -En -e 's/.*"KSChannelID-(.*)" = ".*";/-\1/p')" |
| local tag_suffix |
| for tag_suffix in ${tag_suffixes}; do |
| local ks_tag="${channel_id}${tag_suffix}" |
| local ks_tag_key="${KS_CHANNEL_KEY}${tag_suffix}" |
| defaults_disk write "${ks_plist}" "${ks_tag_key}" -string "${ks_tag}" |
| done |
| |
| if [[ -z "${brand_id}" ]]; then |
| defaults_disk delete "${ks_plist}" "${KS_BRAND_KEY}" 2> /dev/null || true |
| else |
| defaults_disk write "${ks_plist}" "${KS_BRAND_KEY}" -string "${brand_id}" |
| fi |
| |
| # Canary: the outer application bundle needs a distinct bundle ID for the |
| # system's bundle ID to disambiguate from a simultaneously-installed |
| # non-canary, and for Keystone to support side-by-side auto-update. It must |
| # keep the original Breakpad ID as this ID has been white-listed by the |
| # crash server. The CrProductDirName key, normally absent, is set specially |
| # in the canary build to force the default profile to an alternate |
| # directory. The CFBundleSignature key and PkgInfo file are set to contain |
| # the canary's distinct signature. The executable is renamed, and |
| # CFBundleExecutable is changed to point to it. |
| local app_bundle_id_old="$(defaults_disk read "${app_plist}" "${APP_ID_KEY}")" |
| local app_bundle_id_new="${app_bundle_id_old}" |
| if [[ "${channel_id}" = "canary" ]]; then |
| app_bundle_id_new="${app_bundle_id_old}.canary" |
| defaults_disk write "${app_plist}" "${APP_ID_KEY}" "${app_bundle_id_new}" |
| local ks_bundle_id="$(defaults_disk read "${ks_plist}" "${KS_ID_KEY}")" |
| defaults_disk write "${ks_plist}" "${KS_ID_KEY}" "${ks_bundle_id}.canary" |
| defaults_disk write "${app_plist}" CrProductDirName "Google/Chrome Canary" |
| readonly CANARY_SIGNATURE="Pipi" |
| defaults_disk write "${app_plist}" CFBundleSignature "${CANARY_SIGNATURE}" |
| echo -n "APPL${CANARY_SIGNATURE}" > "${variant_app_path}/Contents/PkgInfo" |
| |
| mv "${variant_app_path}/Contents/MacOS/Google Chrome" \ |
| "${variant_app_path}/Contents/MacOS/Google Chrome Canary" |
| defaults_disk write "${app_plist}" CFBundleExecutable "Google Chrome Canary" |
| fi |
| |
| # Info.plist will work perfectly well in any plist format, but traditionally |
| # applications use xml1 for this, so convert it back after whatever defaults |
| # might have done. |
| plutil -convert xml1 "${app_plist}.plist" |
| if [[ "${app_plist}" != "${ks_plist}" ]]; then |
| plutil -convert xml1 "${ks_plist}.plist" |
| fi |
| |
| # Canary: use different icons for the application and its documents. Adjust |
| # the bundle ID used for the managed preferences manifest. |
| if [[ "${channel}" = "canary" ]]; then |
| rsync "${RSYNC_FLAGS[@]}" "${PACKAGING_DIR}/app_canary.icns" \ |
| "${variant_app_path}/Contents/Resources/app.icns" |
| rsync "${RSYNC_FLAGS[@]}" "${PACKAGING_DIR}/document_canary.icns" \ |
| "${variant_app_path}/Contents/Resources/document.icns" |
| |
| local manifest_dir_new="\ |
| ${variant_app_path}/Contents/Resources/${app_bundle_id_new}.manifest" |
| mv "${variant_app_path}/Contents/Resources/${app_bundle_id_old}.manifest" \ |
| "${manifest_dir_new}" |
| local manifest_plist_tmp="${variant_dir}/manifest" |
| mv "${manifest_dir_new}/Contents/Resources/${app_bundle_id_old}.manifest" \ |
| "${manifest_plist_tmp}.plist" |
| defaults_disk write "${manifest_plist_tmp}" pfm_domain \ |
| "${app_bundle_id_new}" |
| plutil -convert xml1 "${manifest_plist_tmp}.plist" |
| mv "${manifest_plist_tmp}.plist" \ |
| "${manifest_dir_new}/Contents/Resources/${app_bundle_id_new}.manifest" |
| fi |
| |
| # Sign the application - it's now fully signed. |
| "${PACKAGING_DIR}/sign_app.sh" "${variant_app_path}" \ |
| "${codesign_keychain}" "${codesign_id}" |
| |
| local app_name="${APP_NAME}" |
| |
| # Canary: the application's name in the disk image should be changed from |
| # Google Chrome.app to Google Chrome Canary.app. The canary also uses a |
| # different .DS_Store file and has a different volume icon. |
| local dsstore_name="chrome_dmg_dsstore" |
| local icon_name="chrome_dmg_icon.icns" |
| if [[ "${channel_id}" = "canary" ]]; then |
| app_name="$(sed -e "s/\\.app\$/ ${name_fragment}.app/" <<< "${app_name}")" |
| dsstore_name="chrome_canary_dmg_dsstore" |
| icon_name="chrome_canary_dmg_icon.icns" |
| fi |
| |
| # A locally-created empty directory is more trustworthy than /var/empty. |
| local empty_dir="${g_temp_dir}/empty" |
| mkdir "${empty_dir}" |
| |
| local dmg_path="${output_dir}/${dmg_name}" |
| |
| # Make the disk image. Don't include ${name_fragment}, ${dash_fragment}, |
| # or anything else in --volname because the .DS_Store expects the volume |
| # name to be constant. Don't put a name on the /Applications symbolic link |
| # because the same disk image is used for all languages. |
| "${PACKAGING_DIR}/pkg-dmg" \ |
| --verbosity 0 \ |
| --tempdir "${g_temp_dir}" \ |
| --source "${empty_dir}" \ |
| --target "${dmg_path}" \ |
| --format UDBZ \ |
| --volname "${product_name}" \ |
| --icon "${PACKAGING_DIR}/${icon_name}" \ |
| --copy "${variant_app_path}/:/${app_name}" \ |
| --copy "${PACKAGING_DIR}/keystone_install.sh:/.keystone_install" \ |
| --mkdir "/.background" \ |
| --copy \ |
| "${PACKAGING_DIR}/chrome_dmg_background.png:/.background/background.png" \ |
| --copy "${PACKAGING_DIR}/${dsstore_name}:/.DS_Store" \ |
| --symlink "/Applications:/ " |
| |
| rmdir "${empty_dir}" |
| rm -rf "${variant_dir}" |
| } |
| |
| # shell_safe_path ensures that |path| is safe to pass to tools as a |
| # command-line argument. If the first character in |path| is "-", "./" is |
| # prepended to it. The possibily-modified |path| is output. |
| shell_safe_path() { |
| local path="${1}" |
| if [[ "${path:0:1}" = "-" ]]; then |
| echo "./${path}" |
| else |
| echo "${path}" |
| fi |
| } |
| |
| usage() { |
| echo "usage: ${ME}: output_dir input_dir codesign_keychain codesign_id \ |
| omahaproxy_url" >& 2 |
| } |
| |
| main() { |
| local output_dir input_dir codesign_keychain codesign_id omahaproxy_url |
| output_dir="$(shell_safe_path "${1}")" |
| input_dir="$(shell_safe_path "${2}")" |
| codesign_keychain="$(shell_safe_path "${3}")" |
| codesign_id="${4}" |
| omahaproxy_url="${5}" |
| |
| trap cleanup EXIT HUP INT QUIT TERM |
| |
| err "output_dir=${output_dir}" |
| err "input_dir=${input_dir}" |
| err "codesign_keychain=${codesign_keychain}" |
| err "codesign_id=${codesign_id}" |
| err "omahaproxy_url=${omahaproxy_url}" |
| |
| if [[ -z "${input_dir}" ]] || |
| [[ "${input_dir:0:1}" != "/" ]] || |
| [[ ! -d "${input_dir}" ]]; then |
| # input_dir needs to be an absolute path because that's what |defaults| |
| # requires. |
| err "input_dir must exist and be an absolute path to a directory" |
| usage |
| exit 1 |
| fi |
| if [[ ! -f "${codesign_keychain}" ]]; then |
| err "codesign_keychain must exist and be a file" |
| usage |
| exit 1 |
| fi |
| |
| local source_app_path="${input_dir}/${APP_NAME}" |
| |
| if [[ ! -d "${source_app_path}" ]]; then |
| err "${source_app_path} not found" |
| exit 1 |
| fi |
| |
| # Make sure ${output_dir} doesn't already exist, or if it does, make sure |
| # it's empty. |
| if [[ ! -d "${output_dir}" ]]; then |
| mkdir "${output_dir}" |
| fi |
| shopt -s nullglob dotglob |
| local output_dir_contents=("${output_dir}"/*) |
| shopt -u nullglob dotglob |
| if [[ ${#output_dir_contents[@]} -ne 0 ]]; then |
| err "output_dir must not exist or be empty" |
| exit 1 |
| fi |
| |
| # Application sanity checks. Make sure that it smells right and that the |
| # version numbers in the various plists match. |
| local app_plist="${source_app_path}/Contents/Info" |
| local app_version |
| app_version="$(defaults_disk read "${app_plist}" "${APP_VERSION_KEY}")" |
| local versioned_dir="${source_app_path}/Contents/Versions/${app_version}" |
| local framework_path="${versioned_dir}/${PRODUCT_NAME} Framework.framework" |
| local framework_plist="${framework_path}/Resources/Info" |
| |
| local ks_plist="${app_plist}" |
| local ks_version |
| if ! ks_version="$(defaults_disk read "${ks_plist}" "${KS_VERSION_KEY}")"; \ |
| then |
| err "Keystone version not present" |
| exit 1 |
| fi |
| if [[ "${ks_version}" != "${app_version}" ]]; then |
| err "Keystone version does not match application version" |
| exit 1 |
| fi |
| |
| local bp_plist="${framework_plist}" |
| local bp_version |
| if ! bp_version="$(defaults_disk read "${bp_plist}" "${BP_VERSION_KEY}")"; \ |
| then |
| err "Breakpad version not present" |
| exit 1 |
| fi |
| if [[ "${bp_version}" != "${app_version}" ]]; then |
| err "Breakpad version does not match application version" |
| exit 1 |
| fi |
| |
| # Consult omahaproxy to determine the current live versions on each channel. |
| # Get omahaproxy output first in a separate variable, allowing a failure |
| # to be caught directly. |
| # |
| # omahaproxy runs on appengine, which makes it the least reliable part of |
| # the process. Loop around omahaproxy to retry in the event of a failure. |
| # Do this early, so that the script can fail before performing major work |
| # in the event that omahaproxy output can't be collected. |
| local omahaproxy_full_url="\ |
| ${omahaproxy_url}/dl_urls?os=${OMAHAPROXY_PLATFORM}" |
| err "consulting ${omahaproxy_full_url}" |
| local omahaproxy_output |
| local i |
| for i in {1..5}; do |
| if ! omahaproxy_output="$(curl "${CURL_FLAGS[@]}" --max-time 15 \ |
| "${omahaproxy_full_url}")" || |
| [[ -z "${omahaproxy_output}" ]]; then |
| if [[ ${i} -lt 5 ]]; then |
| err "warning: could not get omahaproxy output, will retry" |
| sleep 15 |
| else |
| err "could not get omahaproxy output" |
| exit 1 |
| fi |
| else |
| break |
| fi |
| done |
| |
| # Loop through the live versions reported by omahaproxy. |
| local platform channel version url |
| local live_versions=() |
| local live_urls=() |
| local line_number=0 |
| while IFS="," read -r platform channel version url; do |
| line_number=$((${line_number} + 1)) |
| if [[ ${line_number} -eq 1 ]]; then |
| # Skip the first line, it's a header. |
| continue |
| fi |
| |
| if [[ "${platform}" != "${OMAHAPROXY_PLATFORM}" ]]; then |
| err "omahaproxy returned data for platform ${platform}" |
| exit 1 |
| fi |
| |
| # In CH and as used with Keystone, the stable channel is an empty/unset |
| # string. omahaproxy reports it as "stable". Change "stable" to "" for |
| # comparison with the elements of the CH array. |
| if [[ "${channel}" = "stable" ]]; then |
| channel="" |
| fi |
| |
| # Loop through the known channels, and save the live version for each |
| # known channel. |
| for i in "${!CH[@]}"; do |
| local channel_i="${CH[${i}]}" |
| |
| # Skip anything with a brand code. Every channel with a brand code |
| # should also appear once without a brand code. |
| if [[ -n "${BR[${i}]}" ]]; then |
| continue |
| fi |
| |
| if [[ "${channel_i}" = "${channel}" ]]; then |
| live_versions[${i}]="${version}" |
| live_urls[${i}]="${url}" |
| fi |
| done |
| done <<< "${omahaproxy_output}" |
| |
| g_cleanup+=("${output_dir}") |
| |
| g_temp_dir="$(mktemp -d -t "${ME}")" |
| g_cleanup+=("${g_temp_dir}") |
| |
| # A "half-signed" application has its inner components signed, but the |
| # outermost browser application bundle is unsigned. The outer bundle can't |
| # be signed until Keystone channel and branding values are added in |
| # build_dmg. However, in order to facilitate differential updates, the inner |
| # components need to be byte-for-byte identical regardless of the Keystone |
| # values. The Keystone values are not placed in any inner component, and the |
| # inner components are signed just once, here, in order to guarantee |
| # that they'll be identical for each Keystone channel and branding setting. |
| # A signature includes a signing timestamp, so there can be only one signing |
| # operation of the inner components even if there are no other changes. |
| |
| local half_signed_dir="${g_temp_dir}/half_signed" |
| mkdir "${half_signed_dir}" |
| local half_signed_app_path="${half_signed_dir}/${APP_NAME}" |
| |
| rsync "${RSYNC_FLAGS[@]}" "${source_app_path}/" "${half_signed_app_path}" |
| |
| err "half-signing application" |
| "${PACKAGING_DIR}/sign_versioned_dir.sh" "${half_signed_app_path}" \ |
| "${codesign_keychain}" "${codesign_id}" |
| |
| if [[ ${#CH[@]} -ne ${#BR[@]} ]] || |
| [[ ${#CH[@]} -ne ${#NF[@]} ]]; then |
| err "there must be the same number of channels, brands, and name fragments" |
| exit 1 |
| fi |
| |
| # Loop through, setting the channels and brand codes, signing everything, |
| # and building disk images. |
| for i in "${!CH[@]}"; do |
| local channel="${CH[${i}]}" |
| local brand="${BR[${i}]}" |
| local name_fragment="${NF[${i}]}" |
| |
| build_dmg "${output_dir}" "${half_signed_app_path}" \ |
| "${codesign_keychain}" "${codesign_id}" \ |
| "${app_version}" "${channel}" "${brand}" "${name_fragment}" |
| done |
| |
| # Loop through everything with a live version. Note that bash arrays are |
| # sparse, and ${!live_versions[@]} will only return indices that were set in |
| # the ${live_versions} array. |
| for i in "${!live_versions[@]}"; do |
| local live_version="${live_versions[${i}]}" |
| local live_url="${live_urls[${i}]}" |
| local channel="${CH[${i}]}" |
| local name_fragment="${NF[${i}]}" |
| |
| # If the name fragment isn't empty, add a dash in front of it. |
| local dash_fragment="${name_fragment}" |
| if [[ -n "${dash_fragment}" ]]; then |
| dash_fragment="-${dash_fragment}" |
| fi |
| |
| local product_name="${PRODUCT_NAME}" |
| local dmg_product_name="${DMG_PRODUCT_NAME}" |
| |
| # Canary: The disk image should use GoogleChromeCanary as the product name |
| # instead of putting the fragment after the version number. |
| if [[ "${channel}" = "canary" ]]; then |
| product_name="${product_name} ${name_fragment}" |
| dmg_product_name="${dmg_product_name}${name_fragment}" |
| dash_fragment="" |
| fi |
| |
| local print_channel="${channel}" |
| if [[ -z "${print_channel}" ]]; then |
| print_channel="stable" |
| fi |
| |
| if ! is_version_ge "${live_version}" "${MIN_DIFF_VERSION}"; then |
| # The live version is too old to be updated with a differential |
| # updater. Skip it. |
| err "skipping ${print_channel} differential updater, \ |
| live ${live_version} < minimum ${MIN_DIFF_VERSION}" |
| continue |
| fi |
| |
| if ! is_version_gt "${app_version}" "${live_version}"; then |
| # The live version is newer than the app version; a differential updater |
| # would actually be a downgrader. Skip it. |
| err "skipping ${print_channel} differential updater, \ |
| new ${app_version} <= live ${live_version}" |
| continue |
| fi |
| |
| local live_major=$(cut -d. -f1 <<< "${live_version}") |
| local app_major=$(cut -d. -f1 <<< "${app_version}") |
| if [[ ${app_major} -gt $((${live_major} + 1)) ]]; then |
| err "skipping ${print_channel} differential updater, \ |
| new ${app_version} is more than one major version ahead of live ${live_version}" |
| continue |
| fi |
| |
| err "downloading ${live_url}" |
| local live_dmg="${g_temp_dir}/\ |
| ${dmg_product_name}-${live_version}${dash_fragment}.dmg" |
| for i in {1..5}; do |
| if ! curl "${CURL_FLAGS[@]}" --max-time 120 "${live_url}" \ |
| -o "${live_dmg}" || |
| ! [[ -f "${live_dmg}" ]]; then |
| if [[ ${i} -lt 5 ]]; then |
| err "warning: could not get live image, will retry" |
| sleep 15 |
| else |
| err "live_dmg file ${live_dmg} not found" |
| exit 1 |
| fi |
| else |
| break |
| fi |
| done |
| |
| local new_dmg="${output_dir}/\ |
| ${dmg_product_name}-${app_version}${dash_fragment}.dmg" |
| if ! [[ -f "${new_dmg}" ]]; then |
| err "new_dmg file ${new_dmg} not found" |
| exit 1 |
| fi |
| |
| local patch_dmg_name=\ |
| "${dmg_product_name}-${live_version}-${app_version}${dash_fragment}-Update.dmg" |
| local patch_dmg="${output_dir}/${patch_dmg_name}" |
| |
| err "creating ${patch_dmg_name}" |
| "${PACKAGING_DIR}/dmgdiffer.sh" \ |
| "${product_name}" "${live_dmg}" "${new_dmg}" "${patch_dmg}" |
| done |
| |
| rm -rf "${g_temp_dir}" |
| unset g_cleanup[${#g_cleanup[@]}] # g_temp_dir |
| |
| unset g_cleanup[${#g_cleanup[@]}] # out_dir |
| trap - EXIT |
| } |
| |
| # Use -lt instead of -eq to tolerate this script's caller providing more |
| # arguments. This script's caller may be upgraded to provide more arguments |
| # to future versions of this script, but if this version runs, it should be |
| # able to operate with only the arguments it needs. |
| if [[ ${#} -lt 5 ]]; then |
| usage |
| exit 1 |
| fi |
| |
| main "${@}" |
| exit ${?} |