1#!/bin/bash
2#
3# Generates an SDK Repository XML based on the input files.
4
5set -e
6
7PROG_DIR=$(dirname $0)
8
9TYPES="tool platform-tool build-tool platform sample doc add-on system-image source"
10OSES="linux macosx windows any linux-x86 darwin"
11
12TMP_DIR=$(mktemp -d -t sdkrepo.tmp.XXXXXXXX)
13trap "rm -rf $TMP_DIR" EXIT
14
15function debug() {
16  echo "DEBUG: " $@ > /dev/stderr
17}
18
19function error() {
20  echo "*** ERROR: " $@
21  usage
22}
23
24function usage() {
25  cat <<EOFU
26Usage: $0 output.xml xml-schema [type [os zip[:dest]]*...]*
27where:
28- schema is one of 'repository' or 'addon'
29- type is one of ${TYPES// /, } (or their plural).
30- os   is one of  ${OSES// /, }.
31There can be more than one zip for the same type
32as long as they use different OSes.
33Zip can be in the form "source:dest" to be renamed on the fly.
34EOFU
35  exit 1
36}
37
38# Validate the tools we need
39if [[ ! -x $(which sha1sum) ]]; then
40  error "Missing tool: sha1sum (Linux: apt-get install coreutils; Mac: port install md5sha1sum)"
41fi
42
43if [[ -z "$XMLLINT" ]]; then
44  XMLLINT=xmllint
45fi
46
47# Parse input params
48OUT="$1"
49[[ -z "$OUT" ]] && error "Missing output.xml name."
50shift
51
52# Get the schema filename. E.g. ".../.../sdk-repository-10.xsd". Can be relative or absolute.
53SCHEMA="$1"
54[[ ! -f "$SCHEMA" ]] && error "Invalid XML schema name: $SCHEMA."
55shift
56
57# Get XML:NS for SDK from the schema
58# This will be something like "http://schemas.android.com/sdk/android/addon/3"
59XMLNS=$(sed -n '/xmlns:sdk="/s/.*"\(.*\)".*/\1/p' "$SCHEMA")
60[[ -z "$XMLNS" ]] && error "Failed to find xmlns:sdk in $SCHEMA."
61#echo "## Using xmlns:sdk=$XMLNS"
62
63# Extract the schema version number from the XMLNS, e.g. it would extract "3"
64XSD_VERSION="${XMLNS##*/}"
65
66# Get the root element from the schema. This is the first element
67# which name starts with "sdk-" (e.g. sdk-repository, sdk-addon)
68ROOT=$(sed -n -e '/xsd:element.*name="sdk-/s/.*name="\(sdk-[^"]*\)".*/\1/p' "$SCHEMA")
69[[ -z "$ROOT" ]] && error "Failed to find root element in $SCHEMA."
70#echo "## Using root element $ROOT"
71
72# Generate XML header
73cat > "$OUT" <<EOFH
74<?xml version="1.0"?>
75<sdk:$ROOT
76    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
77    xmlns:sdk="$XMLNS">
78EOFH
79
80# check_enum value value1 value2 value3...
81# returns valueN if matched or nothing.
82function check_enum() {
83  local VALUE="$1"
84  local i
85  shift
86  for i in "$@"; do
87    if [[ "$i" == "$VALUE" ]]; then
88      echo "$VALUE"
89      break;
90    fi
91  done
92}
93
94# Definition of the attributes we read from source.properties or manifest.ini
95# files and the equivalent XML element being generated.
96
97ATTRS=(
98  # Columns:
99  # --------------------------+------------------------+----------------------
100  # Name read from            | XML element written    | Min-XSD version
101  # source.properties         | to repository.xml      | where XML can be used
102  # --------------------------+------------------------+----------------------
103  # from source.properties for repository.xml packages
104  Pkg.Revision                  revision                 1
105  Pkg.Desc                      description              1
106  Platform.Version              version                  1
107  AndroidVersion.ApiLevel       api-level                1
108  AndroidVersion.CodeName       codename                 1
109  Platform.IncludedAbi          included-abi             5
110  Platform.MinToolsRev          min-tools-rev            1
111  Platform.MinPlatformToolsRev  min-platform-tools-rev   3
112  Sample.MinApiLevel            min-api-level            2
113  Layoutlib.Api                 layoutlib/api            4
114  Layoutlib.Revision            layoutlib/revision       4
115  # from source.properties for addon.xml packages
116  # (note that vendor is mapped to different XML elements based on the XSD version)
117  Extra.VendorDisplay           vendor-display           4
118  Extra.VendorId                vendor-id                4
119  Extra.Vendor                  vendor-id                4
120  Extra.Vendor                  vendor                   1
121  Extra.NameDisplay             name-display             4
122  Extra.Path                    path                     1
123  Extra.OldPaths                old-paths                3
124  Extra.MinApiLevel             min-api-level            2
125  # for system-image
126  SystemImage.Abi               abi                      r:3,s:1
127  SystemImage.TagId             tag-id                   r:9,s:2
128  SystemImage.TagDisplay        tag-display              r:9,s:2
129  Addon.VendorId                add-on/vendor-id         s:3
130  Addon.VendorDisplay           add-on/vendor-display    s:3
131  # from addon manifest.ini for addon.xml packages
132  # (note that vendor/name are mapped to different XML elements based on the XSD version)
133  vendor-id                     vendor-id                4
134  vendor-display                vendor-display           4
135  vendor                        vendor-display           4
136  vendor                        vendor                   1
137  name-id                       name-id                  4
138  name-display                  name-display             4
139  name                          name-display             4
140  name                          name                     1
141  description                   description              1
142  api                           api-level                1
143  version                       revision                 1
144  revision                      revision                 1
145)
146
147# Start with repo-10, addon-7 and sys-img-3, we don't encode the os/arch
148# in the <archive> attributes anymore. Instead we have separate elements.
149
150function uses_new_host_os() {
151  if [[ "$ROOT" == "sdk-repository" && "$XSD_VERSION" -ge "10" ]]; then return 0; fi
152  if [[ "$ROOT" == "sdk-addon"      && "$XSD_VERSION" -ge  "7" ]]; then return 0; fi
153  if [[ "$ROOT" == "sdk-sys-img"    && "$XSD_VERSION" -ge  "3" ]]; then return 0; fi
154  return 1
155}
156
157ATTRS_ARCHIVE=(
158  Archive.HostOs                host-os                   1
159  Archive.HostBits              host-bits                 1
160  Archive.JvmBits               jvm-bits                  1
161  Archive.MinJvmVers            min-jvm-version           1
162)
163
164
165# Starting with XSD repo-7 and addon-5, some revision elements are no longer just
166# integers. Instead they are in major.minor.micro.preview format. This defines
167# which elements. This depends on the XSD root element and the XSD version.
168#
169# Note: addon extra revision can't take a preview number. We don't enforce
170# this in this script. Instead schema validation will fail if the extra
171# source.property declares an RC and it gets inserted in the addon.xml here.
172
173if [[ "$ROOT" == "sdk-repository" && "$XSD_VERSION" -ge 7 ]] ||
174   [[ "$ROOT" == "sdk-addon"      && "$XSD_VERSION" -ge 5 ]]; then
175FULL_REVISIONS=(
176  tool          revision
177  build-tool    revision
178  platform-tool revision
179  extra         revision
180  @             min-tools-rev
181  @             min-platform-tools-rev
182)
183else
184FULL_REVISIONS=()
185fi
186
187
188# Parse all archives.
189
190function needs_full_revision() {
191  local PARENT="$1"
192  local ELEMENT="$2"
193  shift
194  shift
195  local P E
196
197  while [[ "$1" ]]; do
198    P=$1
199    E=$2
200    if [[ "$E" == "$ELEMENT" ]] && [[ "$P" == "@" || "$P" == "$PARENT" ]]; then
201      return 0 # true
202    fi
203    shift
204    shift
205  done
206
207  return 1 # false
208}
209
210# Parses and print a full revision in the form "1.2.3 rc4".
211# Note that the format requires to have 1 space before the
212# optional "rc" (e.g. '1 rc4', not '1rc4') and no space after
213# the rc (so not '1 rc 4' either)
214function write_full_revision() {
215  local VALUE="$1"
216  local EXTRA_SPACE="$2"
217  local KEYS="major minor micro preview"
218  local V K
219
220  while [[ -n "$VALUE" && -n "$KEYS" ]]; do
221    # Take 1st segment delimited by . or space
222    V="${VALUE%%[. ]*}"
223
224    # Print it
225    if [[ "${V:0:2}" == "rc" ]]; then
226      V="${V:2}"
227      K="preview"
228      KEYS=""
229    else
230      K="${KEYS%% *}"
231    fi
232
233    if [[ -n "$V" && -n "$K" ]]; then
234        echo "$EXTRA_SPACE            <sdk:$K>$V</sdk:$K>"
235    fi
236
237    # Take the rest.
238    K="${KEYS#* }"
239    if [[ "$K" == "$KEYS" ]]; then KEYS=""; else KEYS="$K"; fi
240    V="${VALUE#*[. ]}"
241    if [[ "$V" == "$VALUE" ]]; then VALUE=""; else VALUE="$V"; fi
242  done
243}
244
245
246function parse_attributes() {
247  local PROPS="$1"
248  shift
249  local RESULT=""
250  local VALUE
251  local REV
252  local USED
253  local S
254
255  # Get the first letter of the schema name (e.g. sdk-repo => 'r')
256  # This can be r, a or s and would match the min-XSD per-schema value
257  # in the ATTRS list.
258  S=$(basename "$SCHEMA")
259  S="${S:4:1}"
260
261  # $1 here is the ATTRS list above.
262  while [[ "$1" ]]; do
263    # Check the version in which the attribute was introduced and
264    # ignore things which are too *new* for this schema. This lets
265    # us generate old schemas for backward compatibility purposes.
266    SRC=$1
267    DST=$2
268    REV=$3
269
270    if [[ $REV =~ ([ras0-9:,]+,)?$S:([0-9])(,.*)? ]]; then
271      # Per-schema type min-XSD revision. Format is "[<type>:rev],*]
272      # where type is one of r, a or s matching $S above.
273      REV="${BASH_REMATCH[2]}"
274    fi
275
276    if [[ ( $REV =~ ^[0-9]+ && $XSD_VERSION -ge $REV ) || $XSD_VERSION == $REV ]]; then
277      # Parse the property, if present. Any space is replaced by @
278      VALUE=$( grep "^$SRC=" "$PROPS" | cut -d = -f 2 | tr ' ' '@' | tr -d '\r' )
279      if [[ -n "$VALUE" ]]; then
280        # In case an XML element would be mapped multiple times,
281        # only use its first definition.
282        if [[ "${USED/$DST/}" == "$USED" ]]; then
283          USED="$USED $DST"
284          RESULT="$RESULT $DST $VALUE"
285        fi
286      fi
287    fi
288    shift
289    shift
290    shift
291  done
292
293  echo "$RESULT"
294}
295
296function output_attributes() {
297  local ELEMENT="$1"
298  local OUT="$2"
299  shift
300  shift
301  local KEY VALUE
302  local NODE LAST_NODE EXTRA_SPACE
303
304  while [[ "$1" ]]; do
305    KEY="$1"
306    VALUE="${2//@/ }"
307    NODE="${KEY%%/*}"
308    KEY="${KEY##*/}"
309    if [[ "$NODE" == "$KEY" ]]; then
310      NODE=""
311      EXTRA_SPACE=""
312    fi
313    if [[ "$NODE" != "$LAST_NODE" ]]; then
314      EXTRA_SPACE="    "
315      [[ "$LAST_NODE" ]] && echo "          </sdk:$LAST_NODE>" >> "$OUT"
316      LAST_NODE="$NODE"
317      [[ "$NODE"      ]] && echo "          <sdk:$NODE>" >> "$OUT"
318    fi
319    if needs_full_revision "$ELEMENT" "$KEY" ${FULL_REVISIONS[@]}; then
320      echo "$EXTRA_SPACE        <sdk:$KEY>"       >> "$OUT"
321      write_full_revision "$VALUE" "$EXTRA_SPACE" >> "$OUT"
322      echo "$EXTRA_SPACE        </sdk:$KEY>"      >> "$OUT"
323    else
324      echo "$EXTRA_SPACE        <sdk:$KEY>$VALUE</sdk:$KEY>" >> "$OUT"
325    fi
326    shift
327    shift
328  done
329  if [[ "$LAST_NODE" ]]; then echo "          </sdk:$LAST_NODE>" >> "$OUT"; fi
330}
331
332while [[ -n "$1" ]]; do
333  # Process archives.
334  # First we expect a type. For convenience the type can be plural.
335  TYPE=$(check_enum "${1%%s}" $TYPES)
336  [[ -z $TYPE ]] && error "Unknown archive type '$1'."
337  shift
338
339  ELEMENT="$TYPE"
340
341  MAP=""
342  FIRST="1"
343  LIBS_XML=""
344
345  OS=$(check_enum "$1" $OSES)
346  while [[ $OS ]]; do
347    shift
348    [[ $OS == "linux-x86" ]] && OS=linux
349    [[ $OS == "darwin" ]] && OS=macosx
350
351    SRC="$1"
352    DST="$1"
353    if [[ "${SRC/:/}" != "$SRC" ]]; then
354      DST="${SRC/*:/}"
355      SRC="${SRC/:*/}"
356    fi
357    [[ ! -f "$SRC" ]] && error "Missing file for archive $TYPE/$OS: $SRC"
358    shift
359
360    # Depending on the archive type, we need a number of attributes
361    # from the source.properties or the manifest.ini. We'll take
362    # these attributes from the first zip found.
363    #
364    # What we need vs. which package uses it:
365    # - description             all
366    # - revision                all
367    # - version                 platform
368    # - included-abi            platform
369    # - api-level               platform sample doc add-on system-image
370    # - codename                platform sample doc add-on system-image
371    # - min-tools-rev           platform sample
372    # - min-platform-tools-rev  tool
373    # - min-api-level           extra
374    # - vendor                  extra               add-on
375    # - path                    extra
376    # - old-paths               extra
377    # - abi                     system-image
378    #
379    # We don't actually validate here.
380    # Just take whatever is defined and put it in the XML.
381    # XML validation against the schema will be done at the end.
382
383    if [[ $FIRST ]]; then
384      FIRST=""
385
386      if unzip -l "$SRC" | grep -qs "source.properties" ; then
387        # Extract Source Properties
388        # unzip: -j=flat (no dirs), -q=quiet, -o=overwrite, -d=dest dir
389        unzip -j -q -o -d "$TMP_DIR" "$SRC" "*/source.properties"
390        PROPS="$TMP_DIR/source.properties"
391
392      elif unzip -l "$SRC" | grep -qs "manifest.ini" ; then
393        unzip -j -q -o -d "$TMP_DIR" "$SRC" "*/manifest.ini"
394        PROPS="$TMP_DIR/manifest.ini"
395
396        # Parse the libs for an addon and generate the <libs> node
397        # libraries is a semi-colon separated list
398        LIBS=$(parse_attributes "$PROPS" "libraries")
399        LIBS_XML="        <sdk:libs>"
400        for LIB in ${LIBS//;/ }; do
401          LIBS_XML="$LIBS_XML
402           <sdk:lib><sdk:name>$LIB</sdk:name></sdk:lib>"
403        done
404        LIBS_XML="$LIBS_XML
405        </sdk:libs>"
406
407      else
408        error "Failed to find source.properties or manifest.ini in $SRC"
409      fi
410
411      [[ ! -f $PROPS ]] && error "Failed to extract $PROPS from $SRC"
412      MAP=$(parse_attributes "$PROPS" ${ATTRS[@]})
413
414      # Time to generate the XML for the package
415      echo "    <sdk:${ELEMENT}>" >> "$OUT"
416      output_attributes "$ELEMENT" "$OUT" $MAP
417      [[ -n "$LIBS_XML" ]] && echo "$LIBS_XML" >> "$OUT"
418      echo "        <sdk:archives>" >> "$OUT"
419    fi
420
421    # Generate archive info
422    #echo "## Add $TYPE/$OS archive $SRC"
423    SIZE=$( stat -c %s "$SRC" )
424    SHA1=$( sha1sum "$SRC" | cut -d " "  -f 1 )
425
426    if uses_new_host_os ; then
427      USE_HOST_OS=1
428    else
429      OLD_OS_ATTR=" os='$OS'"
430    fi
431
432    cat >> "$OUT" <<EOFA
433            <sdk:archive$OLD_OS_ATTR>
434                <sdk:size>$SIZE</sdk:size>
435                <sdk:checksum type='sha1'>$SHA1</sdk:checksum>
436                <sdk:url>$DST</sdk:url>
437EOFA
438    if [[ $USE_HOST_OS ]]; then
439      # parse the Archive.Host/Jvm info from the source.props if present
440      MAP=$(parse_attributes "$PROPS" ${ATTRS_ARCHIVE[@]})
441      # Always generate host-os if not present
442      if [[ "${MAP/ host-os /}" == "$MAP" ]]; then
443        MAP="$MAP host-os $OS"
444      fi
445      output_attributes "archive" "$OUT" $MAP
446    fi
447    echo "            </sdk:archive>" >> "$OUT"
448
449    # Skip to next arch/zip entry.
450    # If not a valid OS, close the archives/package nodes.
451    OS=$(check_enum "$1" $OSES)
452
453    if [[ ! "$OS" ]]; then
454      echo "        </sdk:archives>" >> "$OUT"
455      echo "    </sdk:${ELEMENT}>" >> "$OUT"
456    fi
457  done
458
459done
460
461# Generate XML footer
462echo "</sdk:$ROOT>" >> "$OUT"
463
464#echo "## Validate XML against schema"
465$XMLLINT --noout --schema $SCHEMA "$OUT"
466