Beispiel #1
0
def repack(apk, processed_out, resources, temp, quiet):
    processed_apk = os.path.join(temp, 'processed.apk')
    shutil.copyfile(apk, processed_apk)
    if not processed_out:
        utils.Print('Using original APK as is', quiet=quiet)
        return processed_apk
    utils.Print('Repacking APK with dex files from {}'.format(processed_apk),
                quiet=quiet)

    # Delete original dex files in APK.
    with utils.ChangedWorkingDirectory(temp, quiet=quiet):
        cmd = ['zip', '-d', 'processed.apk', '*.dex']
        utils.RunCmd(cmd, quiet=quiet)

    # Unzip the jar or zip file into `temp`.
    if processed_out.endswith('.zip') or processed_out.endswith('.jar'):
        cmd = ['unzip', processed_out, '-d', temp]
        if quiet:
            cmd.insert(1, '-q')
        utils.RunCmd(cmd, quiet=quiet)
        processed_out = temp

    # Insert the new dex and resource files from `processed_out` into the APK.
    with utils.ChangedWorkingDirectory(processed_out, quiet=quiet):
        dex_files = glob.glob('*.dex')
        resource_files = glob.glob(resources) if resources else []
        cmd = ['zip', '-u', '-9', processed_apk] + dex_files + resource_files
        utils.RunCmd(cmd, quiet=quiet)
    return processed_apk
Beispiel #2
0
def sign(unsigned_apk, signed_apk, keystore, quiet=False):
    utils.Print('Signing (ignore the warnings)', quiet=quiet)
    cmd = ['zip', '-d', unsigned_apk, 'META-INF/*']
    utils.RunCmd(cmd, quiet=quiet)
    cmd = [
        'jarsigner', '-sigalg', 'SHA1withRSA', '-digestalg', 'SHA1',
        '-keystore', keystore, '-storepass', 'android', '-signedjar',
        signed_apk, unsigned_apk, 'androiddebugkey'
    ]
    utils.RunCmd(cmd, quiet=quiet)
Beispiel #3
0
def masseur(apk,
            dex=None,
            resources=None,
            out=None,
            adb_options=None,
            keystore=None,
            install=False,
            quiet=False):
    if not out:
        out = os.path.basename(apk)
    if not keystore:
        keystore = findKeystore()
    with utils.TempDir() as temp:
        processed_apk = None
        if dex:
            processed_apk = repack(apk, dex, resources, temp, quiet)
        else:
            utils.Print('Signing original APK without modifying dex files',
                        quiet=quiet)
            processed_apk = os.path.join(temp, 'processed.apk')
            shutil.copyfile(apk, processed_apk)
        signed_apk = sign(processed_apk, keystore, temp, quiet=quiet)
        aligned_apk = align(signed_apk, temp, quiet=quiet)
        utils.Print('Writing result to {}'.format(out), quiet=quiet)
        shutil.copyfile(aligned_apk, out)
        if install:
            adb_cmd = ['adb']
            if adb_options:
                adb_cmd.extend(
                    [option for option in adb_options.split(' ') if option])
            adb_cmd.extend(['install', '-t', '-r', '-d', out])
            utils.RunCmd(adb_cmd, quiet=quiet)
Beispiel #4
0
def RunMonkey(app, options, apk_dest):
  if not WaitForEmulator(options):
    return False

  UninstallApkOnEmulator(app, options)
  InstallApkOnEmulator(apk_dest, options)

  number_of_events_to_generate = options.monkey_events

  # Intentionally using a constant seed such that the monkey generates the same
  # event sequence for each shrinker.
  random_seed = 42

  cmd = ['adb', '-s', options.emulator_id, 'shell', 'monkey', '-p', app.id,
      '-s', str(random_seed), str(number_of_events_to_generate)]

  try:
    stdout = utils.RunCmd(
        cmd, quiet=options.quiet, logging=IsLoggingEnabledFor(app, options))
    succeeded = (
        'Events injected: {}'.format(number_of_events_to_generate) in stdout)
  except subprocess.CalledProcessError as e:
    succeeded = False

  UninstallApkOnEmulator(app, options)

  return succeeded
Beispiel #5
0
def RebuildAppWithShrinker(app, apk, apk_dest, proguard_config_file, shrinker,
                           min_sdk, compile_sdk, options, temp_dir):
    assert 'r8' in shrinker
    assert apk_dest.endswith('.apk')

    print('Rebuilding {} with {}'.format(app, shrinker))

    # Compile given APK with shrinker to temporary zip file.
    android_jar = utils.get_android_jar(compile_sdk)
    r8_jar = os.path.join(temp_dir,
                          'r8lib.jar' if IsMinifiedR8(shrinker) else 'r8.jar')
    zip_dest = apk_dest[:-4] + '.zip'

    # TODO(christofferqa): Entry point should be CompatProguard if the shrinker
    # is 'r8'.
    entry_point = 'com.android.tools.r8.R8'

    cmd = [
        'java', '-ea:com.android.tools.r8...', '-cp', r8_jar, entry_point,
        '--release', '--min-api',
        str(min_sdk), '--pg-conf', proguard_config_file, '--lib', android_jar,
        '--output', zip_dest, apk
    ]

    for android_optional_jar in utils.get_android_optional_jars(compile_sdk):
        cmd.append('--lib')
        cmd.append(android_optional_jar)

    utils.RunCmd(cmd, quiet=options.quiet)

    # Make a copy of the given APK, move the newly generated dex files into the
    # copied APK, and then sign the APK.
    apk_masseur.masseur(apk, dex=zip_dest, out=apk_dest, quiet=options.quiet)
Beispiel #6
0
def run_instrumented(app_id,
                     test_id,
                     emulator_id,
                     apk,
                     test_apk,
                     quiet,
                     enable_logging,
                     test_runner='androidx.test.runner.AndroidJUnitRunner'):
    if not wait_for_emulator(emulator_id):
        return None

    install_apk_on_emulator(apk, emulator_id, quiet)
    install_apk_on_emulator(test_apk, emulator_id, quiet)

    cmd = [
        'adb', '-s', emulator_id, 'shell', 'am', 'instrument', '-w',
        '{}/{}'.format(test_id, test_runner)
    ]

    try:
        stdout = utils.RunCmd(cmd, quiet=quiet, logging=enable_logging)
        # The runner will print OK (X tests) if completed succesfully
        succeeded = any("OK (" in s for s in stdout)
    except subprocess.CalledProcessError as e:
        succeeded = False

    uninstall_apk_on_emulator(test_id, emulator_id)
    uninstall_apk_on_emulator(app_id, emulator_id)

    return succeeded
Beispiel #7
0
def run_monkey(app_id, emulator_id, apk, monkey_events, quiet, enable_logging):
    if not wait_for_emulator(emulator_id):
        return False

    install_apk_on_emulator(apk, emulator_id, quiet)

    # Intentionally using a constant seed such that the monkey generates the same
    # event sequence for each shrinker.
    random_seed = 42

    cmd = [
        'adb', '-s', emulator_id, 'shell', 'monkey', '-p', app_id, '-s',
        str(random_seed),
        str(monkey_events)
    ]

    try:
        stdout = utils.RunCmd(cmd, quiet=quiet, logging=enable_logging)
        succeeded = ('Events injected: {}'.format(monkey_events) in stdout)
    except subprocess.CalledProcessError as e:
        succeeded = False

    uninstall_apk_on_emulator(app_id, emulator_id)

    return succeeded
Beispiel #8
0
def align(signed_apk, temp, quiet):
    utils.Print('Aligning', quiet=quiet)
    aligned_apk = os.path.join(temp, 'aligned.apk')
    zipalign_path = ('zipalign' if 'build_tools' in os.environ.get('PATH') else
                     os.path.join(utils.getAndroidBuildTools(), 'zipalign'))
    cmd = [zipalign_path, '-f', '4', signed_apk, aligned_apk]
    utils.RunCmd(cmd, quiet=quiet)
    return signed_apk
Beispiel #9
0
def sign_with_apksigner(unsigned_apk,
                        signed_apk,
                        keystore,
                        password='******',
                        quiet=False):
    cmd = [
        os.path.join(utils.getAndroidBuildTools(), 'apksigner'), 'sign', '-v',
        '--ks', keystore, '--ks-pass', 'pass:'******'--min-sdk-version',
        '19', '--out', signed_apk, unsigned_apk
    ]
    utils.RunCmd(cmd, quiet=quiet)
Beispiel #10
0
def repack(processed_out, original_apk, temp, quiet):
    processed_apk = os.path.join(temp, 'processed.apk')
    shutil.copyfile(original_apk, processed_apk)
    if not processed_out:
        utils.Print('Using original APK as is', quiet=quiet)
        return processed_apk
    utils.Print('Repacking APK with dex files from {}'.format(processed_apk),
                quiet=quiet)
    with utils.ChangedWorkingDirectory(temp, quiet=quiet):
        cmd = ['zip', '-d', 'processed.apk', '*.dex']
        utils.RunCmd(cmd, quiet=quiet)
    if processed_out.endswith('.zip') or processed_out.endswith('.jar'):
        cmd = ['unzip', processed_out, '-d', temp]
        if quiet:
            cmd.insert(1, '-q')
        utils.RunCmd(cmd, quiet=quiet)
        processed_out = temp
    with utils.ChangedWorkingDirectory(processed_out, quiet=quiet):
        dex = glob.glob('*.dex')
        cmd = ['zip', '-u', '-9', processed_apk] + dex
        utils.RunCmd(cmd, quiet=quiet)
    return processed_apk
Beispiel #11
0
def main(argv):
    options = ParseOptions(argv)
    rebase_args = ['--rebase'] if options.rebase else []
    with utils.ChangedWorkingDirectory(REPO_ROOT, quiet=True):
        branches = [
            parse(line)
            for line in utils.RunCmd(['git', 'branch', '-vv'], quiet=True)
        ]

        current_branch = None
        for branch in branches:
            if branch.is_current:
                current_branch = branch
                break
        assert current_branch is not None

        if current_branch.upstream == None:
            print('Nothing to sync')
            return

        stack = []
        while current_branch:
            stack.append(current_branch)
            if current_branch.upstream is None or current_branch.upstream == 'master':
                break
            current_branch = get_branch_with_name(current_branch.upstream,
                                                  branches)

        while len(stack) > 0:
            branch = stack.pop()
            print('Syncing ' + branch.name)
            utils.RunCmd(['git', 'checkout', branch.name], quiet=True)
            utils.RunCmd(['git', 'pull'] + rebase_args, quiet=True)
            if options.upload:
                utils.RunCmd(['git', 'cl', 'upload', '-m', options.message],
                             quiet=True)

        utils.RunCmd(['git', 'cl', 'issue'])
Beispiel #12
0
def RebuildAppWithShrinker(
    app, apk, apk_dest, proguard_config_file, shrinker, min_sdk, compile_sdk,
    options, temp_dir, main_dex_rules):
  assert 'r8' in shrinker
  assert apk_dest.endswith('.apk')

  print('Rebuilding {} with {}'.format(app.name, shrinker))

  # Compile given APK with shrinker to temporary zip file.
  android_jar = utils.get_android_jar(compile_sdk)
  r8_jar = os.path.join(
      temp_dir, 'r8lib.jar' if IsMinifiedR8(shrinker) else 'r8.jar')
  zip_dest = apk_dest[:-4] + '.zip'

  # TODO(christofferqa): Entry point should be CompatProguard if the shrinker
  # is 'r8'.
  entry_point = 'com.android.tools.r8.R8'

  cmd = ([jdk.GetJavaExecutable()] +
         (['-ea:com.android.tools.r8...']
          if not options.disable_assertions
          else []) +
         ['-cp', r8_jar, entry_point,
         '--release', '--min-api', str(min_sdk),
         '--pg-conf', proguard_config_file,
         '--lib', android_jar,
         '--output', zip_dest,
         apk] +
         (['--no-desugaring']
         if app.legacy_desugaring or app.compatibility == '1.7'
         else []))

  for android_optional_jar in utils.get_android_optional_jars(compile_sdk):
    cmd.append('--lib')
    cmd.append(android_optional_jar)

  if main_dex_rules:
    cmd.append('--main-dex-rules')
    cmd.append(main_dex_rules)

  utils.RunCmd(
    cmd, quiet=options.quiet, logging=IsLoggingEnabledFor(app, options))

  # Make a copy of the given APK, move the newly generated dex files into the
  # copied APK, and then sign the APK.
  apk_masseur.masseur(
      apk, dex=zip_dest, resources='META-INF/services/*', out=apk_dest,
      quiet=options.quiet, logging=IsLoggingEnabledFor(app, options),
      keystore=options.keystore)
Beispiel #13
0
def main(argv):
    options = ParseOptions(argv)
    with utils.ChangedWorkingDirectory(REPO_ROOT, quiet=True):
        branches = [
            parse(line)
            for line in utils.RunCmd(['git', 'branch', '-vv'], quiet=True)
        ]

        current_branch = None
        for branch in branches:
            if branch.is_current:
                current_branch = branch
                break
        assert current_branch is not None

        if current_branch.upstream == None:
            print('Nothing to sync')
            return

        stack = []
        while current_branch:
            stack.append(current_branch)
            if current_branch.upstream is None:
                break
            current_branch = get_branch_with_name(current_branch.upstream,
                                                  branches)

        closed_branches = []
        has_seen_local_branch = False  # A branch that is not uploaded.
        has_seen_open_branch = False  # A branch that is not closed.
        while len(stack) > 0:
            branch = stack.pop()

            utils.RunCmd(['git', 'checkout', branch.name], quiet=True)

            status = get_status_for_current_branch()
            print('Syncing %s (status: %s)' % (branch.name, status))

            pull_for_current_branch(branch, options)

            if branch.name == 'master':
                continue

            if status == 'closed':
                assert not has_seen_local_branch, (
                    'Unexpected closed branch %s after new branch' %
                    branch.name)
                assert not has_seen_open_branch, (
                    'Unexpected closed branch %s after open branch' %
                    branch.name)
                closed_branches.append(branch.name)
                continue

            if not options.leave_upstream:
                if not has_seen_open_branch and len(closed_branches) > 0:
                    print(
                        'Setting upstream for first open branch %s to master' %
                        branch.name)
                    set_upstream_for_current_branch_to_master()

            has_seen_open_branch = True
            has_seen_local_branch = has_seen_local_branch or (status == 'None')

            if options.upload and status != 'closed':
                if has_seen_local_branch:
                    print(
                        'Cannot upload branch %s since it comes after a local branch'
                        % branch.name)
                else:
                    utils.RunCmd(
                        ['git', 'cl', 'upload', '-m', options.message],
                        quiet=True)

        if get_delete_branches_option(closed_branches, options):
            delete_branches(closed_branches)

        utils.RunCmd(['git', 'cl', 'issue'])
Beispiel #14
0
def pull_for_current_branch(branch, options):
    if branch.name == 'master' and options.skip_master:
        return
    rebase_args = ['--rebase'] if options.rebase else []
    utils.RunCmd(['git', 'pull'] + rebase_args, quiet=True)
Beispiel #15
0
def get_status_for_current_branch():
    return utils.RunCmd(['git', 'cl', 'status', '--field', 'status'],
                        quiet=True)[0].strip()
Beispiel #16
0
def delete_branches(branches):
    assert len(branches) > 0
    cmd = ['git', 'branch', '-D']
    cmd.extend(branches)
    utils.RunCmd(cmd, quiet=True)
Beispiel #17
0
def set_upstream_for_current_branch_to_master():
    utils.RunCmd(['git', 'cl', 'upstream', 'master'], quiet=True)
Beispiel #18
0
def BuildAppWithShrinker(app,
                         config,
                         shrinker,
                         checkout_dir,
                         out_dir,
                         temp_dir,
                         options,
                         keepRuleSynthesisForRecompilation=False):
    print('Building {} with {}{}'.format(
        app, shrinker,
        ' for recompilation' if keepRuleSynthesisForRecompilation else ''))

    # Add/remove 'r8.jar' from top-level build.gradle.
    if options.disable_tot:
        as_utils.remove_r8_dependency(checkout_dir)
    else:
        as_utils.add_r8_dependency(checkout_dir, temp_dir,
                                   IsMinifiedR8(shrinker))

    app_module = config.get('app_module', 'app')
    archives_base_name = config.get('archives_base_name', app_module)
    flavor = config.get('flavor')

    if not os.path.exists(out_dir):
        os.makedirs(out_dir)

    # Set -printconfiguration in Proguard rules.
    proguard_config_dest = os.path.abspath(
        os.path.join(out_dir, 'proguard-rules.pro'))
    as_utils.SetPrintConfigurationDirective(app, config, checkout_dir,
                                            proguard_config_dest)

    env = {}
    env['ANDROID_HOME'] = utils.ANDROID_HOME
    env['JAVA_OPTS'] = '-ea:com.android.tools.r8...'

    releaseTarget = config.get('releaseTarget')
    if not releaseTarget:
        releaseTarget = app_module + ':' + 'assemble' + (
            flavor.capitalize() if flavor else '') + 'Release'

    # Value for property android.enableR8.
    enableR8 = 'r8' in shrinker
    # Value for property android.enableR8.fullMode.
    enableR8FullMode = shrinker == 'r8full' or shrinker == 'r8full-minified'
    # Build gradlew command line.
    cmd = [
        './gradlew', '--no-daemon', 'clean', releaseTarget, '--profile',
        '--stacktrace', '-Pandroid.enableR8=' + str(enableR8).lower(),
        '-Pandroid.enableR8.fullMode=' + str(enableR8FullMode).lower()
    ]
    if keepRuleSynthesisForRecompilation:
        cmd.append(
            '-Dcom.android.tools.r8.keepRuleSynthesisForRecompilation=true')
    if options.gradle_flags:
        cmd.extend(options.gradle_flags.split(' '))

    stdout = utils.RunCmd(cmd, env, quiet=options.quiet)

    apk_base_name = (archives_base_name + (('-' + flavor) if flavor else '') +
                     '-release')
    signed_apk_name = config.get('signed-apk-name', apk_base_name + '.apk')
    unsigned_apk_name = apk_base_name + '-unsigned.apk'

    build_dir = config.get('build_dir', 'build')
    build_output_apks = os.path.join(app_module, build_dir, 'outputs', 'apk')
    if flavor:
        build_output_apks = os.path.join(build_output_apks, flavor, 'release')
    else:
        build_output_apks = os.path.join(build_output_apks, 'release')

    signed_apk = os.path.join(build_output_apks, signed_apk_name)
    unsigned_apk = os.path.join(build_output_apks, unsigned_apk_name)

    if options.sign_apks and not os.path.isfile(signed_apk):
        assert os.path.isfile(unsigned_apk)
        if options.sign_apks:
            keystore = 'app.keystore'
            keystore_password = '******'
            apk_utils.sign_with_apksigner(utils.ANDROID_BUILD_TOOLS,
                                          unsigned_apk, signed_apk, keystore,
                                          keystore_password)

    if os.path.isfile(signed_apk):
        apk_dest = os.path.join(out_dir, signed_apk_name)
        as_utils.MoveFile(signed_apk, apk_dest, quiet=options.quiet)
    else:
        apk_dest = os.path.join(out_dir, unsigned_apk_name)
        as_utils.MoveFile(unsigned_apk, apk_dest, quiet=options.quiet)

    assert IsBuiltWithR8(apk_dest, temp_dir, options) == ('r8' in shrinker), (
        'Unexpected marker in generated APK for {}'.format(shrinker))

    profile_dest_dir = os.path.join(out_dir, 'profile')
    as_utils.MoveProfileReportTo(profile_dest_dir, stdout, quiet=options.quiet)

    return (apk_dest, profile_dest_dir, proguard_config_dest)