Index: chrome_mac/Google Chrome Packaging/dirdiffer.sh |
=================================================================== |
--- chrome_mac/Google Chrome Packaging/dirdiffer.sh (revision 0) |
+++ chrome_mac/Google Chrome Packaging/dirdiffer.sh (revision 0) |
@@ -0,0 +1,545 @@ |
+#!/bin/bash -p |
+ |
+# Copyright (c) 2011 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. |
+ |
+# usage: dirdiffer.sh old_dir new_dir patch_dir |
+# |
+# dirdiffer creates a patch directory patch_dir that represents the difference |
+# between old_dir and new_dir. patch_dir can be used with dirpatcher to |
+# recreate new_dir given old_dir. |
+# |
+# dirdiffer operates recursively, properly handling ordinary files, symbolic |
+# links, and directories, as they are found in new_dir. Symbolic links and |
+# directories are always replicated as-is in patch_dir. Ordinary files will |
+# be represented at the appropriate location in patch_dir by one of the |
+# following: |
+# |
+# - a binary diff prepared by goobsdiff that can transform the file at the |
+# same position in old_dir to the version in new_dir, but only when such a |
+# file already exists in old_dir and is an ordinary file. These files are |
+# given a "$gbs" suffix. |
+# - a bzip2-compressed copy of the new file from new_dir; in patch_dir, the |
+# new file will have a "$bz2" suffix. |
+# - a gzip-compressed copy of the new file from new_dir; in patch_dir, the |
+# new file will have a "$gz" suffix. |
+# - an xz/lzma2-compressed copy of the new file from new_dir; in patch_dir, |
+# the new file will have an "$xz" suffix. |
+# - an uncompressed copy of the new file from new_dir; in patch_dir, the |
+# new file will have a "$raw" suffix. |
+# |
+# The unconventional suffixes are used because they aren't likely to occur in |
+# filenames. |
+# |
+# Of these options, the smallest possible representation is chosen. Note that |
+# goobsdiff itself will also compress various sections of a binary diff with |
+# bzip2, gzip, or xz/lzma2, or leave them uncompressed, according to which is |
+# smallest. The approach of choosing the smallest possible representation is |
+# time-consuming but given the choices of compressors results in an overall |
+# size reduction of about 3%-5% relative to using bzip2 as the only |
+# compressor; bzip2 is generally more effective for these data sets than gzip, |
+# and xz/lzma2 more effective than bzip2. |
+# |
+# For large input files, goobsdiff is also very time-consuming and |
+# memory-intensive. The overall "wall clock time" spent preparing a patch_dir |
+# representing the differences between Google Chrome's 6.0.422.0 and 6.0.427.0 |
+# versioned directories from successive weekly dev channel releases on a |
+# 2.53GHz dual-core 4GB MacBook Pro is 3 minutes. Reconstructing new_dir with |
+# dirpatcher is much quicker; in the above configuration, only 10 seconds are |
+# needed for reconstruction. |
+# |
+# After creating a full patch_dir structure, but before returning, dirpatcher |
+# is invoked to attempt to recreate new_dir in a temporary location given |
+# old_dir and patch_dir. The recreated new_dir is then compared against the |
+# original new_dir as a verification step. Should verification fail, dirdiffer |
+# exits with a nonzero status, and patch_dir should not be used. |
+# |
+# Environment variables: |
+# DIRDIFFER_EXCLUDE |
+# When an entry in new_dir matches this regular expression, it will not be |
+# included in patch_dir. All prospective paths in new_dir will be matched |
+# against this regular expression, including directories. If a directory |
+# matches this pattern, dirdiffer will also ignore the directory's contents. |
+# DIRDIFFER_NO_DIFF |
+# When an entry in new_dir matches this regular expression, it will not be |
+# represented in patch_dir by a $gbs file prepared by goobsdiff. It will only |
+# appear as a $bz2, $gz, or $raw file. Only files in new_dir, not |
+# directories, will be matched against this regular expression. |
+# |
+# Exit codes: |
+# 0 OK |
+# 1 Unknown failure |
+# 2 Incorrect number of parameters |
+# 3 Input directories do not exist or are not directories |
+# 4 Output directory already exists |
+# 5 Parent of output directory does not exist or is not a directory |
+# 6 An input or output directories contains another |
+# 7 Could not create output directory |
+# 8 File already exists in output directory |
+# 9 Found an irregular file (non-directory, file, or symbolic link) in input |
+# 10 Could not create symbolic link |
+# 11 File copy failed |
+# 12 bzip2 compression failed |
+# 13 gzip compression failed |
+# 14 xz/lzma2 compression failed |
+# 15 Patch creation failed |
+# 16 Verification failed |
+# 17 Could not set mode (permissions) |
+# 18 Could not set modification time |
+# 19 Invalid regular expression (irregular expression?) |
+ |
+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 |
+ |
+shopt -s dotglob nullglob |
+ |
+# find_tool looks for an executable file named |tool_name|: |
+# - in the same directory as this script, |
+# - if this script is located in a Chromium source tree, at the expected |
+# Release output location in the Mac out directory, |
+# - as above, but in the Debug output location |
+# If found in any of the above locations, the script's path is output. |
+# Otherwise, this function outputs |tool_name| as a fallback, allowing it to |
+# be found (or not) by an ordinary ${PATH} search. |
+find_tool() { |
+ local tool_name="${1}" |
+ |
+ local script_dir |
+ script_dir="$(dirname "${0}")" |
+ |
+ local tool="${script_dir}/${tool_name}" |
+ if [[ -f "${tool}" ]] && [[ -x "${tool}" ]]; then |
+ echo "${tool}" |
+ return |
+ fi |
+ |
+ local script_dir_phys |
+ script_dir_phys="$(cd "${script_dir}" && pwd -P)" |
+ if [[ "${script_dir_phys}" =~ ^(.*)/src/chrome/installer/mac$ ]]; then |
+ tool="${BASH_REMATCH[1]}/src/out/Release/${tool_name}" |
+ if [[ -f "${tool}" ]] && [[ -x "${tool}" ]]; then |
+ echo "${tool}" |
+ return |
+ fi |
+ |
+ tool="${BASH_REMATCH[1]}/src/out/Debug/${tool_name}" |
+ if [[ -f "${tool}" ]] && [[ -x "${tool}" ]]; then |
+ echo "${tool}" |
+ return |
+ fi |
+ fi |
+ |
+ echo "${tool_name}" |
+} |
+ |
+ME="$(basename "${0}")" |
+readonly ME |
+DIRPATCHER="$(dirname "${0}")/dirpatcher.sh" |
+readonly DIRPATCHER |
+GOOBSDIFF="$(find_tool goobsdiff)" |
+readonly GOOBSDIFF |
+readonly BZIP2="bzip2" |
+readonly GZIP="gzip" |
+XZ="$(find_tool xz)" |
+readonly XZ |
+readonly GBS_SUFFIX='$gbs' |
+readonly BZ2_SUFFIX='$bz2' |
+readonly GZ_SUFFIX='$gz' |
+readonly XZ_SUFFIX='$xz' |
+readonly PLAIN_SUFFIX='$raw' |
+ |
+# Workaround for http://code.google.com/p/chromium/issues/detail?id=83180#c3 |
+# In bash 4.0, "declare VAR" no longer initializes VAR if not already set. |
+: ${DIRDIFFER_EXCLUDE:=} |
+: ${DIRDIFFER_NO_DIFF:=} |
+ |
+err() { |
+ local error="${1}" |
+ |
+ echo "${ME}: ${error}" >& 2 |
+} |
+ |
+declare -a g_cleanup g_verify_exclude |
+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} |
+} |
+ |
+copy_mode_and_time() { |
+ local new_file="${1}" |
+ local patch_file="${2}" |
+ |
+ local mode |
+ mode="$(stat "-f%OMp%OLp" "${new_file}")" |
+ if ! chmod -h "${mode}" "${patch_file}"; then |
+ exit 17 |
+ fi |
+ |
+ if ! [[ -L "${patch_file}" ]]; then |
+ # Symbolic link modification times can't be copied because there's no |
+ # shell tool that provides direct access to lutimes. Instead, the symbolic |
+ # link was created with rsync, which already copied the timestamp with |
+ # lutimes. |
+ if ! touch -r "${new_file}" "${patch_file}"; then |
+ exit 18 |
+ fi |
+ fi |
+} |
+ |
+file_size() { |
+ local file="${1}" |
+ |
+ stat -f %z "${file}" |
+} |
+ |
+make_patch_file() { |
+ local old_file="${1}" |
+ local new_file="${2}" |
+ local patch_file="${3}" |
+ |
+ local uncompressed_file="${patch_file}${PLAIN_SUFFIX}" |
+ if ! cp "${new_file}" "${uncompressed_file}"; then |
+ exit 11 |
+ fi |
+ local uncompressed_size |
+ uncompressed_size="$(file_size "${new_file}")" |
+ |
+ local keep_file="${uncompressed_file}" |
+ local keep_size="${uncompressed_size}" |
+ |
+ local bz2_file="${patch_file}${BZ2_SUFFIX}" |
+ if [[ -e "${bz2_file}" ]]; then |
+ err "${bz2_file} already exists" |
+ exit 8 |
+ fi |
+ if ! "${BZIP2}" -9c < "${new_file}" > "${bz2_file}"; then |
+ err "couldn't compress ${new_file} to ${bz2_file} with ${BZIP2}" |
+ exit 12 |
+ fi |
+ local bz2_size |
+ bz2_size="$(file_size "${bz2_file}")" |
+ |
+ if [[ "${bz2_size}" -ge "${keep_size}" ]]; then |
+ rm -f "${bz2_file}" |
+ else |
+ rm -f "${keep_file}" |
+ keep_file="${bz2_file}" |
+ keep_size="${bz2_size}" |
+ fi |
+ |
+ local gz_file="${patch_file}${GZ_SUFFIX}" |
+ if [[ -e "${gz_file}" ]]; then |
+ err "${gz_file} already exists" |
+ exit 8 |
+ fi |
+ if ! "${GZIP}" -9cn < "${new_file}" > "${gz_file}"; then |
+ err "couldn't compress ${new_file} to ${gz_file} with ${GZIP}" |
+ exit 13 |
+ fi |
+ local gz_size |
+ gz_size="$(file_size "${gz_file}")" |
+ |
+ if [[ "${gz_size}" -ge "${keep_size}" ]]; then |
+ rm -f "${gz_file}" |
+ else |
+ rm -f "${keep_file}" |
+ keep_file="${gz_file}" |
+ keep_size="${gz_size}" |
+ fi |
+ |
+ local xz_flags=("-c") |
+ |
+ # If the file looks like a Mach-O file, including a universal/fat file, add |
+ # the x86 BCJ filter, which results in slightly better compression of x86 |
+ # and x86_64 executables. Mach-O files might contain other architectures, |
+ # but they aren't currently expected in Chrome. |
+ local file_output |
+ file_output="$(file "${new_file}" 2> /dev/null || true)" |
+ if [[ "${file_output}" =~ Mach-O ]]; then |
+ xz_flags+=("--x86") |
+ fi |
+ |
+ # Use an lzma2 encoder. This is equivalent to xz -9 -e, but allows filters |
+ # to precede the compressor. |
+ xz_flags+=("--lzma2=preset=9e") |
+ |
+ local xz_file="${patch_file}${XZ_SUFFIX}" |
+ if [[ -e "${xz_file}" ]]; then |
+ err "${xz_file} already exists" |
+ exit 8 |
+ fi |
+ if ! "${XZ}" "${xz_flags[@]}" < "${new_file}" > "${xz_file}"; then |
+ err "couldn't compress ${new_file} to ${xz_file} with ${XZ}" |
+ exit 14 |
+ fi |
+ local xz_size |
+ xz_size="$(file_size "${xz_file}")" |
+ |
+ if [[ "${xz_size}" -ge "${keep_size}" ]]; then |
+ rm -f "${xz_file}" |
+ else |
+ rm -f "${keep_file}" |
+ keep_file="${xz_file}" |
+ keep_size="${xz_size}" |
+ fi |
+ |
+ if [[ -f "${old_file}" ]] && ! [[ -L "${old_file}" ]] && |
+ ! [[ "${new_file}" =~ ${DIRDIFFER_NO_DIFF} ]]; then |
+ local gbs_file="${patch_file}${GBS_SUFFIX}" |
+ if [[ -e "${gbs_file}" ]]; then |
+ err "${gbs_file} already exists" |
+ exit 8 |
+ fi |
+ if ! "${GOOBSDIFF}" "${old_file}" "${new_file}" "${gbs_file}"; then |
+ err "couldn't create ${gbs_file} by comparing ${old_file} to ${new_file}" |
+ exit 15 |
+ fi |
+ local gbs_size |
+ gbs_size="$(file_size "${gbs_file}")" |
+ |
+ if [[ "${gbs_size}" -ge "${keep_size}" ]]; then |
+ rm -f "${gbs_file}" |
+ else |
+ rm -f "${keep_file}" |
+ keep_file="${gbs_file}" |
+ keep_size="${gbs_size}" |
+ fi |
+ fi |
+ |
+ copy_mode_and_time "${new_file}" "${keep_file}" |
+} |
+ |
+make_patch_symlink() { |
+ local new_file="${1}" |
+ local patch_file="${2}" |
+ |
+ # local target |
+ # target="$(readlink "${new_file}")" |
+ # ln -s "${target}" "${patch_file}" |
+ |
+ # Use rsync instead of the above, as it's the only way to preserve the |
+ # timestamp of a symbolic link using shell tools. |
+ if ! rsync -lt "${new_file}" "${patch_file}"; then |
+ exit 10 |
+ fi |
+ |
+ copy_mode_and_time "${new_file}" "${patch_file}" |
+} |
+ |
+make_patch_dir() { |
+ local old_dir="${1}" |
+ local new_dir="${2}" |
+ local patch_dir="${3}" |
+ |
+ if ! mkdir "${patch_dir}"; then |
+ exit 7 |
+ fi |
+ |
+ local new_file |
+ for new_file in "${new_dir}/"*; do |
+ local file="${new_file:${#new_dir} + 1}" |
+ local old_file="${old_dir}/${file}" |
+ local patch_file="${patch_dir}/${file}" |
+ |
+ if [[ "${new_file}" =~ ${DIRDIFFER_EXCLUDE} ]]; then |
+ g_verify_exclude+=("${new_file}") |
+ continue |
+ fi |
+ |
+ if [[ -e "${patch_file}" ]]; then |
+ err "${patch_file} already exists" |
+ exit 8 |
+ fi |
+ |
+ if [[ -L "${new_file}" ]]; then |
+ make_patch_symlink "${new_file}" "${patch_file}" |
+ elif [[ -d "${new_file}" ]]; then |
+ make_patch_dir "${old_file}" "${new_file}" "${patch_file}" |
+ elif [[ ! -f "${new_file}" ]]; then |
+ err "can't handle irregular file ${new_file}" |
+ exit 9 |
+ else |
+ make_patch_file "${old_file}" "${new_file}" "${patch_file}" |
+ fi |
+ done |
+ |
+ copy_mode_and_time "${new_dir}" "${patch_dir}" |
+} |
+ |
+verify_patch_dir() { |
+ local old_dir="${1}" |
+ local new_dir="${2}" |
+ local patch_dir="${3}" |
+ |
+ local verify_temp_dir verify_dir |
+ verify_temp_dir="$(mktemp -d -t "${ME}")" |
+ g_cleanup+=("${verify_temp_dir}") |
+ verify_dir="${verify_temp_dir}/patched" |
+ |
+ if ! "${DIRPATCHER}" "${old_dir}" "${patch_dir}" "${verify_dir}"; then |
+ err "patch application for verification failed" |
+ exit 16 |
+ fi |
+ |
+ # rsync will print a line for any file, directory, or symbolic link that |
+ # differs or exists only in one directory. As used here, it correctly |
+ # considers link targets, file contents, permissions, and timestamps. |
+ local rsync_command=(rsync -clprt --delete --out-format=%n \ |
+ "${new_dir}/" "${verify_dir}") |
+ if [[ ${#g_verify_exclude[@]} -gt 0 ]]; then |
+ local exclude |
+ for exclude in "${g_verify_exclude[@]}"; do |
+ # ${g_verify_exclude[@]} contains paths in ${new_dir}. Strip off |
+ # ${new_dir} from the beginning of each, but leave a leading "/" so that |
+ # rsync treats them as being at the root of the "transfer." |
+ rsync_command+=("--exclude" "${exclude:${#new_dir}}") |
+ done |
+ fi |
+ |
+ local rsync_output |
+ if ! rsync_output="$("${rsync_command[@]}")"; then |
+ err "rsync for verification failed" |
+ exit 16 |
+ fi |
+ |
+ rm -rf "${verify_temp_dir}" |
+ unset g_cleanup[${#g_cleanup[@]}] |
+ |
+ if [[ -n "${rsync_output}" ]]; then |
+ err "verification failed" |
+ exit 16 |
+ fi |
+} |
+ |
+# 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 possibly-modified |path| is output. |
+shell_safe_path() { |
+ local path="${1}" |
+ if [[ "${path:0:1}" = "-" ]]; then |
+ echo "./${path}" |
+ else |
+ echo "${path}" |
+ fi |
+} |
+ |
+dirs_contained() { |
+ local dir1="${1}/" |
+ local dir2="${2}/" |
+ |
+ if [[ "${dir1:0:${#dir2}}" = "${dir2}" ]] || |
+ [[ "${dir2:0:${#dir1}}" = "${dir1}" ]]; then |
+ return 0 |
+ fi |
+ |
+ return 1 |
+} |
+ |
+usage() { |
+ echo "usage: ${ME} old_dir new_dir patch_dir" >& 2 |
+} |
+ |
+main() { |
+ local old_dir new_dir patch_dir |
+ old_dir="$(shell_safe_path "${1}")" |
+ new_dir="$(shell_safe_path "${2}")" |
+ patch_dir="$(shell_safe_path "${3}")" |
+ |
+ trap cleanup EXIT HUP INT QUIT TERM |
+ |
+ if ! [[ -d "${old_dir}" ]] || ! [[ -d "${new_dir}" ]]; then |
+ err "old_dir and new_dir must exist and be directories" |
+ usage |
+ exit 3 |
+ fi |
+ |
+ if [[ -e "${patch_dir}" ]]; then |
+ err "patch_dir must not exist" |
+ usage |
+ exit 4 |
+ fi |
+ |
+ local patch_dir_parent |
+ patch_dir_parent="$(dirname "${patch_dir}")" |
+ if ! [[ -d "${patch_dir_parent}" ]]; then |
+ err "patch_dir parent directory must exist and be a directory" |
+ usage |
+ exit 5 |
+ fi |
+ |
+ # The weird conditional structure is because the status of the RE comparison |
+ # needs to be available in ${?} without conflating it with other conditions |
+ # or negating it. Only a status of 2 from the =~ operator indicates an |
+ # invalid regular expression. |
+ |
+ if [[ -n "${DIRDIFFER_EXCLUDE}" ]]; then |
+ if [[ "" =~ ${DIRDIFFER_EXCLUDE} ]]; then |
+ true |
+ elif [[ ${?} -eq 2 ]]; then |
+ err "DIRDIFFER_EXCLUDE contains an invalid regular expression" |
+ exit 19 |
+ fi |
+ fi |
+ |
+ if [[ -n "${DIRDIFFER_NO_DIFF}" ]]; then |
+ if [[ "" =~ ${DIRDIFFER_NO_DIFF} ]]; then |
+ true |
+ elif [[ ${?} -eq 2 ]]; then |
+ err "DIRDIFFER_NO_DIFF contains an invalid regular expression" |
+ exit 19 |
+ fi |
+ fi |
+ |
+ local old_dir_phys new_dir_phys patch_dir_parent_phys patch_dir_phys |
+ old_dir_phys="$(cd "${old_dir}" && pwd -P)" |
+ new_dir_phys="$(cd "${new_dir}" && pwd -P)" |
+ patch_dir_parent_phys="$(cd "${patch_dir_parent}" && pwd -P)" |
+ patch_dir_phys="${patch_dir_parent_phys}/$(basename "${patch_dir}")" |
+ |
+ if dirs_contained "${old_dir_phys}" "${new_dir_phys}" || |
+ dirs_contained "${old_dir_phys}" "${patch_dir_phys}" || |
+ dirs_contained "${new_dir_phys}" "${patch_dir_phys}"; then |
+ err "directories must not contain one another" |
+ usage |
+ exit 6 |
+ fi |
+ |
+ g_cleanup[${#g_cleanup[@]}]="${patch_dir}" |
+ |
+ make_patch_dir "${old_dir}" "${new_dir}" "${patch_dir}" |
+ |
+ verify_patch_dir "${old_dir}" "${new_dir}" "${patch_dir}" |
+ |
+ unset g_cleanup[${#g_cleanup[@]}] |
+ trap - EXIT |
+} |
+ |
+if [[ ${#} -ne 3 ]]; then |
+ usage |
+ exit 2 |
+fi |
+ |
+main "${@}" |
+exit ${?} |
Property changes on: chrome_mac/Google Chrome Packaging/dirdiffer.sh |
___________________________________________________________________ |
Added: svn:executable |
+ * |