1#!/bin/bash
2
3# Builds protoc executable into target/protoc.exe; optionally build protoc
4# plugins into target/protoc-gen-*.exe
5# To be run from Maven.
6# Usage: build-protoc.sh <OS> <ARCH> <TARGET>
7# <OS> and <ARCH> are ${os.detected.name} and ${os.detected.arch} from os-maven-plugin
8# <TARGET> can be "protoc" or "protoc-gen-javalite"
9OS=$1
10ARCH=$2
11MAKE_TARGET=$3
12
13if [[ $# < 3 ]]; then
14  echo "No arguments provided. This script is intended to be run from Maven."
15  exit 1
16fi
17
18case $MAKE_TARGET in
19  protoc-gen-javalite)
20    ;;
21  protoc)
22    ;;
23  *)
24    echo "Target ""$TARGET"" invalid."
25    exit 1
26esac
27
28# Under Cygwin, bash doesn't have these in PATH when called from Maven which
29# runs in Windows version of Java.
30export PATH="/bin:/usr/bin:$PATH"
31
32############################################################################
33# Helper functions
34############################################################################
35E_PARAM_ERR=98
36E_ASSERT_FAILED=99
37
38# Usage:
39fail()
40{
41  echo "ERROR: $1"
42  echo
43  exit $E_ASSERT_FAILED
44}
45
46# Usage: assertEq VAL1 VAL2 $LINENO
47assertEq ()
48{
49  lineno=$3
50  if [ -z "$lineno" ]; then
51    echo "lineno not given"
52    exit $E_PARAM_ERR
53  fi
54
55  if [[ "$1" != "$2" ]]; then
56    echo "Assertion failed:  \"$1\" == \"$2\""
57    echo "File \"$0\", line $lineno"    # Give name of file and line number.
58    exit $E_ASSERT_FAILED
59  fi
60}
61
62# Checks the artifact is for the expected architecture
63# Usage: checkArch <path-to-protoc>
64checkArch ()
65{
66  echo
67  echo "Checking file format ..."
68  if [[ "$OS" == windows || "$OS" == linux ]]; then
69    format="$(objdump -f "$1" | grep -o "file format .*$" | grep -o "[^ ]*$")"
70    echo Format=$format
71    if [[ "$OS" == linux ]]; then
72      if [[ "$ARCH" == x86_32 ]]; then
73        assertEq $format "elf32-i386" $LINENO
74      elif [[ "$ARCH" == x86_64 ]]; then
75        assertEq $format "elf64-x86-64" $LINENO
76      else
77        fail "Unsupported arch: $ARCH"
78      fi
79    else
80      # $OS == windows
81      if [[ "$ARCH" == x86_32 ]]; then
82        assertEq $format "pei-i386" $LINENO
83      elif [[ "$ARCH" == x86_64 ]]; then
84        assertEq $format "pei-x86-64" $LINENO
85      else
86        fail "Unsupported arch: $ARCH"
87      fi
88    fi
89  elif [[ "$OS" == osx ]]; then
90    format="$(file -b "$1" | grep -o "[^ ]*$")"
91    echo Format=$format
92    if [[ "$ARCH" == x86_32 ]]; then
93      assertEq $format "i386" $LINENO
94    elif [[ "$ARCH" == x86_64 ]]; then
95      assertEq $format "x86_64" $LINENO
96    else
97      fail "Unsupported arch: $ARCH"
98    fi
99  else
100    fail "Unsupported system: $OS"
101  fi
102  echo
103}
104
105# Checks the dependencies of the artifact. Artifacts should only depend on
106# system libraries.
107# Usage: checkDependencies <path-to-protoc>
108checkDependencies ()
109{
110  if [[ "$OS" == windows ]]; then
111    dump_cmd='objdump -x '"$1"' | fgrep "DLL Name"'
112    white_list="KERNEL32\.dll\|msvcrt\.dll"
113  elif [[ "$OS" == linux ]]; then
114    dump_cmd='ldd '"$1"
115    if [[ "$ARCH" == x86_32 ]]; then
116      white_list="linux-gate\.so\.1\|libpthread\.so\.0\|libm\.so\.6\|libc\.so\.6\|ld-linux\.so\.2"
117    elif [[ "$ARCH" == x86_64 ]]; then
118      white_list="linux-vdso\.so\.1\|libpthread\.so\.0\|libm\.so\.6\|libc\.so\.6\|ld-linux-x86-64\.so\.2"
119    fi
120  elif [[ "$OS" == osx ]]; then
121    dump_cmd='otool -L '"$1"' | fgrep dylib'
122    white_list="libz\.1\.dylib\|libstdc++\.6\.dylib\|libSystem\.B\.dylib"
123  fi
124  if [[ -z "$white_list" || -z "$dump_cmd" ]]; then
125    fail "Unsupported platform $OS-$ARCH."
126  fi
127  echo "Checking for expected dependencies ..."
128  eval $dump_cmd | grep -i "$white_list" || fail "doesn't show any expected dependencies"
129  echo "Checking for unexpected dependencies ..."
130  eval $dump_cmd | grep -i -v "$white_list"
131  ret=$?
132  if [[ $ret == 0 ]]; then
133    fail "found unexpected dependencies (listed above)."
134  elif [[ $ret != 1 ]]; then
135    fail "Error when checking dependencies."
136  fi  # grep returns 1 when "not found", which is what we expect
137  echo "Dependencies look good."
138  echo
139}
140############################################################################
141
142echo "Building protoc, OS=$OS ARCH=$ARCH TARGET=$TARGET"
143
144# Nested double quotes are unintuitive, but it works.
145cd "$(dirname "$0")"
146
147WORKING_DIR=$(pwd)
148CONFIGURE_ARGS="--disable-shared"
149
150if [[ "$OS" == windows ]]; then
151  MAKE_TARGET="${MAKE_TARGET}.exe"
152fi
153
154# Override the default value set in configure.ac that has '-g' which produces
155# huge binary.
156CXXFLAGS="-DNDEBUG"
157LDFLAGS=""
158
159if [[ "$(uname)" == CYGWIN* ]]; then
160  assertEq "$OS" windows $LINENO
161  # Use mingw32 compilers because executables produced by Cygwin compiler
162  # always have dependency on Cygwin DLL.
163  if [[ "$ARCH" == x86_64 ]]; then
164    CONFIGURE_ARGS="$CONFIGURE_ARGS --host=x86_64-w64-mingw32"
165  elif [[ "$ARCH" == x86_32 ]]; then
166    CONFIGURE_ARGS="$CONFIGURE_ARGS --host=i686-pc-mingw32"
167  else
168    fail "Unsupported arch by CYGWIN: $ARCH"
169  fi
170elif [[ "$(uname)" == MINGW32* ]]; then
171  assertEq "$OS" windows $LINENO
172  assertEq "$ARCH" x86_32 $LINENO
173elif [[ "$(uname)" == MINGW64* ]]; then
174  assertEq "$OS" windows $LINENO
175  assertEq "$ARCH" x86_64 $LINENO
176elif [[ "$(uname)" == Linux* ]]; then
177  if [[ "$OS" == linux ]]; then
178    if [[ "$ARCH" == x86_64 ]]; then
179      CXXFLAGS="$CXXFLAGS -m64"
180    elif [[ "$ARCH" == x86_32 ]]; then
181      CXXFLAGS="$CXXFLAGS -m32"
182    else
183      fail "Unsupported arch: $ARCH"
184    fi
185  elif [[ "$OS" == windows ]]; then
186    # Cross-compilation for Windows
187    # TODO(zhangkun83) MinGW 64 always adds dependency on libwinpthread-1.dll,
188    # which is undesirable for repository deployment.
189    CONFIGURE_ARGS="$CONFIGURE_ARGS"
190    if [[ "$ARCH" == x86_64 ]]; then
191      CONFIGURE_ARGS="$CONFIGURE_ARGS --host=x86_64-w64-mingw32"
192    elif [[ "$ARCH" == x86_32 ]]; then
193      CONFIGURE_ARGS="$CONFIGURE_ARGS --host=i686-w64-mingw32"
194    else
195      fail "Unsupported arch: $ARCH"
196    fi
197  else
198    fail "Cannot build $OS on $(uname)"
199  fi
200elif [[ "$(uname)" == Darwin* ]]; then
201  assertEq "$OS" osx $LINENO
202  # Make the binary compatible with OSX 10.7 and later
203  CXXFLAGS="$CXXFLAGS -mmacosx-version-min=10.7"
204  if [[ "$ARCH" == x86_64 ]]; then
205    CXXFLAGS="$CXXFLAGS -m64"
206  elif [[ "$ARCH" == x86_32 ]]; then
207    CXXFLAGS="$CXXFLAGS -m32"
208  else
209    fail "Unsupported arch: $ARCH"
210  fi
211else
212  fail "Unsupported system: $(uname)"
213fi
214
215# Statically link libgcc and libstdc++.
216# -s to produce stripped binary.
217# And they don't work under Mac.
218if [[ "$OS" != osx ]]; then
219  LDFLAGS="$LDFLAGS -static-libgcc -static-libstdc++ -s"
220fi
221
222export CXXFLAGS LDFLAGS
223
224TARGET_FILE=target/$MAKE_TARGET.exe
225
226cd "$WORKING_DIR"/.. && ./configure $CONFIGURE_ARGS &&
227  cd src && make clean && make $MAKE_TARGET -j4 &&
228  cd "$WORKING_DIR" && mkdir -p target &&
229  (cp ../src/$MAKE_TARGET $TARGET_FILE ||
230   cp ../src/$MAKE_TARGET.exe $TARGET_FILE) ||
231  exit 1
232
233if [[ "$OS" == osx ]]; then
234  # Since Mac linker doesn't accept "-s", we need to run strip
235  strip $TARGET_FILE || exit 1
236fi
237
238checkArch $TARGET_FILE && checkDependencies $TARGET_FILE
239