def _build_app(args, extra_args):
    """Builds an app or applet and returns the resulting executable ID
    (unless it was a dry-run, in which case None is returned).

    TODO: remote app builds still return None, but we should fix this.

    """

    if not args.remote:
        # LOCAL BUILD

        try:
            output = build_and_upload_locally(
                args.src_dir,
                args.mode,
                overwrite=args.overwrite,
                archive=args.archive,
                publish=args.publish,
                destination_override=args.destination,
                version_override=args.version_override,
                bill_to_override=args.bill_to,
                use_temp_build_project=args.use_temp_build_project,
                do_parallel_build=args.parallel_build,
                do_version_autonumbering=args.version_autonumbering,
                do_try_update=args.update,
                dx_toolkit_autodep=args.dx_toolkit_autodep,
                do_check_syntax=args.check_syntax,
                ensure_upload=args.ensure_upload,
                force_symlinks=args.force_symlinks,
                dry_run=args.dry_run,
                confirm=args.confirm,
                return_object_dump=args.json,
                region=args.region,
                **extra_args
                )

            if output is not None and args.run is None:
                print(json.dumps(output))
        except dxpy.app_builder.AppBuilderException as e:
            # AppBuilderException represents errors during app or applet building
            # that could reasonably have been anticipated by the user.
            print("Error: %s" % (e.args,), file=sys.stderr)
            sys.exit(3)
        except dxpy.exceptions.DXAPIError as e:
            print("Error: %s" % (e,), file=sys.stderr)
            sys.exit(3)

        if args.dry_run:
            return None

        return output['id']

    else:
        # REMOTE BUILD

        try:
            app_json = _parse_app_spec(args.src_dir)
            _check_suggestions(app_json, publish=args.publish)
            _verify_app_source_dir(args.src_dir, args.mode)
            if args.mode == "app" and not args.dry_run:
                dxpy.executable_builder.verify_developer_rights('app-' + app_json['name'])
        except dxpy.app_builder.AppBuilderException as e:
            print("Error: %s" % (e.args,), file=sys.stderr)
            sys.exit(3)

        # The following flags might be useful in conjunction with
        # --remote. To enable these, we need to learn how to pass these
        # options through to the interior call of dx_build_app(let).
        if args.dry_run:
            parser.error('--remote cannot be combined with --dry-run')
        if args.overwrite:
            parser.error('--remote cannot be combined with --overwrite/-f')
        if args.archive:
            parser.error('--remote cannot be combined with --archive/-a')

        # The following flags are probably not useful in conjunction
        # with --remote.
        if args.json:
            parser.error('--remote cannot be combined with --json')
        if not args.use_temp_build_project:
            parser.error('--remote cannot be combined with --no-temp-build-project')

        if isinstance(args.region, list) and len(args.region) > 1:
            parser.error('--region can only be specified once for remote builds')
        region = args.region[0] if args.region is not None else None

        more_kwargs = {}
        if args.version_override:
            more_kwargs['version_override'] = args.version_override
        if args.bill_to:
            more_kwargs['bill_to_override'] = args.bill_to
        if not args.version_autonumbering:
            more_kwargs['do_version_autonumbering'] = False
        if not args.update:
            more_kwargs['do_try_update'] = False
        if not args.parallel_build:
            more_kwargs['do_parallel_build'] = False
        if not args.check_syntax:
            more_kwargs['do_check_syntax'] = False

        return _build_app_remote(args.mode, args.src_dir, destination_override=args.destination,
                                 publish=args.publish, dx_toolkit_autodep=args.dx_toolkit_autodep,
                                 region=region, watch=args.watch, **more_kwargs)
Builds an app or applet and returns the resulting executable ID
    (unless it was a dry-run, in which case None is returned).

    TODO: remote app builds still return None, but we should fix this.