diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/apicheck/Android.mk | 2 | ||||
-rwxr-xr-x | tools/buildinfo.sh | 23 | ||||
-rw-r--r-- | tools/device/AndroidBoard.mk.template | 8 | ||||
-rw-r--r-- | tools/device/AndroidProducts.mk.template | 2 | ||||
-rw-r--r-- | tools/device/BoardConfig.mk.template | 31 | ||||
-rw-r--r-- | tools/device/cm.mk.template | 15 | ||||
-rw-r--r-- | tools/device/device.mk.template | 24 | ||||
-rw-r--r-- | tools/device/makerecoveries.sh | 69 | ||||
-rwxr-xr-x | tools/device/mkrecoveryzip.sh | 97 | ||||
-rwxr-xr-x | tools/device/mkvendor.sh | 115 | ||||
-rw-r--r-- | tools/device/recovery.fstab.template | 10 | ||||
-rw-r--r-- | tools/device/system.prop.template | 3 | ||||
-rwxr-xr-x | tools/getb64key.py | 17 | ||||
-rwxr-xr-x | tools/post_process_props.py | 25 | ||||
-rwxr-xr-x | tools/releasetools/add_img_to_target_files.py | 38 | ||||
-rwxr-xr-x | tools/releasetools/build_image.py | 15 | ||||
-rw-r--r-- | tools/releasetools/common.py | 123 | ||||
-rw-r--r-- | tools/releasetools/edify_generator.py | 58 | ||||
-rwxr-xr-x | tools/releasetools/img_from_target_files.py | 26 | ||||
-rwxr-xr-x | tools/releasetools/ota_from_target_files.py | 170 | ||||
-rwxr-xr-x | tools/repopick.py | 355 | ||||
-rwxr-xr-x | tools/roomservice.py | 295 |
22 files changed, 1403 insertions, 118 deletions
diff --git a/tools/apicheck/Android.mk b/tools/apicheck/Android.mk index 1674a17..1505c8d 100644 --- a/tools/apicheck/Android.mk +++ b/tools/apicheck/Android.mk @@ -32,7 +32,7 @@ include $(BUILD_SYSTEM)/base_rules.mk $(LOCAL_BUILT_MODULE): $(HOST_OUT_JAVA_LIBRARIES)/doclava$(COMMON_JAVA_PACKAGE_SUFFIX) $(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/etc/apicheck | $(ACP) - @echo "Copy: $(PRIVATE_MODULE) ($@)" + @echo -e ${CL_CYN}"Copy:"${CL_RST}" $(PRIVATE_MODULE) ($@)" $(copy-file-to-new-target) $(hide) chmod 755 $@ diff --git a/tools/buildinfo.sh b/tools/buildinfo.sh index 5c199b8..559cd88 100755 --- a/tools/buildinfo.sh +++ b/tools/buildinfo.sh @@ -20,10 +20,8 @@ echo "ro.build.user=$USER" echo "ro.build.host=`hostname`" echo "ro.build.tags=$BUILD_VERSION_TAGS" echo "ro.build.flavor=$TARGET_BUILD_FLAVOR" -echo "ro.product.model=$PRODUCT_MODEL" echo "ro.product.brand=$PRODUCT_BRAND" echo "ro.product.name=$PRODUCT_NAME" -echo "ro.product.device=$TARGET_DEVICE" echo "ro.product.board=$TARGET_BOOTLOADER_BOARD_NAME" # These values are deprecated, use "ro.product.cpu.abilist" @@ -45,15 +43,20 @@ fi echo "ro.wifi.channels=$PRODUCT_DEFAULT_WIFI_CHANNELS" echo "ro.board.platform=$TARGET_BOARD_PLATFORM" -echo "# ro.build.product is obsolete; use ro.product.device" -echo "ro.build.product=$TARGET_DEVICE" - -echo "# Do not try to parse description, fingerprint, or thumbprint" -echo "ro.build.description=$PRIVATE_BUILD_DESC" -echo "ro.build.fingerprint=$BUILD_FINGERPRINT" -if [ -n "$BUILD_THUMBPRINT" ] ; then - echo "ro.build.thumbprint=$BUILD_THUMBPRINT" +if [ "$TARGET_UNIFIED_DEVICE" == "" ] ; then + echo "# ro.build.product is obsolete; use ro.product.device" + echo "ro.build.product=$TARGET_DEVICE" + echo "ro.product.model=$PRODUCT_MODEL" + echo "ro.product.device=$TARGET_DEVICE" + echo "# Do not try to parse description, fingerprint, or thumbprint" + echo "ro.build.description=$PRIVATE_BUILD_DESC" + echo "ro.build.fingerprint=$BUILD_FINGERPRINT" + if [ -n "$BUILD_THUMBPRINT" ] ; then + echo "ro.build.thumbprint=$BUILD_THUMBPRINT" + fi fi echo "ro.build.characteristics=$TARGET_AAPT_CHARACTERISTICS" +echo "ro.cm.device=$CM_DEVICE" + echo "# end build properties" diff --git a/tools/device/AndroidBoard.mk.template b/tools/device/AndroidBoard.mk.template new file mode 100644 index 0000000..55a36d5 --- /dev/null +++ b/tools/device/AndroidBoard.mk.template @@ -0,0 +1,8 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +ALL_PREBUILT += $(INSTALLED_KERNEL_TARGET) + +# include the non-open-source counterpart to this file +-include vendor/__MANUFACTURER__/__DEVICE__/AndroidBoardVendor.mk diff --git a/tools/device/AndroidProducts.mk.template b/tools/device/AndroidProducts.mk.template new file mode 100644 index 0000000..f31c5bf --- /dev/null +++ b/tools/device/AndroidProducts.mk.template @@ -0,0 +1,2 @@ +PRODUCT_MAKEFILES := \ + $(LOCAL_DIR)/device___DEVICE__.mk diff --git a/tools/device/BoardConfig.mk.template b/tools/device/BoardConfig.mk.template new file mode 100644 index 0000000..617673f --- /dev/null +++ b/tools/device/BoardConfig.mk.template @@ -0,0 +1,31 @@ +USE_CAMERA_STUB := true + +# inherit from the proprietary version +-include vendor/__MANUFACTURER__/__DEVICE__/BoardConfigVendor.mk + +TARGET_ARCH := arm +TARGET_NO_BOOTLOADER := true +TARGET_BOARD_PLATFORM := unknown +TARGET_CPU_ABI := armeabi-v7a +TARGET_CPU_ABI2 := armeabi +TARGET_ARCH_VARIANT := armv7-a-neon +TARGET_CPU_VARIANT := cortex-a7 +TARGET_CPU_SMP := true +ARCH_ARM_HAVE_TLS_REGISTER := true + +TARGET_BOOTLOADER_BOARD_NAME := __DEVICE__ + +BOARD_KERNEL_CMDLINE := __CMDLINE__ +BOARD_KERNEL_BASE := 0x__BASE__ +BOARD_KERNEL_PAGESIZE := __PAGE_SIZE__ + +# fix this up by examining /proc/mtd on a running device +BOARD_BOOTIMAGE_PARTITION_SIZE := 0x00380000 +BOARD_RECOVERYIMAGE_PARTITION_SIZE := 0x00480000 +BOARD_SYSTEMIMAGE_PARTITION_SIZE := 0x08c60000 +BOARD_USERDATAIMAGE_PARTITION_SIZE := 0x105c0000 +BOARD_FLASH_BLOCK_SIZE := 131072 + +TARGET_PREBUILT_KERNEL := device/__MANUFACTURER__/__DEVICE__/kernel + +BOARD_HAS_NO_SELECT_BUTTON := true diff --git a/tools/device/cm.mk.template b/tools/device/cm.mk.template new file mode 100644 index 0000000..e07898a --- /dev/null +++ b/tools/device/cm.mk.template @@ -0,0 +1,15 @@ +# Release name +PRODUCT_RELEASE_NAME := __DEVICE__ + +# Inherit some common CM stuff. +$(call inherit-product, vendor/cm/config/common_full_phone.mk) + +# Inherit device configuration +$(call inherit-product, device/__MANUFACTURER__/__DEVICE__/device___DEVICE__.mk) + +## Device identifier. This must come after all inclusions +PRODUCT_DEVICE := __DEVICE__ +PRODUCT_NAME := cm___DEVICE__ +PRODUCT_BRAND := __MANUFACTURER__ +PRODUCT_MODEL := __DEVICE__ +PRODUCT_MANUFACTURER := __MANUFACTURER__ diff --git a/tools/device/device.mk.template b/tools/device/device.mk.template new file mode 100644 index 0000000..91ffdc9 --- /dev/null +++ b/tools/device/device.mk.template @@ -0,0 +1,24 @@ +$(call inherit-product, $(SRC_TARGET_DIR)/product/languages_full.mk) + +# The gps config appropriate for this device +$(call inherit-product, device/common/gps/gps_us_supl.mk) + +$(call inherit-product-if-exists, vendor/__MANUFACTURER__/__DEVICE__/__DEVICE__-vendor.mk) + +DEVICE_PACKAGE_OVERLAYS += device/__MANUFACTURER__/__DEVICE__/overlay + + +ifeq ($(TARGET_PREBUILT_KERNEL),) + LOCAL_KERNEL := device/__MANUFACTURER__/__DEVICE__/kernel +else + LOCAL_KERNEL := $(TARGET_PREBUILT_KERNEL) +endif + +PRODUCT_COPY_FILES += \ + $(LOCAL_KERNEL):kernel + +$(call inherit-product, build/target/product/full.mk) + +PRODUCT_BUILD_PROP_OVERRIDES += BUILD_UTC_DATE=0 +PRODUCT_NAME := full___DEVICE__ +PRODUCT_DEVICE := __DEVICE__ diff --git a/tools/device/makerecoveries.sh b/tools/device/makerecoveries.sh new file mode 100644 index 0000000..f561c81 --- /dev/null +++ b/tools/device/makerecoveries.sh @@ -0,0 +1,69 @@ +if [ -z "$1" ] +then + echo "Please provide a lunch option." + return +fi + +PRODUCTS=$1 + +for product in $PRODUCTS +do + echo $product +done + +echo $(echo $PRODUCTS | wc -w) Products + +unset PUBLISHED_RECOVERIES + +MCP=$(which mcp) +if [ -z "$MCP" ] +then + NO_UPLOAD=true +fi + +function mcpguard () { + if [ -z $NO_UPLOAD ] + then + mcp $1 $2 + md5sum $1 > $1.md5sum.txt + mcp $1.md5sum.txt $2.md5sum.txt + fi +} + +VERSION=$(cat bootable/recovery/Android.mk | grep RECOVERY_VERSION | grep RECOVERY_NAME | awk '{ print $4 }' | sed s/v//g) +echo Recovery Version: $VERSION + +for lunchoption in $PRODUCTS +do + lunch $lunchoption + if [ -z $NO_CLEAN ] + then + rm -rf $OUT/obj/EXECUTABLES/recovery_intermediates + rm -rf $OUT/recovery* + rm -rf $OUT/root* + fi + DEVICE_NAME=$(echo $TARGET_PRODUCT | sed s/koush_// | sed s/aosp_// | sed s/motorola// | sed s/huawei_// | sed s/htc_// | sed s/_us// | sed s/cyanogen_// | sed s/generic_// | sed s/full_//) + PRODUCT_NAME=$(basename $OUT) + make -j16 recoveryzip + RESULT=$? + if [ $RESULT != "0" ] + then + echo build error! + break + fi + mcpguard $OUT/recovery.img recoveries/recovery-clockwork-$VERSION-$DEVICE_NAME.img + mcpguard $OUT/utilities/update.zip recoveries/recovery-clockwork-$VERSION-$DEVICE_NAME.zip + + if [ -f "ROMManagerManifest/devices.rb" ] + then + pushd ROMManagerManifest + ruby devices.rb $DEVICE_NAME $VERSION $lunchoption + popd + fi +done + +for published_recovery in $PUBLISHED_RECOVERIES +do + echo $published_recovery +done + diff --git a/tools/device/mkrecoveryzip.sh b/tools/device/mkrecoveryzip.sh new file mode 100755 index 0000000..e6fae37 --- /dev/null +++ b/tools/device/mkrecoveryzip.sh @@ -0,0 +1,97 @@ +#!/bin/bash + +OUT=$1 +SIGNAPK=$2 + +if [ -z "$OUT" -o -z "$SIGNAPK" ] +then + echo "Android build environment not detected." + exit 1 +fi + +ANDROID_ROOT=$(pwd) +OUT=$ANDROID_ROOT/$OUT +SIGNAPK=$ANDROID_ROOT/$SIGNAPK + +pushd . > /dev/null 2> /dev/null + +UTILITIES_DIR=$OUT/utilities +mkdir -p $UTILITIES_DIR +RECOVERY_DIR=$UTILITIES_DIR/recovery +rm -rf $RECOVERY_DIR +mkdir -p $RECOVERY_DIR +cd $RECOVERY_DIR +cp -R $OUT/recovery/root/etc etc +cp -R $OUT/recovery/root/sbin sbin +cp -R $OUT/recovery/root/res res +SCRIPT_DIR=META-INF/com/google/android +mkdir -p $SCRIPT_DIR +cp $OUT/system/bin/updater $SCRIPT_DIR/update-binary + + +UPDATER_SCRIPT=$SCRIPT_DIR/updater-script +rm -f $UPDATER_SCRIPT +touch $UPDATER_SCRIPT +mkdir -p $(dirname $UPDATER_SCRIPT) + +FILES= +SYMLINKS= + +for file in $(find .) +do + +if [ -d $file ] +then + continue +fi + +META_INF=$(echo $file | grep META-INF) +if [ ! -z $META_INF ] +then + continue; +fi + +if [ -h $file ] +then + SYMLINKS=$SYMLINKS' '$file +elif [ -f $file ] +then + FILES=$FILES' '$file +fi +done + + +echo 'ui_print("Replacing stock recovery with ClockworkMod recovery...");' >> $UPDATER_SCRIPT + +echo 'delete("sbin/recovery");' >> $UPDATER_SCRIPT +echo 'package_extract_file("sbin/recovery", "/sbin/recovery");' >> $UPDATER_SCRIPT +echo 'set_perm(0, 0, 0755, "/sbin/recovery");' >> $UPDATER_SCRIPT +echo 'symlink("recovery", "/sbin/busybox");' >> $UPDATER_SCRIPT + +echo 'run_program("/sbin/busybox", "sh", "-c", "busybox rm -f /etc ; busybox mkdir -p /etc;");' >> $UPDATER_SCRIPT + +for file in $FILES +do + echo 'delete("'$(echo $file | sed s!\\./!!g)'");' >> $UPDATER_SCRIPT + echo 'package_extract_file("'$(echo $file | sed s!\\./!!g)'", "'$(echo $file | sed s!\\./!/!g)'");' >> $UPDATER_SCRIPT + if [ -x $file ] + then + echo 'set_perm(0, 0, 0755, "'$(echo $file | sed s!\\./!/!g)'");' >> $UPDATER_SCRIPT + fi +done + +for file in $SYMLINKS +do + echo 'symlink("'$(readlink $file)'", "'$(echo $file | sed s!\\./!/!g)'");' >> $UPDATER_SCRIPT +done + +echo 'set_perm_recursive(0, 2000, 0755, 0755, "/sbin");' >> $UPDATER_SCRIPT +echo 'run_program("/sbin/busybox", "sh", "-c", "/sbin/killrecovery.sh");' >> $UPDATER_SCRIPT +rm -f $UTILITIES_DIR/unsigned.zip +rm -f $UTILITIES_DIR/update.zip +echo zip -ry $UTILITIES_DIR/unsigned.zip . -x $SYMLINKS '*\[*' '*\[\[*' +zip -ry $UTILITIES_DIR/unsigned.zip . -x $SYMLINKS '*\[*' '*\[\[*' +java -jar $SIGNAPK -w $ANDROID_ROOT/build/target/product/security/testkey.x509.pem $ANDROID_ROOT/build/target/product/security/testkey.pk8 $UTILITIES_DIR/unsigned.zip $UTILITIES_DIR/update.zip + +echo Recovery FakeFlash is now available at $OUT/utilities/update.zip +popd > /dev/null 2> /dev/null diff --git a/tools/device/mkvendor.sh b/tools/device/mkvendor.sh new file mode 100755 index 0000000..778d82d --- /dev/null +++ b/tools/device/mkvendor.sh @@ -0,0 +1,115 @@ +#!/bin/bash + +function usage +{ + echo Usage: + echo " $(basename $0) manufacturer device [boot.img]" + echo " The boot.img argument is the extracted recovery or boot image." + echo " The boot.img argument should not be provided for devices" + echo " that have non standard boot images (ie, Samsung)." + echo + echo Example: + echo " $(basename $0) motorola sholes ~/Downloads/recovery-sholes.img" + exit 0 +} + +MANUFACTURER=$1 +DEVICE=$2 +BOOTIMAGE=$3 + +UNPACKBOOTIMG=$(which unpackbootimg) + +echo Arguments: $@ + +if [ -z "$MANUFACTURER" ] +then + usage +fi + +if [ -z "$DEVICE" ] +then + usage +fi + +ANDROID_TOP=$(dirname $0)/../../../ +pushd $ANDROID_TOP > /dev/null +ANDROID_TOP=$(pwd) +popd > /dev/null + +TEMPLATE_DIR=$(dirname $0) +pushd $TEMPLATE_DIR > /dev/null +TEMPLATE_DIR=$(pwd) +popd > /dev/null + +DEVICE_DIR=$ANDROID_TOP/device/$MANUFACTURER/$DEVICE + +if [ ! -z "$BOOTIMAGE" ] +then + if [ -z "$UNPACKBOOTIMG" ] + then + echo unpackbootimg not found. Is your android build environment set up and have the host tools been built? + exit 0 + fi + + BOOTIMAGEFILE=$(basename $BOOTIMAGE) + + echo Output will be in $DEVICE_DIR + mkdir -p $DEVICE_DIR + + TMPDIR=/tmp/$(whoami)/bootimg + rm -rf $TMPDIR + mkdir -p $TMPDIR + cp $BOOTIMAGE $TMPDIR + pushd $TMPDIR > /dev/null + unpackbootimg -i $BOOTIMAGEFILE > /dev/null + mkdir ramdisk + pushd ramdisk > /dev/null + gunzip -c ../$BOOTIMAGEFILE-ramdisk.gz | cpio -i + popd > /dev/null + BASE=$(cat $TMPDIR/$BOOTIMAGEFILE-base) + CMDLINE=$(cat $TMPDIR/$BOOTIMAGEFILE-cmdline) + PAGESIZE=$(cat $TMPDIR/$BOOTIMAGEFILE-pagesize) + export SEDCMD="s#__CMDLINE__#$CMDLINE#g" + echo $SEDCMD > $TMPDIR/sedcommand + cp $TMPDIR/$BOOTIMAGEFILE-zImage $DEVICE_DIR/kernel + popd > /dev/null +else + mkdir -p $DEVICE_DIR + touch $DEVICE_DIR/kernel + BASE=10000000 + CMDLINE=no_console_suspend + PAGESIZE=00000800 + export SEDCMD="s#__CMDLINE__#$CMDLINE#g" + echo $SEDCMD > $TMPDIR/sedcommand +fi + +for file in $(find $TEMPLATE_DIR -name '*.template') +do + OUTPUT_FILE=$DEVICE_DIR/$(basename $(echo $file | sed s/\\.template//g)) + cat $file | sed s/__DEVICE__/$DEVICE/g | sed s/__MANUFACTURER__/$MANUFACTURER/g | sed -f $TMPDIR/sedcommand | sed s/__BASE__/$BASE/g | sed s/__PAGE_SIZE__/$PAGESIZE/g > $OUTPUT_FILE +done + +if [ ! -z "$TMPDIR" ] +then + RECOVERY_FSTAB=$TMPDIR/ramdisk/etc/recovery.fstab + if [ -f "$RECOVERY_FSTAB" ] + then + cp $RECOVERY_FSTAB $DEVICE_DIR/recovery.fstab + fi +fi + + +mv $DEVICE_DIR/device.mk $DEVICE_DIR/device_$DEVICE.mk + +echo Creating initial git repository. +pushd $DEVICE_DIR +git init +git add . +git commit -a -m "mkvendor.sh: Initial commit of $DEVICE" +popd + +echo Done! +echo Use the following command to set up your build environment: +echo ' 'lunch cm_$DEVICE-eng +echo And use the follwowing command to build a recovery: +echo ' '. build/tools/device/makerecoveries.sh cm_$DEVICE-eng diff --git a/tools/device/recovery.fstab.template b/tools/device/recovery.fstab.template new file mode 100644 index 0000000..41fb92e --- /dev/null +++ b/tools/device/recovery.fstab.template @@ -0,0 +1,10 @@ +# mount point fstype device [device2] + +/boot mtd boot +/cache yaffs2 cache +/data yaffs2 userdata +/misc mtd misc +/recovery mtd recovery +/sdcard vfat /dev/block/mmcblk0p1 /dev/block/mmcblk0 +/system yaffs2 system +/sd-ext ext4 /dev/block/mmcblk0p2 diff --git a/tools/device/system.prop.template b/tools/device/system.prop.template new file mode 100644 index 0000000..4113929 --- /dev/null +++ b/tools/device/system.prop.template @@ -0,0 +1,3 @@ +# +# system.prop for __DEVICE__ +# diff --git a/tools/getb64key.py b/tools/getb64key.py new file mode 100755 index 0000000..a0cd1c3 --- /dev/null +++ b/tools/getb64key.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python + +import base64 +import sys +import os + +pkFile = open(sys.argv[1], 'rb').readlines() +base64Key = "" +inCert = False +for line in pkFile: + if line.startswith("-"): + inCert = not inCert + continue + + base64Key += line.strip() + +print base64.b16encode(base64.b64decode(base64Key)).lower() diff --git a/tools/post_process_props.py b/tools/post_process_props.py index fa6106f..5193d4b 100755 --- a/tools/post_process_props.py +++ b/tools/post_process_props.py @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import sys +import os, sys # Usage: post_process_props.py file.prop [blacklist_key, ...] # Blacklisted keys are removed from the property file, if present @@ -27,15 +27,22 @@ PROP_VALUE_MAX = 91 # Put the modifications that you need to make into the /system/build.prop into this # function. The prop object has get(name) and put(name,value) methods. -def mangle_build_prop(prop): +def mangle_build_prop(prop, overrides): + if len(overrides) == 0: + return + overridelist = overrides.replace(" ",",").split(",") + for proppair in overridelist: + values = proppair.split("=") + prop.put(values[0], values[1]) + pass # Put the modifications that you need to make into the /default.prop into this # function. The prop object has get(name) and put(name,value) methods. def mangle_default_prop(prop): - # If ro.debuggable is 1, then enable adb on USB by default - # (this is for userdebug builds) - if prop.get("ro.debuggable") == "1": + # If ro.adb.secure is not 1, then enable adb on USB by default + # (this is for eng builds) + if prop.get("ro.adb.secure") != "1": val = prop.get("persist.sys.usb.config") if val == "": val = "adb" @@ -110,6 +117,10 @@ class PropFile: def main(argv): filename = argv[1] + if (len(argv) > 2): + extraargs = argv[2] + else: + extraargs = "" f = open(filename) lines = f.readlines() f.close() @@ -117,7 +128,7 @@ def main(argv): properties = PropFile(lines) if filename.endswith("/build.prop"): - mangle_build_prop(properties) + mangle_build_prop(properties, extraargs) elif filename.endswith("/default.prop"): mangle_default_prop(properties) else: @@ -128,7 +139,7 @@ def main(argv): sys.exit(1) # Drop any blacklisted keys - for key in argv[2:]: + for key in argv[3:]: properties.delete(key) f = open(filename, 'w+') diff --git a/tools/releasetools/add_img_to_target_files.py b/tools/releasetools/add_img_to_target_files.py index f7332b2..e8d61ad 100755 --- a/tools/releasetools/add_img_to_target_files.py +++ b/tools/releasetools/add_img_to_target_files.py @@ -194,6 +194,42 @@ def AddUserdata(output_zip, prefix="IMAGES/"): os.rmdir(temp_dir) +def AddUserdataExtra(output_zip): + """Create extra userdata image and store it in output_zip.""" + + image_props = build_image.ImagePropFromGlobalDict(OPTIONS.info_dict, + "data_extra") + # If no userdataextra_size is provided for extfs, skip userdata_extra.img. + if (image_props.get("fs_type", "").startswith("ext") and + not image_props.get("partition_size")): + return + + extra_name = image_props.get("partition_name", "extra") + + print "creating userdata_%s.img..." % extra_name + + # The name of the directory it is making an image out of matters to + # mkyaffs2image. So we create a temp dir, and within it we create an + # empty dir named "data", and build the image from that. + temp_dir = tempfile.mkdtemp() + user_dir = os.path.join(temp_dir, "data") + os.mkdir(user_dir) + img = tempfile.NamedTemporaryFile() + + fstab = OPTIONS.info_dict["fstab"] + if fstab: + image_props["fs_type" ] = fstab["/data"].fs_type + succ = build_image.BuildImage(user_dir, image_props, img.name) + assert succ, "build userdata_%s.img image failed" % extra_name + + # Disable size check since this fetches original data partition size + #common.CheckSize(img.name, "userdata_extra.img", OPTIONS.info_dict) + output_zip.write(img.name, "userdata_%s.img" % extra_name) + img.close() + os.rmdir(user_dir) + os.rmdir(temp_dir) + + def AddCache(output_zip, prefix="IMAGES/"): """Create an empty cache image and store it in output_zip.""" @@ -293,6 +329,8 @@ def AddImagesToTargetFiles(filename): AddVendor(output_zip) banner("userdata") AddUserdata(output_zip) + banner("extrauserdata") + AddUserdataExtra(output_zip) banner("cache") AddCache(output_zip) diff --git a/tools/releasetools/build_image.py b/tools/releasetools/build_image.py index 4b43c0c..91fe54b 100755 --- a/tools/releasetools/build_image.py +++ b/tools/releasetools/build_image.py @@ -263,9 +263,13 @@ def BuildImage(in_dir, prop_dict, out_file, target_out=None): build_command = ["mkuserimg.sh"] if "extfs_sparse_flag" in prop_dict: build_command.append(prop_dict["extfs_sparse_flag"]) - run_fsck = True - build_command.extend([in_dir, out_file, fs_type, - prop_dict["mount_point"]]) + #run_fsck = True + if "is_userdataextra" in prop_dict: + build_command.extend([in_dir, out_file, fs_type, + "data"]) + else: + build_command.extend([in_dir, out_file, fs_type, + prop_dict["mount_point"]]) build_command.append(prop_dict["partition_size"]) if "journal_size" in prop_dict: build_command.extend(["-j", prop_dict["journal_size"]]) @@ -439,6 +443,11 @@ def ImagePropFromGlobalDict(glob_dict, mount_point): copy_prop("fs_type", "fs_type") copy_prop("userdata_fs_type", "fs_type") copy_prop("userdata_size", "partition_size") + elif mount_point == "data_extra": + copy_prop("fs_type", "fs_type") + copy_prop("userdataextra_size", "partition_size") + copy_prop("userdataextra_name", "partition_name") + d["is_userdataextra"] = True elif mount_point == "cache": copy_prop("cache_fs_type", "fs_type") copy_prop("cache_size", "partition_size") diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py index ebf47cb..d13f82a 100644 --- a/tools/releasetools/common.py +++ b/tools/releasetools/common.py @@ -148,6 +148,8 @@ def LoadInfoDict(input_file): if "fstab_version" not in d: d["fstab_version"] = "1" + if "device_type" not in d: + d["device_type"] = "MMC" try: data = read_helper("META/imagesizes.txt") for line in data.split("\n"): @@ -177,7 +179,7 @@ def LoadInfoDict(input_file): makeint("boot_size") makeint("fstab_version") - d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"]) + d["fstab"] = LoadRecoveryFSTab(read_helper, d["fstab_version"], d["device_type"]) d["build.prop"] = LoadBuildProp(read_helper) return d @@ -200,7 +202,7 @@ def LoadDictionaryFromLines(lines): d[name] = value return d -def LoadRecoveryFSTab(read_helper, fstab_version): +def LoadRecoveryFSTab(read_helper, fstab_version, type): class Partition(object): def __init__(self, mount_point, fs_type, device, length, device2, context): self.mount_point = mount_point @@ -329,44 +331,74 @@ def BuildBootableImage(sourcedir, fs_config_file, info_dict=None): assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,) assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,) - # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set - mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg" - - cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")] - - fn = os.path.join(sourcedir, "second") - if os.access(fn, os.F_OK): - cmd.append("--second") - cmd.append(fn) - - fn = os.path.join(sourcedir, "cmdline") - if os.access(fn, os.F_OK): - cmd.append("--cmdline") - cmd.append(open(fn).read().rstrip("\n")) - - fn = os.path.join(sourcedir, "base") - if os.access(fn, os.F_OK): - cmd.append("--base") - cmd.append(open(fn).read().rstrip("\n")) - - fn = os.path.join(sourcedir, "pagesize") + """check if uboot is requested""" + fn = os.path.join(sourcedir, "ubootargs") if os.access(fn, os.F_OK): - cmd.append("--pagesize") - cmd.append(open(fn).read().rstrip("\n")) + cmd = ["mkimage"] + for argument in open(fn).read().rstrip("\n").split(" "): + cmd.append(argument) + cmd.append("-d") + cmd.append(os.path.join(sourcedir, "kernel")+":"+ramdisk_img.name) + cmd.append(img.name) - args = info_dict.get("mkbootimg_args", None) - if args and args.strip(): - cmd.extend(shlex.split(args)) - - img_unsigned = None - if info_dict.get("vboot", None): - img_unsigned = tempfile.NamedTemporaryFile() - cmd.extend(["--ramdisk", ramdisk_img.name, - "--output", img_unsigned.name]) else: - cmd.extend(["--ramdisk", ramdisk_img.name, + # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set + mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg" + cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")] + + fn = os.path.join(sourcedir, "second") + if os.access(fn, os.F_OK): + cmd.append("--second") + cmd.append(fn) + + fn = os.path.join(sourcedir, "cmdline") + if os.access(fn, os.F_OK): + cmd.append("--cmdline") + cmd.append(open(fn).read().rstrip("\n")) + + fn = os.path.join(sourcedir, "base") + if os.access(fn, os.F_OK): + cmd.append("--base") + cmd.append(open(fn).read().rstrip("\n")) + + fn = os.path.join(sourcedir, "tagsaddr") + if os.access(fn, os.F_OK): + cmd.append("--tags-addr") + cmd.append(open(fn).read().rstrip("\n")) + + fn = os.path.join(sourcedir, "tags_offset") + if os.access(fn, os.F_OK): + cmd.append("--tags_offset") + cmd.append(open(fn).read().rstrip("\n")) + + fn = os.path.join(sourcedir, "ramdisk_offset") + if os.access(fn, os.F_OK): + cmd.append("--ramdisk_offset") + cmd.append(open(fn).read().rstrip("\n")) + + fn = os.path.join(sourcedir, "dt_args") + if os.access(fn, os.F_OK): + cmd.append("--dt") + cmd.append(open(fn).read().rstrip("\n")) + + fn = os.path.join(sourcedir, "pagesize") + if os.access(fn, os.F_OK): + cmd.append("--pagesize") + cmd.append(open(fn).read().rstrip("\n")) + + args = info_dict.get("mkbootimg_args", None) + if args and args.strip(): + cmd.extend(shlex.split(args)) + + img_unsigned = None + if info_dict.get("vboot", None): + img_unsigned = tempfile.NamedTemporaryFile() + cmd.extend(["--ramdisk", ramdisk_img.name, + "--output", img_unsigned.name]) + else: + cmd.extend(["--ramdisk", ramdisk_img.name, "--output", img.name]) - + p = Run(cmd, stdout=subprocess.PIPE) p.communicate() assert p.returncode == 0, "mkbootimg of %s image failed" % ( @@ -453,6 +485,7 @@ def UnzipTemp(filename, pattern=None): OPTIONS.tempfiles.append(tmp) def unzip_to_dir(filename, dirname): + subprocess.call(["rm", "-rf", dirname + filename, "targetfiles-*"]) cmd = ["unzip", "-o", "-q", filename, "-d", dirname] if pattern is not None: cmd.append(pattern) @@ -581,8 +614,8 @@ def CheckSize(data, target, info_dict): fs_type = None limit = None if info_dict["fstab"]: - if mount_point == "/userdata": - mount_point = "/data" + if mount_point == "/userdata_extra": mount_point = "/data" + if mount_point == "/userdata": mount_point = "/data" p = info_dict["fstab"][mount_point] fs_type = p.fs_type device = p.device @@ -990,6 +1023,11 @@ class DeviceSpecificParams(object): used to install the image for the device's baseband processor.""" return self._DoCall("FullOTA_InstallEnd") + def FullOTA_PostValidate(self): + """Called after installing and validating /system; typically this is + used to resize the system partition after a block based installation.""" + return self._DoCall("FullOTA_PostValidate") + def IncrementalOTA_Assertions(self): """Called after emitting the block of assertions at the top of an incremental OTA package. Implementations can add whatever @@ -1336,7 +1374,10 @@ PARTITION_TYPES = { "ext4": "EMMC", "emmc": "EMMC", "f2fs": "EMMC", - "squashfs": "EMMC" + "squashfs": "EMMC", + "ext2": "EMMC", + "ext3": "EMMC", + "vfat": "EMMC" } def GetTypeAndDevice(mount_point, info): @@ -1399,6 +1440,10 @@ def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img, return sh = """#!/system/bin/sh +if [ -f /system/etc/recovery-transform.sh ]; then + exec sh /system/etc/recovery-transform.sh %(recovery_size)d %(recovery_sha1)s %(boot_size)d %(boot_sha1)s +fi + if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then applypatch %(bonus_args)s %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s %(recovery_type)s:%(recovery_device)s %(recovery_sha1)s %(recovery_size)d %(boot_sha1)s:/system/recovery-from-boot.p && log -t recovery "Installing new recovery image: succeeded" || log -t recovery "Installing new recovery image: failed" else diff --git a/tools/releasetools/edify_generator.py b/tools/releasetools/edify_generator.py index a52e328..e3425bf 100644 --- a/tools/releasetools/edify_generator.py +++ b/tools/releasetools/edify_generator.py @@ -117,20 +117,44 @@ class EdifyGenerator(object): def AssertDevice(self, device): """Assert that the device identifier is the given string.""" - cmd = ('getprop("ro.product.device") == "%s" || ' - 'abort("This package is for \\"%s\\" devices; ' - 'this is a \\"" + getprop("ro.product.device") + "\\".");') % ( - device, device) + cmd = ('assert(' + + ' || '.join(['getprop("ro.product.device") == "%s" || getprop("ro.build.product") == "%s"' + % (i, i) for i in device.split(",")]) + + ' || abort("This package is for device: %s; ' + + 'this device is " + getprop("ro.product.device") + ".");' + + ');') % device self.script.append(cmd) def AssertSomeBootloader(self, *bootloaders): - """Asert that the bootloader version is one of *bootloaders.""" + """Assert that the bootloader version is one of *bootloaders.""" cmd = ("assert(" + - " ||\0".join(['getprop("ro.bootloader") == "%s"' % (b,) + " || ".join(['getprop("ro.bootloader") == "%s"' % (b,) for b in bootloaders]) + + ' || abort("This package supports bootloader(s): ' + + ", ".join(["%s" % (b,) for b in bootloaders]) + + '; this device has bootloader " + getprop("ro.bootloader") + ".");' + ");") self.script.append(self.WordWrap(cmd)) + def AssertSomeBaseband(self, *basebands): + """Assert that the baseband version is one of *basebands.""" + cmd = ("assert(" + + " || ".join(['getprop("ro.baseband") == "%s"' % (b,) + for b in basebands]) + + ' || abort("This package supports baseband(s): ' + + ", ".join(["%s" % (b,) for b in basebands]) + + '; this device has baseband " + getprop("ro.baseband") + ".");' + + ");") + self.script.append(self._WordWrap(cmd)) + + def RunBackup(self, command): + self.script.append(('run_program("/tmp/install/bin/backuptool.sh", "%s");' % command)) + + def ValidateSignatures(self, command): + self.script.append('package_extract_file("META-INF/org/cyanogenmod/releasekey", "/tmp/releasekey");') + # Exit code 124 == abort. run_program returns raw, so left-shift 8bit + self.script.append('run_program("/tmp/install/bin/otasigcheck.sh") != "31744" || abort("Can\'t install this package on top of incompatible data. Please try another package or run a factory reset");') + def ShowProgress(self, frac, dur): """Update the progress bar, advancing it over 'frac' over the next 'dur' seconds. 'dur' may be zero to advance it via SetProgress @@ -189,6 +213,12 @@ class EdifyGenerator(object): p.mount_point, mount_flags)) self.mounts.add(p.mount_point) + def Unmount(self, mount_point): + """Unmount the partiiton with the given mount_point.""" + if mount_point in self.mounts: + self.mounts.remove(mount_point) + self.script.append('unmount("%s");' % (mount_point,)) + def UnpackPackageDir(self, src, dst): """Unpack a given directory from the OTA package into the given destination directory.""" @@ -310,10 +340,10 @@ class EdifyGenerator(object): if not self.info.get("use_set_metadata", False): self.script.append('set_perm(%d, %d, 0%o, "%s");' % (uid, gid, mode, fn)) else: - if capabilities is None: - capabilities = "0x0" - cmd = 'set_metadata("%s", "uid", %d, "gid", %d, "mode", 0%o, ' \ - '"capabilities", %s' % (fn, uid, gid, mode, capabilities) + cmd = 'set_metadata("%s", "uid", %d, "gid", %d, "mode", 0%o' \ + % (fn, uid, gid, mode) + if capabilities is not None: + cmd += ', "capabilities", %s' % ( capabilities ) if selabel is not None: cmd += ', "selabel", "%s"' % selabel cmd += ');' @@ -326,11 +356,11 @@ class EdifyGenerator(object): self.script.append('set_perm_recursive(%d, %d, 0%o, 0%o, "%s");' % (uid, gid, dmode, fmode, fn)) else: - if capabilities is None: - capabilities = "0x0" cmd = 'set_metadata_recursive("%s", "uid", %d, "gid", %d, ' \ - '"dmode", 0%o, "fmode", 0%o, "capabilities", %s' \ - % (fn, uid, gid, dmode, fmode, capabilities) + '"dmode", 0%o, "fmode", 0%o' \ + % (fn, uid, gid, dmode, fmode) + if capabilities is not None: + cmd += ', "capabilities", "%s"' % ( capabilities ) if selabel is not None: cmd += ', "selabel", "%s"' % selabel cmd += ');' diff --git a/tools/releasetools/img_from_target_files.py b/tools/releasetools/img_from_target_files.py index c486992..e896ba3 100755 --- a/tools/releasetools/img_from_target_files.py +++ b/tools/releasetools/img_from_target_files.py @@ -47,6 +47,31 @@ def CopyInfo(output_zip): output_zip, os.path.join(OPTIONS.input_tmp, "OTA", "android-info.txt"), "android-info.txt") +def AddRadio(output_zip): + """If they exist, add RADIO files to the output.""" + if os.path.isdir(os.path.join(OPTIONS.input_tmp, "RADIO")): + for radio_root, radio_dirs, radio_files in os.walk(os.path.join(OPTIONS.input_tmp, "RADIO")): + for radio_file in radio_files: + output_zip.write(os.path.join(radio_root, radio_file), radio_file) + + # If a filesmap file exists, create a script to flash the radio images based on it + filesmap = os.path.join(OPTIONS.input_tmp, "RADIO/filesmap") + if os.path.isfile(filesmap): + print "creating flash-radio.sh..." + filesmap_data = open(filesmap, "r") + filesmap_regex = re.compile(r'^(\S+)\s\S+\/by-name\/(\S+).*') + tmp_flash_radio = tempfile.NamedTemporaryFile() + tmp_flash_radio.write("#!/bin/sh\n\n") + for filesmap_line in filesmap_data: + filesmap_entry = filesmap_regex.search(filesmap_line) + if filesmap_entry: + tmp_flash_radio.write("fastboot flash %s %s\n" % (filesmap_entry.group(2), filesmap_entry.group(1))) + tmp_flash_radio.flush() + if os.path.getsize(tmp_flash_radio.name) > 0: + output_zip.write(tmp_flash_radio.name, "flash-radio.sh") + else: + print "flash-radio.sh is empty, skipping..." + tmp_flash_radio.close() def main(argv): bootable_only = [False] @@ -72,6 +97,7 @@ def main(argv): OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0]) output_zip = zipfile.ZipFile(args[1], "w", compression=zipfile.ZIP_DEFLATED) CopyInfo(output_zip) + AddRadio(output_zip) try: done = False diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py index e5a30ad..77af8ab 100755 --- a/tools/releasetools/ota_from_target_files.py +++ b/tools/releasetools/ota_from_target_files.py @@ -92,6 +92,18 @@ Usage: ota_from_target_files [flags] input_target_files output_ota_package --stash_threshold <float> Specifies the threshold that will be used to compute the maximum allowed stash size (defaults to 0.8). + + --backup <boolean> + Enable or disable the execution of backuptool.sh. + Disabled by default. + + --override_device <device> + Override device-specific asserts. Can be a comma-separated list. + + --override_prop <boolean> + Override build.prop items with custom vendor init. + Enabled when TARGET_UNIFIED_DEVICE is defined in BoardConfig + """ import sys @@ -134,6 +146,9 @@ OPTIONS.full_bootloader = False # Stash size cannot exceed cache_size * threshold. OPTIONS.cache_size = None OPTIONS.stash_threshold = 0.8 +OPTIONS.backuptool = False +OPTIONS.override_device = 'auto' +OPTIONS.override_prop = False def MostPopularKey(d, default): """Given a dict, return the key corresponding to the largest @@ -148,7 +163,7 @@ def MostPopularKey(d, default): def IsSymlink(info): """Return true if the zipfile.ZipInfo object passed in represents a symlink.""" - return (info.external_attr >> 16) == 0o120777 + return (info.external_attr >> 16) & 0o770000 == 0o120000 def IsRegular(info): """Return true if the zipfile.ZipInfo object passed in represents a @@ -413,7 +428,10 @@ def SignOutput(temp_zip_name, output_zip_name): def AppendAssertions(script, info_dict, oem_dict=None): oem_props = info_dict.get("oem_fingerprint_properties") if oem_props is None or len(oem_props) == 0: - device = GetBuildProp("ro.product.device", info_dict) + if OPTIONS.override_device == "auto": + device = GetBuildProp("ro.product.device", info_dict) + else: + device = OPTIONS.override_device script.AssertDevice(device) else: if oem_dict is None: @@ -445,8 +463,9 @@ def GetOemProperty(name, oem_props, oem_dict, info_dict): return oem_dict[name] return GetBuildProp(name, info_dict) - def CalculateFingerprint(oem_props, oem_dict, info_dict): + if OPTIONS.override_prop: + return GetBuildProp("ro.build.date.utc", info_dict) if oem_props is None: return GetBuildProp("ro.build.fingerprint", info_dict) return "%s/%s/%s:%s" % ( @@ -496,6 +515,15 @@ def GetImage(which, tmpdir, info_dict): return sparse_img.SparseImage(path, mappath, clobbered_blocks) +def CopyInstallTools(output_zip): + install_path = os.path.join(OPTIONS.input_tmp, "INSTALL") + for root, subdirs, files in os.walk(install_path): + for f in files: + install_source = os.path.join(root, f) + install_target = os.path.join("install", os.path.relpath(root, install_path), f) + output_zip.write(install_source, install_target) + + def WriteFullOTAPackage(input_zip, output_zip): # TODO: how to determine this? We don't know what version it will # be installed on top of. For now, we expect the API just won't @@ -513,13 +541,18 @@ def WriteFullOTAPackage(input_zip, output_zip): oem_dict = common.LoadDictionaryFromLines( open(OPTIONS.oem_source).readlines()) - metadata = { - "post-build": CalculateFingerprint(oem_props, oem_dict, - OPTIONS.info_dict), - "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict, - OPTIONS.info_dict), - "post-timestamp": GetBuildProp("ro.build.date.utc", OPTIONS.info_dict), - } + if OPTIONS.override_prop: + metadata = {"post-timestamp": GetBuildProp("ro.build.date.utc", + OPTIONS.info_dict), + } + else: + metadata = {"post-build": CalculateFingerprint( + oem_props, oem_dict, OPTIONS.info_dict), + "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict, + OPTIONS.info_dict), + "post-timestamp": GetBuildProp("ro.build.date.utc", + OPTIONS.info_dict), + } device_specific = common.DeviceSpecificParams( input_zip=input_zip, @@ -533,10 +566,10 @@ def WriteFullOTAPackage(input_zip, output_zip): has_recovery_patch = HasRecoveryPatch(input_zip) block_based = OPTIONS.block_based and has_recovery_patch - if not OPTIONS.omit_prereq: - ts = GetBuildProp("ro.build.date.utc", OPTIONS.info_dict) - ts_text = GetBuildProp("ro.build.date", OPTIONS.info_dict) - script.AssertOlderBuild(ts, ts_text) + #if not OPTIONS.omit_prereq: + # ts = GetBuildProp("ro.build.date.utc", OPTIONS.info_dict) + # ts_text = GetBuildProp("ro.build.date", OPTIONS.info_dict) + # script.AssertOlderBuild(ts, ts_text) AppendAssertions(script, OPTIONS.info_dict, oem_dict) device_specific.FullOTA_Assertions() @@ -585,8 +618,19 @@ else if get_stage("%(bcb_dev)s") == "3/3" then script.Print("Target: %s" % CalculateFingerprint( oem_props, oem_dict, OPTIONS.info_dict)) + script.AppendExtra("ifelse(is_mounted(\"/system\"), unmount(\"/system\"));") device_specific.FullOTA_InstallBegin() + CopyInstallTools(output_zip) + script.UnpackPackageDir("install", "/tmp/install") + script.SetPermissionsRecursive("/tmp/install", 0, 0, 0755, 0644, None, None) + script.SetPermissionsRecursive("/tmp/install/bin", 0, 0, 0755, 0755, None, None) + + if OPTIONS.backuptool: + script.Mount("/system") + script.RunBackup("backup") + script.Unmount("/system") + system_progress = 0.75 if OPTIONS.wipe_user_data: @@ -594,6 +638,14 @@ else if get_stage("%(bcb_dev)s") == "3/3" then if HasVendorPartition(input_zip): system_progress -= 0.1 + script.AppendExtra("if is_mounted(\"/data\") then") + script.ValidateSignatures("data") + script.AppendExtra("else") + script.Mount("/data") + script.ValidateSignatures("data") + script.Unmount("/data") + script.AppendExtra("endif;") + if "selinux_fc" in OPTIONS.info_dict: WritePolicyConfig(OPTIONS.info_dict["selinux_fc"], output_zip) @@ -658,6 +710,16 @@ else if get_stage("%(bcb_dev)s") == "3/3" then common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict) common.ZipWriteStr(output_zip, "boot.img", boot_img.data) + device_specific.FullOTA_PostValidate() + + if OPTIONS.backuptool: + script.ShowProgress(0.02, 10) + if block_based: + script.Mount("/system") + script.RunBackup("restore") + if block_based: + script.Unmount("/system") + script.ShowProgress(0.05, 5) script.WriteRawImage("/boot", "boot.img") @@ -688,11 +750,15 @@ endif; script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary) WriteMetadata(metadata, output_zip) + common.ZipWriteStr(output_zip, "system/build.prop", + ""+input_zip.read("SYSTEM/build.prop")) + + common.ZipWriteStr(output_zip, "META-INF/org/cyanogenmod/releasekey", + ""+input_zip.read("META/releasekey.txt")) def WritePolicyConfig(file_name, output_zip): common.ZipWrite(output_zip, file_name, os.path.basename(file_name)) - def WriteMetadata(metadata, output_zip): common.ZipWriteStr(output_zip, "META-INF/com/android/metadata", "".join(["%s=%s\n" % kv @@ -744,12 +810,16 @@ def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip): source_version, OPTIONS.target_info_dict, fstab=OPTIONS.source_info_dict["fstab"]) - metadata = { - "pre-device": GetBuildProp("ro.product.device", - OPTIONS.source_info_dict), - "post-timestamp": GetBuildProp("ro.build.date.utc", - OPTIONS.target_info_dict), - } + if OPTIONS.override_prop: + metadata = {"post-timestamp": GetBuildProp("ro.build.date.utc", + OPTIONS.target_info_dict), + } + else: + metadata = {"pre-device": GetBuildProp("ro.product.device", + OPTIONS.source_info_dict), + "post-timestamp": GetBuildProp("ro.build.date.utc", + OPTIONS.target_info_dict), + } device_specific = common.DeviceSpecificParams( source_zip=source_zip, @@ -767,10 +837,11 @@ def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip): # devices with thumbprints are all using file-based OTAs. Long term we # should factor out the common parts into a shared one to avoid further # divergence. - source_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.source_info_dict) - target_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.target_info_dict) - metadata["pre-build"] = source_fp - metadata["post-build"] = target_fp + if not OPTIONS.override_prop: + source_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.source_info_dict) + target_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.target_info_dict) + metadata["pre-build"] = source_fp + metadata["post-build"] = target_fp source_boot = common.GetBootableImage( "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT", @@ -1144,12 +1215,16 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): oem_dict = common.LoadDictionaryFromLines( open(OPTIONS.oem_source).readlines()) - metadata = { - "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict, - OPTIONS.source_info_dict), - "post-timestamp": GetBuildProp("ro.build.date.utc", - OPTIONS.target_info_dict), - } + if OPTIONS.override_prop: + metadata = {"post-timestamp": GetBuildProp("ro.build.date.utc", + OPTIONS.target_info_dict), + } + else: + metadata = {"pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict, + OPTIONS.source_info_dict), + "post-timestamp": GetBuildProp("ro.build.date.utc", + OPTIONS.target_info_dict), + } device_specific = common.DeviceSpecificParams( source_zip=source_zip, @@ -1169,20 +1244,19 @@ def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip): else: vendor_diff = None - target_fp = CalculateFingerprint(oem_props, oem_dict, - OPTIONS.target_info_dict) - source_fp = CalculateFingerprint(oem_props, oem_dict, - OPTIONS.source_info_dict) + if not OPTIONS.override_prop: + target_fp = CalculateFingerprint(oem_props, oem_dict, OPTIONS.target_info_dict) + source_fp = CalculateFingerprint(oem_props, oem_dict, OPTIONS.source_info_dict) - if oem_props is None: - script.AssertSomeFingerprint(source_fp, target_fp) - else: - script.AssertSomeThumbprint( - GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict), - GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict)) + if oem_props is None: + script.AssertSomeFingerprint(source_fp, target_fp) + else: + script.AssertSomeThumbprint( + GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict), + GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict)) - metadata["pre-build"] = source_fp - metadata["post-build"] = target_fp + metadata["pre-build"] = source_fp + metadata["post-build"] = target_fp source_boot = common.GetBootableImage( "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT", @@ -1499,7 +1573,6 @@ endif; WriteMetadata(metadata, output_zip) - def main(argv): def option_handler(o, a): @@ -1550,6 +1623,12 @@ def main(argv): except ValueError: raise ValueError("Cannot parse value %r for option %r - expecting " "a float" % (a, o)) + elif o in ("--backup"): + OPTIONS.backuptool = bool(a.lower() == 'true') + elif o in ("--override_device"): + OPTIONS.override_device = a + elif o in ("--override_prop"): + OPTIONS.override_prop = bool(a.lower() == 'true') else: return False return True @@ -1575,6 +1654,9 @@ def main(argv): "verify", "no_fallback_to_full", "stash_threshold=", + "backup=", + "override_device=", + "override_prop=" ], extra_option_handler=option_handler) if len(args) != 2: diff --git a/tools/repopick.py b/tools/repopick.py new file mode 100755 index 0000000..ac8875a --- /dev/null +++ b/tools/repopick.py @@ -0,0 +1,355 @@ +#!/usr/bin/env python +# +# Copyright (C) 2013-15 The CyanogenMod Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# +# Run repopick.py -h for a description of this utility. +# + +from __future__ import print_function + +import sys +import json +import os +import subprocess +import re +import argparse +import textwrap +from xml.etree import ElementTree + +try: + # For python3 + import urllib.error + import urllib.request +except ImportError: + # For python2 + import imp + import urllib2 + urllib = imp.new_module('urllib') + urllib.error = urllib2 + urllib.request = urllib2 + + +# Verifies whether pathA is a subdirectory (or the same) as pathB +def is_subdir(a, b): + a = os.path.realpath(a) + '/' + b = os.path.realpath(b) + '/' + return b == a[:len(b)] + + +def fetch_query_via_ssh(remote_url, query): + """Given a remote_url and a query, return the list of changes that fit it + This function is slightly messy - the ssh api does not return data in the same structure as the HTTP REST API + We have to get the data, then transform it to match what we're expecting from the HTTP RESET API""" + if remote_url.count(':') == 2: + (uri, userhost, port) = remote_url.split(':') + userhost = userhost[2:] + elif remote_url.count(':') == 1: + (uri, userhost) = remote_url.split(':') + userhost = userhost[2:] + port = 29418 + else: + raise Exception('Malformed URI: Expecting ssh://[user@]host[:port]') + + + out = subprocess.check_output(['ssh', '-x', '-p{0}'.format(port), userhost, 'gerrit', 'query', '--format=JSON --patch-sets --current-patch-set', query]) + + reviews = [] + for line in out.split('\n'): + try: + data = json.loads(line) + # make our data look like the http rest api data + review = { + 'branch': data['branch'], + 'change_id': data['id'], + 'current_revision': data['currentPatchSet']['revision'], + 'number': int(data['number']), + 'revisions': {patch_set['revision']: { + 'number': int(patch_set['number']), + 'fetch': { + 'ssh': { + 'ref': patch_set['ref'], + 'url': u'ssh://{0}:{1}/{2}'.format(userhost, port, data['project']) + } + } + } for patch_set in data['patchSets']}, + 'subject': data['subject'], + 'project': data['project'], + 'status': data['status'] + } + reviews.append(review) + except: + pass + args.quiet or print('Found {0} reviews'.format(len(reviews))) + return reviews + + +def fetch_query_via_http(remote_url, query): + + """Given a query, fetch the change numbers via http""" + url = '{0}/changes/?q={1}&o=CURRENT_REVISION&o=ALL_REVISIONS'.format(remote_url, query) + data = urllib.request.urlopen(url).read().decode('utf-8') + reviews = json.loads(data[5:]) + + for review in reviews: + review[u'number'] = review.pop('_number') + + return reviews + + +def fetch_query(remote_url, query): + """Wrapper for fetch_query_via_proto functions""" + if remote_url[0:3] == 'ssh': + return fetch_query_via_ssh(remote_url, query) + elif remote_url[0:4] == 'http': + return fetch_query_via_http(remote_url, query.replace(' ', '+')) + else: + raise Exception('Gerrit URL should be in the form http[s]://hostname/ or ssh://[user@]host[:port]') + +if __name__ == '__main__': + # Default to CyanogenMod Gerrit + default_gerrit = 'http://review.cyanogenmod.org' + + parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, description=textwrap.dedent('''\ + repopick.py is a utility to simplify the process of cherry picking + patches from CyanogenMod's Gerrit instance (or any gerrit instance of your choosing) + + Given a list of change numbers, repopick will cd into the project path + and cherry pick the latest patch available. + + With the --start-branch argument, the user can specify that a branch + should be created before cherry picking. This is useful for + cherry-picking many patches into a common branch which can be easily + abandoned later (good for testing other's changes.) + + The --abandon-first argument, when used in conjunction with the + --start-branch option, will cause repopick to abandon the specified + branch in all repos first before performing any cherry picks.''')) + parser.add_argument('change_number', nargs='*', help='change number to cherry pick. Use {change number}/{patchset number} to get a specific revision.') + parser.add_argument('-i', '--ignore-missing', action='store_true', help='do not error out if a patch applies to a missing directory') + parser.add_argument('-s', '--start-branch', nargs=1, help='start the specified branch before cherry picking') + parser.add_argument('-a', '--abandon-first', action='store_true', help='before cherry picking, abandon the branch specified in --start-branch') + parser.add_argument('-b', '--auto-branch', action='store_true', help='shortcut to "--start-branch auto --abandon-first --ignore-missing"') + parser.add_argument('-q', '--quiet', action='store_true', help='print as little as possible') + parser.add_argument('-v', '--verbose', action='store_true', help='print extra information to aid in debug') + parser.add_argument('-f', '--force', action='store_true', help='force cherry pick even if change is closed') + parser.add_argument('-p', '--pull', action='store_true', help='execute pull instead of cherry-pick') + parser.add_argument('-P', '--path', help='use the specified path for the change') + parser.add_argument('-t', '--topic', help='pick all commits from a specified topic') + parser.add_argument('-Q', '--query', help='pick all commits using the specified query') + parser.add_argument('-g', '--gerrit', default=default_gerrit, help='Gerrit Instance to use. Form proto://[user@]host[:port]') + args = parser.parse_args() + if not args.start_branch and args.abandon_first: + parser.error('if --abandon-first is set, you must also give the branch name with --start-branch') + if args.auto_branch: + args.abandon_first = True + args.ignore_missing = True + if not args.start_branch: + args.start_branch = ['auto'] + if args.quiet and args.verbose: + parser.error('--quiet and --verbose cannot be specified together') + + if (1 << bool(args.change_number) << bool(args.topic) << bool(args.query)) != 2: + parser.error('One (and only one) of change_number, topic, and query are allowed') + + # Change current directory to the top of the tree + if 'ANDROID_BUILD_TOP' in os.environ: + top = os.environ['ANDROID_BUILD_TOP'] + + if not is_subdir(os.getcwd(), top): + sys.stderr.write('ERROR: You must run this tool from within $ANDROID_BUILD_TOP!\n') + sys.exit(1) + os.chdir(os.environ['ANDROID_BUILD_TOP']) + + # Sanity check that we are being run from the top level of the tree + if not os.path.isdir('.repo'): + sys.stderr.write('ERROR: No .repo directory found. Please run this from the top of your tree.\n') + sys.exit(1) + + # If --abandon-first is given, abandon the branch before starting + if args.abandon_first: + # Determine if the branch already exists; skip the abandon if it does not + plist = subprocess.check_output(['repo', 'info']) + needs_abandon = False + for pline in plist: + matchObj = re.match(r'Local Branches.*\[(.*)\]', pline) + if matchObj: + local_branches = re.split('\s*,\s*', matchObj.group(1)) + if any(args.start_branch[0] in s for s in local_branches): + needs_abandon = True + + if needs_abandon: + # Perform the abandon only if the branch already exists + if not args.quiet: + print('Abandoning branch: %s' % args.start_branch[0]) + subprocess.check_output(['repo', 'abandon', args.start_branch[0]]) + if not args.quiet: + print('') + + # Get the master manifest from repo + # - convert project name and revision to a path + project_name_to_data = {} + manifest = subprocess.check_output(['repo', 'manifest']) + xml_root = ElementTree.fromstring(manifest) + projects = xml_root.findall('project') + default_revision = xml_root.findall('default')[0].get('revision').split('/')[-1] + + #dump project data into the a list of dicts with the following data: + #{project: {path, revision}} + + for project in projects: + name = project.get('name') + path = project.get('path') + revision = project.get('revision') + if revision is None: + revision = default_revision + + if not name in project_name_to_data: + project_name_to_data[name] = {} + project_name_to_data[name][revision] = path + + # get data on requested changes + reviews = [] + change_numbers = [] + if args.topic: + reviews = fetch_query(args.gerrit, 'topic:{0}'.format(args.topic)) + change_numbers = sorted([str(r['number']) for r in reviews]) + if args.query: + reviews = fetch_query(args.gerrit, args.query) + change_numbers = sorted([str(r['number']) for r in reviews]) + if args.change_number: + reviews = fetch_query(args.gerrit, ' OR '.join('change:{0}'.format(x.split('/')[0]) for x in args.change_number)) + change_numbers = args.change_number + + # make list of things to actually merge + mergables = [] + + for change in change_numbers: + patchset = None + if '/' in change: + (change, patchset) = change.split('/') + change = int(change) + + review = [x for x in reviews if x['number'] == change][0] + mergables.append({ + 'subject': review['subject'], + 'project': review['project'], + 'branch': review['branch'], + 'change_number': review['number'], + 'status': review['status'], + 'fetch': None + }) + mergables[-1]['fetch'] = review['revisions'][review['current_revision']]['fetch'] + mergables[-1]['id'] = change + if patchset: + try: + mergables[-1]['fetch'] = [x['fetch'] for x in review['revisions'] if x['_number'] == patchset][0] + mergables[-1]['id'] = '{0}/{1}'.format(change, patchset) + except (IndexError, ValueError): + args.quiet or print('ERROR: The patch set {0}/{1} could not be found, using CURRENT_REVISION instead.'.format(change, patchset)) + + for item in mergables: + args.quiet or print('Applying change number {0}...'.format(item['id'])) + # Check if change is open and exit if it's not, unless -f is specified + if (item['status'] != 'OPEN' and item['status'] != 'NEW') and not args.query: + if args.force: + print('!! Force-picking a closed change !!\n') + else: + print('Change status is ' + item['status'] + '. Skipping the cherry pick.\nUse -f to force this pick.') + continue + + # Convert the project name to a project path + # - check that the project path exists + project_path = None + + if item['project'] in project_name_to_data and item['branch'] in project_name_to_data[item['project']]: + project_path = project_name_to_data[item['project']][item['branch']] + elif args.path: + project_path = args.path + elif args.ignore_missing: + print('WARNING: Skipping {0} since there is no project directory for: {1}\n'.format(item['id'], item['project'])) + continue + else: + sys.stderr.write('ERROR: For {0}, could not determine the project path for project {1}\n'.format(item['id'], item['project'])) + sys.exit(1) + + # If --start-branch is given, create the branch (more than once per path is okay; repo ignores gracefully) + if args.start_branch: + subprocess.check_output(['repo', 'start', args.start_branch[0], project_path]) + + # Print out some useful info + if not args.quiet: + print('--> Subject: "{0}"'.format(item['subject'])) + print('--> Project path: {0}'.format(project_path)) + print('--> Change number: {0} (Patch Set {0})'.format(item['id'])) + + if 'anonymous http' in item['fetch']: + method = 'anonymous http' + else: + method = 'ssh' + + # Try fetching from GitHub first if using default gerrit + if args.gerrit == default_gerrit: + if args.verbose: + print('Trying to fetch the change from GitHub') + + if args.pull: + cmd = ['git pull --no-edit github', item['fetch'][method]['ref']] + else: + cmd = ['git fetch github', item['fetch'][method]['ref']] + if args.quiet: + cmd.append('--quiet') + else: + print(cmd) + result = subprocess.call([' '.join(cmd)], cwd=project_path, shell=True) + if result != 0: + print('ERROR: git command failed') + sys.exit(result) + FETCH_HEAD = '{0}/.git/FETCH_HEAD'.format(project_path) + # Check if it worked + if args.gerrit != default_gerrit or os.stat(FETCH_HEAD).st_size == 0: + # If not using the default gerrit or github failed, fetch from gerrit. + if args.verbose: + if args.gerrit == default_gerrit: + print('Fetching from GitHub didn\'t work, trying to fetch the change from Gerrit') + else: + print('Fetching from {0}'.format(args.gerrit)) + + if args.pull: + cmd = ['git pull --no-edit', item['fetch'][method]['url'], item['fetch'][method]['ref']] + else: + cmd = ['git fetch', item['fetch'][method]['url'], item['fetch'][method]['ref']] + if args.quiet: + cmd.append('--quiet') + else: + print(cmd) + result = subprocess.call([' '.join(cmd)], cwd=project_path, shell=True) + if result != 0: + print('ERROR: git command failed') + sys.exit(result) + # Perform the cherry-pick + if not args.pull: + cmd = ['git cherry-pick FETCH_HEAD'] + if args.quiet: + cmd_out = open(os.devnull, 'wb') + else: + cmd_out = None + result = subprocess.call(cmd, cwd=project_path, shell=True, stdout=cmd_out, stderr=cmd_out) + if result != 0: + print('ERROR: git command failed') + sys.exit(result) + if not args.quiet: + print('') diff --git a/tools/roomservice.py b/tools/roomservice.py new file mode 100755 index 0000000..01a7154 --- /dev/null +++ b/tools/roomservice.py @@ -0,0 +1,295 @@ +#!/usr/bin/env python +# Copyright (C) 2012-2013, The CyanogenMod Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import print_function + +import base64 +import json +import netrc +import os +import re +import sys +try: + # For python3 + import urllib.error + import urllib.parse + import urllib.request +except ImportError: + # For python2 + import imp + import urllib2 + import urlparse + urllib = imp.new_module('urllib') + urllib.error = urllib2 + urllib.parse = urlparse + urllib.request = urllib2 + +from xml.etree import ElementTree + +product = sys.argv[1]; + +if len(sys.argv) > 2: + depsonly = sys.argv[2] +else: + depsonly = None + +try: + device = product[product.index("_") + 1:] +except: + device = product + +if not depsonly: + print("Device %s not found. Attempting to retrieve device repository from CyanogenMod Github (http://github.com/CyanogenMod)." % device) + +repositories = [] + +try: + authtuple = netrc.netrc().authenticators("api.github.com") + + if authtuple: + githubauth = base64.encodestring('%s:%s' % (authtuple[0], authtuple[2])).replace('\n', '') + else: + githubauth = None +except: + githubauth = None + +def add_auth(githubreq): + if githubauth: + githubreq.add_header("Authorization","Basic %s" % githubauth) + +if not depsonly: + githubreq = urllib.request.Request("https://api.github.com/search/repositories?q=%s+user:CyanogenMod+in:name+fork:true" % device) + add_auth(githubreq) + try: + result = json.loads(urllib.request.urlopen(githubreq).read().decode()) + except urllib.error.URLError: + print("Failed to search GitHub") + sys.exit() + except ValueError: + print("Failed to parse return data from GitHub") + sys.exit() + for res in result.get('items', []): + repositories.append(res) + +local_manifests = r'.repo/local_manifests' +if not os.path.exists(local_manifests): os.makedirs(local_manifests) + +def exists_in_tree(lm, repository): + for child in lm.getchildren(): + if child.attrib['name'].endswith(repository): + return True + return False + +# in-place prettyprint formatter +def indent(elem, level=0): + i = "\n" + level*" " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for elem in elem: + indent(elem, level+1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + +def get_default_revision(): + m = ElementTree.parse(".repo/manifest.xml") + d = m.findall('default')[0] + r = d.get('revision') + return r.replace('refs/heads/', '').replace('refs/tags/', '') + +def get_from_manifest(devicename): + try: + lm = ElementTree.parse(".repo/local_manifests/roomservice.xml") + lm = lm.getroot() + except: + lm = ElementTree.Element("manifest") + + for localpath in lm.findall("project"): + if re.search("android_device_.*_%s$" % device, localpath.get("name")): + return localpath.get("path") + + # Devices originally from AOSP are in the main manifest... + try: + mm = ElementTree.parse(".repo/manifest.xml") + mm = mm.getroot() + except: + mm = ElementTree.Element("manifest") + + for localpath in mm.findall("project"): + if re.search("android_device_.*_%s$" % device, localpath.get("name")): + return localpath.get("path") + + return None + +def is_in_manifest(projectname): + try: + lm = ElementTree.parse(".repo/local_manifests/roomservice.xml") + lm = lm.getroot() + except: + lm = ElementTree.Element("manifest") + + for localpath in lm.findall("project"): + if localpath.get("name") == projectname: + return 1 + + ## Search in main manifest, too + try: + lm = ElementTree.parse(".repo/manifest.xml") + lm = lm.getroot() + except: + lm = ElementTree.Element("manifest") + + for localpath in lm.findall("project"): + if localpath.get("name") == projectname: + return 1 + + return None + +def add_to_manifest(repositories, fallback_branch = None): + try: + lm = ElementTree.parse(".repo/local_manifests/roomservice.xml") + lm = lm.getroot() + except: + lm = ElementTree.Element("manifest") + + for repository in repositories: + repo_name = repository['repository'] + repo_target = repository['target_path'] + if exists_in_tree(lm, repo_name): + print('CyanogenMod/%s already exists' % (repo_name)) + continue + + print('Adding dependency: CyanogenMod/%s -> %s' % (repo_name, repo_target)) + project = ElementTree.Element("project", attrib = { "path": repo_target, + "remote": "github", "name": "CyanogenMod/%s" % repo_name }) + + if 'branch' in repository: + project.set('revision',repository['branch']) + elif fallback_branch: + print("Using fallback branch %s for %s" % (fallback_branch, repo_name)) + project.set('revision', fallback_branch) + else: + print("Using default branch for %s" % repo_name) + + lm.append(project) + + indent(lm, 0) + raw_xml = ElementTree.tostring(lm).decode() + raw_xml = '<?xml version="1.0" encoding="UTF-8"?>\n' + raw_xml + + f = open('.repo/local_manifests/roomservice.xml', 'w') + f.write(raw_xml) + f.close() + +def fetch_dependencies(repo_path, fallback_branch = None): + print('Looking for dependencies') + dependencies_path = repo_path + '/cm.dependencies' + syncable_repos = [] + + if os.path.exists(dependencies_path): + dependencies_file = open(dependencies_path, 'r') + dependencies = json.loads(dependencies_file.read()) + fetch_list = [] + + for dependency in dependencies: + if not is_in_manifest("CyanogenMod/%s" % dependency['repository']): + fetch_list.append(dependency) + syncable_repos.append(dependency['target_path']) + + dependencies_file.close() + + if len(fetch_list) > 0: + print('Adding dependencies to manifest') + add_to_manifest(fetch_list, fallback_branch) + else: + print('Dependencies file not found, bailing out.') + + if len(syncable_repos) > 0: + print('Syncing dependencies') + os.system('repo sync --force-sync %s' % ' '.join(syncable_repos)) + + for deprepo in syncable_repos: + fetch_dependencies(deprepo) + +def has_branch(branches, revision): + return revision in [branch['name'] for branch in branches] + +if depsonly: + repo_path = get_from_manifest(device) + if repo_path: + fetch_dependencies(repo_path) + else: + print("Trying dependencies-only mode on a non-existing device tree?") + + sys.exit() + +else: + for repository in repositories: + repo_name = repository['name'] + if repo_name.startswith("android_device_") and repo_name.endswith("_" + device): + print("Found repository: %s" % repository['name']) + + manufacturer = repo_name.replace("android_device_", "").replace("_" + device, "") + + default_revision = get_default_revision() + print("Default revision: %s" % default_revision) + print("Checking branch info") + githubreq = urllib.request.Request(repository['branches_url'].replace('{/branch}', '')) + add_auth(githubreq) + result = json.loads(urllib.request.urlopen(githubreq).read().decode()) + + ## Try tags, too, since that's what releases use + if not has_branch(result, default_revision): + githubreq = urllib.request.Request(repository['tags_url'].replace('{/tag}', '')) + add_auth(githubreq) + result.extend (json.loads(urllib.request.urlopen(githubreq).read().decode())) + + repo_path = "device/%s/%s" % (manufacturer, device) + adding = {'repository':repo_name,'target_path':repo_path} + + fallback_branch = None + if not has_branch(result, default_revision): + if os.getenv('ROOMSERVICE_BRANCHES'): + fallbacks = list(filter(bool, os.getenv('ROOMSERVICE_BRANCHES').split(' '))) + for fallback in fallbacks: + if has_branch(result, fallback): + print("Using fallback branch: %s" % fallback) + fallback_branch = fallback + break + + if not fallback_branch: + print("Default revision %s not found in %s. Bailing." % (default_revision, repo_name)) + print("Branches found:") + for branch in [branch['name'] for branch in result]: + print(branch) + print("Use the ROOMSERVICE_BRANCHES environment variable to specify a list of fallback branches.") + sys.exit() + + add_to_manifest([adding], fallback_branch) + + print("Syncing repository to retrieve project.") + os.system('repo sync --force-sync %s' % repo_path) + print("Repository synced!") + + fetch_dependencies(repo_path, fallback_branch) + print("Done") + sys.exit() + +print("Repository for %s not found in the CyanogenMod Github repository list. If this is in error, you may need to manually add it to your local_manifests/roomservice.xml." % device) |