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
#!/bin/zsh
emulate -L zsh;setopt extendedglob

# "=command" means the full path to the command (for security)

# used when app is initially dropped.
# check for architectures present then exit
if [[ -f = $1 ]]; then
    /usr/bin/file $2/**/*(*N) | /usr/bin/grep \(for\ architecture | /usr/bin/sed -E "s/.*architecture ([a-z0-9_A-Z]+)\).*/\1/g" | /usr/bin/sort | /usr/bin/uniq
    exit
fi

# now we have the actual trimming...

# Check the options - what do we have to do?
while getopts :dstrbl:a: o;do case $o in
d)junk=1;;t)tif=1;;r)res='';;b)bkp=1;;s)sym=1;;l)lang=$OPTARG;;a)arch=$OPTARG;;\?)echo error;exit;;
esac
done

# Remove options from the arg list.
((OPTIND>1)) && shift $((OPTIND-1)) || {echo FAILED - no app to trim.;exit}

# $* means all the options
for dir in $*; do
# the app itself should be a directory (and writable?)
if ! { [[ -d $dir ]]&&[[ -w $dir ]] }; then echo "FAILED - check permissions.";exit; fi;

echo "Trimming $dir.\n"

# path to the "working copy" of the app in /tmp
tmpapp=/tmp/${dir:t}
# create the working copy, excluding junk files if specified
/usr/bin/rsync -Ca${res-E} --exclude-from=- --delete-excluded "$dir/" $tmpapp <<<${junk+'classes.nib
info.nib
data.dependency
.DS_Store
Headers/
PrivateHeaders/
*.h
pbdevelopment.plist'}

echo -n ${junk+"== Removed useless junk files.\n"}${res+"== Cleared extended attributes.\n"}

# into our "working copy"
cd $tmpapp || {echo "FAILED - check permissions.";/bin/rm -rf $tmpapp;exit}

# this removes broken symlinks (if "remove junk is set")
[[ -n $junk ]] && /bin/rm -fv **/*(-@N) 2>/dev/null

# Remove foreign languages.
[[ -n $lang ]]&&{
echo "== Cleared foreign languages."
for line in **/Resources(/N);do
integer count="`print -l $line/*.lproj(/N) | wc -l`"
((count>1))&&{
integer left="`eval print -l "${(q)line}/*.lproj~${(q)line}/($lang)*(/N)" | wc -l`"
integer right="`eval print -l "${(q)line}/*.lproj(N)" | wc -l`"
((left<right))&&eval /bin/rm -vrf "${(q)line}/*.lproj~${(q)line}/($lang)*(/N)" | grep -v lproj/
}
done
}

# lipo universal binaries
[[ -n $arch ]] && {
echo "== Trimmed universal binaries."
# get list of fat files
lines=`/usr/bin/file **/*(*N) |/usr/bin/grep -e 'fat file' -e 'universal binary'|/usr/bin/sed -E 's/: *(set.id )?Mach.*//g'`
# for each fat file...
for line in ${(s.
.)lines}; do
# make sure we're "+w". this may be unnecessary.
[[ -w $line:h ]]||/bin/chmod ug+w $line:h;[[ -w $line ]]||/bin/chmod ug+w $line
# output to 'log'
echo $line
# do lipo to /tmp. "2>&1" means that error messages are printed the standard out (our logview)
if /usr/bin/lipo $line -thin $arch -output "/tmp/${line:t}.lipo" 2>&1; then
# Copy the lipo to the app
    /bin/cp "/tmp/${line:t}.lipo" $line 2>&1
# Get rid of the temp lipo file
    /bin/chmod +w "/tmp/${line:t}.lipo" && /bin/rm -f "/tmp/${line:t}.lipo"
fi
done
}

[[ -n $sym ]]&&{
# strip debug symbols. "2>&1" means error messages to the standard out.
/usr/bin/strip -Sur Contents/MacOS/*(*) 2>&1
/usr/bin/strip -Sx **/*.dylib(N) **/*.framework/**/*(*N) 2>/dev/null
echo "== Stripped debug symbols."
}

[[ -n $tif ]]&&{
echo "== Compressed TIFF images."
zmodload zsh/stat # zsh stat is much, much faster
# the (.NLk-500Uw,GI,W):
# "Lk-500" means under 500kb, "Uw,GI,W" checks if we can write. latter may not be necessary now as we use "cp".

for line in **/*.(#i)tif(f|)(.NLk-500Uw,GI,W); do
if /usr/bin/tiffutil -lzw $line -out "/tmp/${line:t}" 2>/dev/null; then
(( `stat -L +size /tmp/${line:t}` < `stat -L +size $line` )) && /bin/cp "/tmp/${line:t}" $line && echo $line
/bin/chmod +w "/tmp/${line:t}" && /bin/rm -f "/tmp/${line:t}"
fi
done
}
# now to copy our changes back to the real app
cd $dir/..
# if we're making a backup, get a name that doesn't exist by adding 'backup' to the end of the name of the app.
appcopy=${dir:t:r}
[[ -n $bkp ]]&&{
while [[ -d $appcopy.${dir:e} ]];do appcopy=$appcopy\ backup;done
# the original app becomes the backup
/bin/mv $dir:t "$appcopy.${dir:e}"
# out trimmed app takes its place
/bin/mv $tmpapp $dir:t
}||{
# we're not making a backup
# remove the real app and replace with trimmed app
# otherwise if we can't, make our app "appname trimmed.app"
/bin/rm -rf ${dir:t} && /bin/mv $tmpapp ${dir:t} || {while [[ -d $appcopy.${dir:e} ]];do appcopy=$appcopy\ trimmed;done;/bin/mv $tmpapp "$appcopy.${dir:e}";echo "==\nALERT: Could not delete $dir:t - please delete this. Trimmed app saved to '$appcopy.${dir:e}'"}
}
# and we're done.
echo "\n== Trim completed =="
done