aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorJohn Cater <jcater@google.com>2023-09-07 13:43:09 -0700
committerCopybara-Service <copybara-worker@google.com>2023-09-07 13:43:41 -0700
commit91ca9f6952fab96fd54f52857f764fb7aa002552 (patch)
tree632b3e1827e65798d4e5d0ffdc9e213d8775faac /test
parent8c1dbff9cbc35f11a3ac1af909132f44809a406f (diff)
downloadbazelbuild-rules_android-91ca9f6952fab96fd54f52857f764fb7aa002552.tar.gz
Implement android_sdk_repository in Starlark.
Fixes https://github.com/bazelbuild/rules_android/issues/76. PiperOrigin-RevId: 563532977 Change-Id: I1a17d21984429bf426c9ff3f8ba7cdab337c8529
Diffstat (limited to 'test')
-rw-r--r--test/rules/android_sdk_repository/BUILD75
-rw-r--r--test/rules/android_sdk_repository/android_helper.sh253
-rwxr-xr-xtest/rules/android_sdk_repository/android_sdk_repository_platforms_test.sh37
-rwxr-xr-xtest/rules/android_sdk_repository/android_sdk_repository_test.sh37
-rw-r--r--test/rules/android_sdk_repository/test_lib.sh178
5 files changed, 580 insertions, 0 deletions
diff --git a/test/rules/android_sdk_repository/BUILD b/test/rules/android_sdk_repository/BUILD
new file mode 100644
index 0000000..72cabf9
--- /dev/null
+++ b/test/rules/android_sdk_repository/BUILD
@@ -0,0 +1,75 @@
+# Description:
+# Tests for android_sdk_repository.
+
+load("@bazel_binaries//:defs.bzl", "bazel_binaries")
+load(
+ "@rules_bazel_integration_test//bazel_integration_test:defs.bzl",
+ "script_test",
+)
+
+package(
+ default_applicable_licenses = ["//:license"],
+ default_visibility = [
+ "//test:__subpackages__",
+ ],
+)
+
+licenses(["notice"])
+
+sh_library(
+ name = "android_helper",
+ testonly = True,
+ srcs = ["android_helper.sh"],
+ data = [
+ "//rules/android_sdk_repository:bzl",
+ ],
+ visibility = ["//visibility:private"],
+ deps = [
+ "//test/bashunit",
+ "@bazel_tools//tools/bash/runfiles",
+ ],
+)
+
+sh_library(
+ name = "test_lib",
+ testonly = True,
+ srcs = ["test_lib.sh"],
+ visibility = ["//visibility:private"],
+ deps = [
+ ":android_helper",
+ "//test/bashunit",
+ "@bazel_tools//tools/bash/runfiles",
+ ],
+)
+
+script_test(
+ name = "android_sdk_repository_test",
+ srcs = ["android_sdk_repository_test.sh"],
+ bazel_binaries = bazel_binaries,
+ bazel_version = bazel_binaries.versions.current,
+ deps = [
+ ":test_lib",
+ "//test/bashunit",
+ "@bazel_tools//tools/bash/runfiles",
+ ],
+)
+
+script_test(
+ name = "android_sdk_repository_platforms_test",
+ srcs = ["android_sdk_repository_platforms_test.sh"],
+ bazel_binaries = bazel_binaries,
+ bazel_version = bazel_binaries.versions.current,
+ deps = [
+ ":test_lib",
+ "//test/bashunit",
+ "@bazel_tools//tools/bash/runfiles",
+ ],
+)
+
+test_suite(
+ name = "integration_tests",
+ tests = [
+ ":android_sdk_repository_platforms_test",
+ ":android_sdk_repository_test",
+ ],
+)
diff --git a/test/rules/android_sdk_repository/android_helper.sh b/test/rules/android_sdk_repository/android_helper.sh
new file mode 100644
index 0000000..63a817f
--- /dev/null
+++ b/test/rules/android_sdk_repository/android_helper.sh
@@ -0,0 +1,253 @@
+#!/bin/bash
+#
+# Copyright 2023 The Bazel Authors. All rights reserved.
+#
+# 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.
+
+# --- begin runfiles.bash initialization v2 ---
+# Copy-pasted from the Bazel Bash runfiles library v2.
+set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash
+source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \
+ source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \
+ source "$0.runfiles/$f" 2>/dev/null || \
+ source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
+ source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
+ { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
+# --- end runfiles.bash initialization v2 ---
+
+source "$(rlocation build_bazel_rules_android/test/bashunit/unittest.bash)" || \
+ (echo >&2 "Failed to locate bashunit.sh" && exit 1)
+
+function set_up() {
+ # Clean out the repository.
+ rm -rf *
+ touch WORKSPACE
+
+ # Clean out the test android SDK if any
+ rm -rf "${TEST_TMPDIR}/android_sdk"
+
+ # Make sure the rules exist and seed the WORKSPACE.
+ rm -rf "${TEST_TMPDIR}/android_sdk_repository_src"
+ set_up_rules
+}
+
+function set_up_rules() {
+ local android_revision_rule="$(rlocation build_bazel_rules_android/rules/android_revision.bzl)"
+
+ local repo_rule="$(rlocation build_bazel_rules_android/rules/android_sdk_repository/rule.bzl)"
+ local repo_rule_dir="$(dirname "${repo_rule}")"
+ local dest_dir="${TEST_TMPDIR}/android_sdk_repository_src"
+ mkdir -p "${dest_dir}/rules/android_sdk_repository"
+ cp -r "${repo_rule_dir}"/* "${dest_dir}/rules/android_sdk_repository"
+ cat > "${dest_dir}/WORKSPACE" <<EOF
+workspace(name = "android_sdk_repository_src")
+EOF
+ cat > "${dest_dir}/rules/BUILD" <<EOF
+exports_files(["*.bzl"])
+EOF
+ cp "${android_revision_rule}" "${dest_dir}/rules/"
+ cat > "${dest_dir}/rules/android_sdk_repository/BUILD" <<EOF
+exports_files(["*.bzl"])
+EOF
+
+ cat >> WORKSPACE <<EOF
+local_repository(
+ name = "android_sdk_repository_src",
+ path = "${dest_dir}",
+)
+load("@android_sdk_repository_src//rules/android_sdk_repository:rule.bzl", "android_sdk_repository")
+EOF
+}
+
+function create_android_sdk() {
+ # Create a fake Android SDK that will be available for the repository rule.
+ local location="${TEST_TMPDIR}/android_sdk"
+ mkdir -p "${location}"
+
+ mkdir "${location}/platform-tools"
+ touch "${location}/platform-tools/adb"
+
+ echo "${location}"
+}
+
+function add_platforms() {
+ local sdk_path="${1}"
+ shift
+
+ # Add all requested API levels
+ for level in "$@"; do
+ local dir="${sdk_path}/platforms/android-${level}"
+ mkdir -p "${dir}"
+ touch "${dir}/android.jar"
+ touch "${dir}/framework.aidl"
+
+ local system_image_dir="${sdk_path}/system-images/android-${level}"
+ mkdir -p "${system_image_dir}/default/arm64-v8a"
+ mkdir -p "${system_image_dir}/default/x86_64"
+ done
+}
+
+function add_build_tools() {
+ local sdk_path="${1}"
+ shift
+
+ # Add all requested tools
+ for version in "$@"; do
+ local dir="${sdk_path}/build-tools/${version}"
+ mkdir -p "${dir}/lib"
+ touch "${dir}/aapt"
+ touch "${dir}/aidl"
+ touch "${dir}/lib/apksigner.jar"
+ touch "${dir}/lib/d8.jar"
+ touch "${dir}/lib/dx.jar"
+ touch "${dir}/mainDexClasses.rules"
+ touch "${dir}/zipalign"
+ done
+}
+
+function create_android_sdk_basic() {
+ local sdk_path="$(create_android_sdk)"
+ add_platforms "${sdk_path}" 31
+ add_build_tools "${sdk_path}" 30.0.3
+ echo "${sdk_path}"
+}
+
+function create_verify() {
+ mkdir verify
+ cat > verify/BUILD <<EOF
+genrule(
+ name = "check_sdk",
+ outs = ["check_sdk.log"],
+ cmd = select({
+ "@androidsdk//:has_androidsdk": "echo sdk present > \$@",
+ "//conditions:default": "echo sdk missing > \$@",
+ }),
+)
+EOF
+
+ "${BIT_BAZEL_BINARY}" build //verify:check_sdk >& $TEST_log || fail "Expected success"
+ cat bazel-bin/verify/check_sdk.log >$TEST_log
+}
+
+function verify_no_android_sdk() {
+ create_verify
+ expect_log "sdk missing"
+}
+
+function verify_android_sdk() {
+ create_verify
+ expect_log "sdk present"
+ "${BIT_BAZEL_BINARY}" query @androidsdk//:files >& $TEST_log || fail "Expected to exist"
+}
+
+function write_platforms() {
+ mkdir -p platforms
+ cat > platforms/BUILD <<EOF
+platform(
+ name = "arm64-v8a",
+ constraint_values = [
+ "@platforms//os:android",
+ "@platforms//cpu:arm64",
+ ],
+)
+EOF
+}
+
+function write_android_sdk_provider() {
+ mkdir -p sdk_check
+ cat > sdk_check/check.bzl <<EOF
+def _find_api_level(android_jar):
+ # expected format: external/androidsdk/platforms/android-LEVEL/android.jar
+ if not android_jar.startswith("external/androidsdk/platforms/android-"):
+ return "unknown"
+ if not android_jar.endswith("/android.jar"):
+ return "unknown"
+ level = android_jar.removeprefix("external/androidsdk/platforms/android-")
+ level = level.removesuffix("/android.jar")
+ return level
+def _show_sdk_info_impl(ctx):
+ print("SDK check results:")
+ provider = ctx.attr._android_sdk[AndroidSdkInfo]
+ print("build_tools_version: %s" % provider.build_tools_version)
+ print("api_level: %s" % _find_api_level(provider.android_jar.path))
+show_sdk_info = rule(
+ implementation = _show_sdk_info_impl,
+ attrs = {
+ "_android_sdk": attr.label(default = "@androidsdk//:sdk"),
+ },
+)
+EOF
+}
+
+function write_android_sdk_provider_platforms() {
+ mkdir -p sdk_check
+ cat > sdk_check/check.bzl <<EOF
+def _find_api_level(android_jar):
+ # expected format: external/androidsdk/platforms/android-LEVEL/android.jar
+ if not android_jar.startswith("external/androidsdk/platforms/android-"):
+ return "unknown"
+ if not android_jar.endswith("/android.jar"):
+ return "unknown"
+ level = android_jar.removeprefix("external/androidsdk/platforms/android-")
+ level = level.removesuffix("/android.jar")
+ return level
+def _show_sdk_info_impl(ctx):
+ print("SDK check results:")
+ toolchain = ctx.toolchains["@bazel_tools//tools/android:sdk_toolchain_type"]
+ if not toolchain:
+ print("No SDK found via toolchain")
+ return
+ provider = toolchain.android_sdk_info
+ print("build_tools_version: %s" % provider.build_tools_version)
+ print("api_level: %s" % _find_api_level(provider.android_jar.path))
+show_sdk_info = rule(
+ implementation = _show_sdk_info_impl,
+ toolchains = [
+ config_common.toolchain_type(
+ "@bazel_tools//tools/android:sdk_toolchain_type",
+ mandatory = False,
+ ),
+ ],
+)
+EOF
+}
+
+function check_android_sdk_provider() {
+ local extra_args=("$@")
+
+ if [[ ${ENABLE_PLATFORMS:-false} == "true" ]]; then
+ write_platforms
+ write_android_sdk_provider_platforms
+ extra_args+=(
+ "--incompatible_enable_android_toolchain_resolution"
+ "--platforms=//platforms:arm64-v8a"
+ )
+ else
+ write_android_sdk_provider
+ fi
+
+ cat > sdk_check/BUILD <<EOF
+load(":check.bzl", "show_sdk_info")
+show_sdk_info(
+ name = "check",
+)
+EOF
+
+ "${BIT_BAZEL_BINARY}" \
+ build \
+ --experimental_google_legacy_api \
+ "${extra_args[@]}" \
+ -- \
+ //sdk_check:check >& $TEST_log || fail "Expected success"
+ expect_log "SDK check results"
+}
diff --git a/test/rules/android_sdk_repository/android_sdk_repository_platforms_test.sh b/test/rules/android_sdk_repository/android_sdk_repository_platforms_test.sh
new file mode 100755
index 0000000..20eaf84
--- /dev/null
+++ b/test/rules/android_sdk_repository/android_sdk_repository_platforms_test.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+#
+# Copyright 2023 The Bazel Authors. All rights reserved.
+#
+# 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.
+
+# --- begin runfiles.bash initialization v2 ---
+# Copy-pasted from the Bazel Bash runfiles library v2.
+set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash
+source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \
+ source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \
+ source "$0.runfiles/$f" 2>/dev/null || \
+ source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
+ source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
+ { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
+# --- end runfiles.bash initialization v2 ---
+
+source "$(rlocation build_bazel_rules_android/test/bashunit/unittest.bash)" || \
+ (echo >&2 "Failed to locate bashunit.sh" && exit 1)
+
+ENABLE_PLATFORMS=true
+
+# Source and run the actual tests from test_lib.sh
+source "$(rlocation build_bazel_rules_android/test/rules/android_sdk_repository/test_lib.sh)" || \
+ (echo >&2 "Failed to locate test_lib.sh" && exit 1)
+
+run_suite "Android integration tests for SDK (with platforms)"
diff --git a/test/rules/android_sdk_repository/android_sdk_repository_test.sh b/test/rules/android_sdk_repository/android_sdk_repository_test.sh
new file mode 100755
index 0000000..53d6573
--- /dev/null
+++ b/test/rules/android_sdk_repository/android_sdk_repository_test.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+#
+# Copyright 2023 The Bazel Authors. All rights reserved.
+#
+# 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.
+
+# --- begin runfiles.bash initialization v2 ---
+# Copy-pasted from the Bazel Bash runfiles library v2.
+set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash
+source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \
+ source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \
+ source "$0.runfiles/$f" 2>/dev/null || \
+ source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
+ source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
+ { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
+# --- end runfiles.bash initialization v2 ---
+
+source "$(rlocation build_bazel_rules_android/test/bashunit/unittest.bash)" || \
+ (echo >&2 "Failed to locate bashunit.sh" && exit 1)
+
+ENABLE_PLATFORMS=false
+
+# Source and run the actual tests from test_lib.sh
+source "$(rlocation build_bazel_rules_android/test/rules/android_sdk_repository/test_lib.sh)" || \
+ (echo >&2 "Failed to locate test_lib.sh" && exit 1)
+
+run_suite "Android integration tests for SDK"
diff --git a/test/rules/android_sdk_repository/test_lib.sh b/test/rules/android_sdk_repository/test_lib.sh
new file mode 100644
index 0000000..f6a53af
--- /dev/null
+++ b/test/rules/android_sdk_repository/test_lib.sh
@@ -0,0 +1,178 @@
+#!/bin/bash
+#
+# Copyright 2023 The Bazel Authors. All rights reserved.
+#
+# 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.
+
+# --- begin runfiles.bash initialization v2 ---
+# Copy-pasted from the Bazel Bash runfiles library v2.
+set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash
+source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \
+ source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \
+ source "$0.runfiles/$f" 2>/dev/null || \
+ source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
+ source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
+ { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
+# --- end runfiles.bash initialization v2 ---
+
+source "$(rlocation build_bazel_rules_android/test/bashunit/unittest.bash)" || \
+ (echo >&2 "Failed to locate bashunit.sh" && exit 1)
+
+source "$(rlocation build_bazel_rules_android/test/rules/android_sdk_repository/android_helper.sh)" || \
+ (echo >&2 "Failed to locate android_helper.sh" && exit 1)
+
+# Actual tests for Android Sdk Repository
+
+# Check that the empty BUILD file was created.
+function test_android_sdk_repository_no_path_or_android_home() {
+ cat >> WORKSPACE <<EOF
+android_sdk_repository(
+ name = "androidsdk",
+)
+EOF
+
+ verify_no_android_sdk
+ "${BIT_BAZEL_BINARY}" build @androidsdk//:files >& $TEST_log && fail "Should have failed" || true
+ expect_log "Either the path attribute of android_sdk_repository"
+}
+
+function test_android_sdk_repository_path_from_attribute() {
+ # Create android SDK
+ local sdk_path="$(create_android_sdk_basic)"
+
+ # Add to repository.
+ cat >> WORKSPACE <<EOF
+android_sdk_repository(
+ name = "androidsdk",
+ path = "${sdk_path}",
+)
+EOF
+
+ # Verify the SDK is created correctly.
+ verify_android_sdk
+}
+
+function test_android_sdk_repository_path_from_environment() {
+ # Create android SDK
+ local sdk_path="$(create_android_sdk_basic)"
+
+ # Add to repository.
+ cat >> WORKSPACE <<EOF
+android_sdk_repository(
+ name = "androidsdk",
+)
+EOF
+
+ export ANDROID_HOME="${sdk_path}"
+ # Verify the SDK is created correctly.
+ verify_android_sdk
+}
+
+function test_android_sdk_repository_fails_invalid_path() {
+ # Create an empty SDK directory.
+ mkdir -p "$TEST_TMPDIR/android_sdk"
+
+ # Add to repository with the invalid path
+ cat >> WORKSPACE <<EOF
+android_sdk_repository(
+ name = "androidsdk",
+ path = "$TEST_TMPDIR/android_sdk",
+)
+EOF
+
+ "${BIT_BAZEL_BINARY}" query @androidsdk//:files >& $TEST_log && fail "Should have failed" || true
+ expect_log "No Android SDK apis found in the Android SDK"
+}
+
+function test_build_tools_largest() {
+ # create several build tools
+ local sdk_path="$(create_android_sdk)"
+ add_platforms "${sdk_path}" 31
+ add_build_tools "${sdk_path}" 10.1.2 20.2.3 30.3.4
+
+ # Add to repository.
+ cat >> WORKSPACE <<EOF
+android_sdk_repository(
+ name = "androidsdk",
+ path = "${sdk_path}",
+)
+EOF
+
+ check_android_sdk_provider
+ expect_log "build_tools_version: 30.3.4"
+}
+
+function test_api_level_default() {
+ if [[ "${ENABLE_PLATFORMS:-false}" == "true" ]]; then
+ # TODO(katre): Fix API selection with platforms.
+ return
+ fi
+ # create several api levels
+ local sdk_path="$(create_android_sdk)"
+ add_platforms "${sdk_path}" 31 23 45
+ add_build_tools "${sdk_path}" 30.3.4
+
+ # Add to repository.
+ cat >> WORKSPACE <<EOF
+android_sdk_repository(
+ name = "androidsdk",
+ path = "${sdk_path}",
+)
+EOF
+
+ # Should be the largest API level available
+ check_android_sdk_provider
+ expect_log "api_level: 45"
+}
+
+function test_api_level_specific() {
+ if [[ "${ENABLE_PLATFORMS:-false}" == "true" ]]; then
+ # TODO(katre): Fix API selection with platforms.
+ return
+ fi
+ # create several api levels
+ local sdk_path="$(create_android_sdk)"
+ add_platforms "${sdk_path}" 31 23 45
+ add_build_tools "${sdk_path}" 30.3.4
+
+ # Add to repository.
+ cat >> WORKSPACE <<EOF
+android_sdk_repository(
+ name = "androidsdk",
+ path = "${sdk_path}",
+ api_level = 31,
+)
+EOF
+
+ check_android_sdk_provider
+ expect_log "api_level: 31"
+}
+
+function test_api_level_specific_missing() {
+ # create several api levels
+ local sdk_path="$(create_android_sdk)"
+ add_platforms "${sdk_path}" 31 23 45
+ add_build_tools "${sdk_path}" 30.3.4
+
+ # Add to repository.
+ cat >> WORKSPACE <<EOF
+android_sdk_repository(
+ name = "androidsdk",
+ path = "${sdk_path}",
+ api_level = 30,
+)
+EOF
+
+ "${BIT_BAZEL_BINARY}" query @androidsdk//:files >& $TEST_log && fail "Should have failed" || true
+ expect_log "Android SDK api level 30 was requested but it is not installed"
+}