Skip to content

Commit

Permalink
Remove openssl dependency from android (#49282)
Browse files Browse the repository at this point in the history
  • Loading branch information
steveisok authored Mar 23, 2021
1 parent 3a144a2 commit bc5b227
Show file tree
Hide file tree
Showing 38 changed files with 76 additions and 144 deletions.
9 changes: 0 additions & 9 deletions docs/workflow/testing/libraries/testing-android.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,6 @@ mkdir ${ANDROID_SDK_ROOT} && unzip ~/asdk.zip -d ${ANDROID_SDK_ROOT}/cmdline-too
yes | ${ANDROID_SDK_ROOT}/cmdline-tools/tools/bin/sdkmanager --sdk_root=${ANDROID_SDK_ROOT} --licenses
${ANDROID_SDK_ROOT}/cmdline-tools/tools/bin/sdkmanager --sdk_root=${ANDROID_SDK_ROOT} "platform-tools" "platforms;android-${SDK_API_LEVEL}" "build-tools;${SDK_BUILD_TOOLS}"

# We also need to download precompiled binaries and headers for OpenSSL from maven, this step is a temporary hack
# and will be removed once we figure out how to integrate OpenSSL properly as a dependency
export ANDROID_OPENSSL_AAR=~/openssl-android
curl https://maven.google.com/com/android/ndk/thirdparty/openssl/${OPENSSL_VER}/openssl-${OPENSSL_VER}.aar -L --output ~/openssl.zip
unzip ~/openssl.zip -d ${ANDROID_OPENSSL_AAR} && rm -rf ~/openssl.zip
printf "\n\nexport ANDROID_NDK_ROOT=${ANDROID_NDK_ROOT}\nexport ANDROID_SDK_ROOT=${ANDROID_SDK_ROOT}\nexport ANDROID_OPENSSL_AAR=${ANDROID_OPENSSL_AAR}\n" >> ${BASHRC}
```
Save it to a file (e.g. `deps.sh`) and execute using `source` (e.g. `chmod +x deps.sh && source ./deps.sh`) in order to propogate the `ANDROID_NDK_ROOT`, `ANDROID_SDK_ROOT` and `ANDROID_OPENSSL_AAR` environment variables to the current process.

## Building Libs and Tests for Android

Now we're ready to build everything for Android:
Expand Down
13 changes: 1 addition & 12 deletions eng/install-native-dependencies.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,8 @@ elif [ "$1" = "OSX" ] || [ "$1" = "tvOS" ] || [ "$1" = "iOS" ]; then
if [ "$?" != "0" ]; then
exit 1;
fi
elif [ "$1" = "Android" ]; then
if [ -z "${ANDROID_OPENSSL_AAR}" ]; then
echo "The ANDROID_OPENSSL_AAR variable is not set. OpenSSL will not be installed."
exit 0;
fi
if [ -d "${ANDROID_OPENSSL_AAR}" ]; then
exit 0;
fi
OPENSSL_VER=1.1.1g-alpha-1
curl https://maven.google.com/com/android/ndk/thirdparty/openssl/${OPENSSL_VER}/openssl-${OPENSSL_VER}.aar -L --output /tmp/openssl.zip
unzip /tmp/openssl.zip -d "${ANDROID_OPENSSL_AAR}" && rm -rf /tmp/openssl.zip
else
echo "Must pass \"Linux\", \"Android\", \"tvOS\", \"iOS\" or \"OSX\" as first argument."
echo "Must pass \"Linux\", \"tvOS\", \"iOS\" or \"OSX\" as first argument."
exit 1
fi

6 changes: 1 addition & 5 deletions eng/pipelines/common/global-build-job.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,6 @@ jobs:
${{ if ne(parameters.enableRichCodeNavigation, true) }}:
value: ''

- ${{ if eq(parameters.osGroup, 'Android') }}:
- name: ANDROID_OPENSSL_AAR
value: /tmp/openssl-android

- ${{ each variable in parameters.variables }}:
- ${{ variable }}

Expand All @@ -108,7 +104,7 @@ jobs:
artifact: Mono_Offsets_${{monoCrossAOTTargetOS}}
path: '$(Build.SourcesDirectory)/artifacts/obj/mono/offsetfiles'

- ${{ if in(parameters.osGroup, 'OSX', 'iOS', 'tvOS', 'Android') }}:
- ${{ if in(parameters.osGroup, 'OSX', 'iOS', 'tvOS') }}:
- script: $(Build.SourcesDirectory)/eng/install-native-dependencies.sh ${{ parameters.osGroup }} ${{ parameters.archType }} azDO
displayName: Install Build Dependencies

Expand Down
40 changes: 12 additions & 28 deletions eng/pipelines/runtime-staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ jobs:
buildConfig: Release
runtimeFlavor: mono
platforms:
- Android_x86
- Android_x64
- iOSSimulator_x64
variables:
Expand Down Expand Up @@ -133,6 +134,17 @@ jobs:
eq(variables['monoContainsChange'], true),
eq(variables['isFullMatrix'], true))
- template: /eng/pipelines/common/platform-matrix.yml
parameters:
jobTemplate: /eng/pipelines/common/global-build-job.yml
buildConfig: Release
platforms:
- Android_arm
jobParameters:
testGroup: innerloop
nameSuffix: Build_Subset_Mono
buildArgs: -subset mono+libs

#
# Build the whole product using Mono and run libraries tests
#
Expand Down Expand Up @@ -252,34 +264,6 @@ jobs:
creator: dotnet-bot
testRunNamePrefixSuffix: Mono_$(_BuildConfig)

#
# Build Mono and Libraries for Android using Android native crypto instead of OpenSSL
#
- template: /eng/pipelines/common/platform-matrix.yml
parameters:
jobTemplate: /eng/pipelines/common/global-build-job.yml
buildConfig: Release
runtimeFlavor: mono
platforms:
- Android_x64
- Android_x86
- Android_arm64
variables:
# disable using OpenSSL
- name: ANDROID_OPENSSL_AAR
value: ''
# map dependencies variables to local variables
- name: librariesContainsChange
value: $[ dependencies.evaluate_paths.outputs['SetPathVars_libraries.containsChange'] ]
jobParameters:
nameSuffix: Libraries_Mono_AndroidCrypto
buildArgs: -s mono+libs -c $(_BuildConfig)
timeoutInMinutes: 180
condition: >-
or(
eq(dependencies.evaluate_paths.outputs['SetPathVars_libraries.containsChange'], true),
eq(variables['isFullMatrix'], true))
# Run disabled installer tests on Linux x64
- template: /eng/pipelines/common/platform-matrix.yml
parameters:
Expand Down
2 changes: 0 additions & 2 deletions eng/pipelines/runtime.yml
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,6 @@ jobs:
buildConfig: ${{ variables.debugOnPrReleaseOnRolling }}
runtimeFlavor: mono
platforms:
- Android_x86
- MacCatalyst_x64
- MacCatalyst_arm64
- tvOSSimulator_x64
Expand All @@ -246,7 +245,6 @@ jobs:
buildConfig: Release
runtimeFlavor: mono
platforms:
- Android_arm
- tvOS_arm64
- iOS_arm
- Linux_musl_x64
Expand Down
1 change: 1 addition & 0 deletions eng/testing/AndroidRunnerTemplate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ $HARNESS_RUNNER android test \
--package-name="net.dot.$ASSEMBLY_NAME" \
--app="$EXECUTION_DIR/bin/$TEST_NAME.apk" \
--output-directory="$XHARNESS_OUT" \
--timeout=1800 \
$EXPECTED_EXIT_CODE

_exitCode=$?
Expand Down
7 changes: 0 additions & 7 deletions eng/testing/tests.mobile.targets
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,6 @@
</AotInputAssemblies>
</ItemGroup>

<Copy Condition="'$(ANDROID_OPENSSL_AAR)' != ''"
SourceFiles="$(ANDROID_OPENSSL_AAR)\prefab\modules\crypto\libs\android.$(AndroidAbi)\libcrypto.so"
DestinationFolder="$(PublishDir)" SkipUnchangedFiles="true"/>
<Copy Condition="'$(ANDROID_OPENSSL_AAR)' != ''"
SourceFiles="$(ANDROID_OPENSSL_AAR)\prefab\modules\ssl\libs\android.$(AndroidAbi)\libssl.so"
DestinationFolder="$(PublishDir)" SkipUnchangedFiles="true"/>

<WriteLinesToFile File="$(PublishDir)xunit-excludes.txt" Lines="$(XunitExcludesTxtFileContent)" />

<MakeDir Directories="$(_MobileIntermediateOutputPath)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public static partial class PlatformDetection
public static bool IsMonoInterpreter => GetIsRunningOnMonoInterpreter();
public static bool IsFreeBSD => RuntimeInformation.IsOSPlatform(OSPlatform.Create("FREEBSD"));
public static bool IsNetBSD => RuntimeInformation.IsOSPlatform(OSPlatform.Create("NETBSD"));
public static bool IsAndroid => RuntimeInformation.IsOSPlatform(OSPlatform.Create("Android"));
public static bool IsAndroid => RuntimeInformation.IsOSPlatform(OSPlatform.Create("ANDROID"));
public static bool IsiOS => RuntimeInformation.IsOSPlatform(OSPlatform.Create("IOS"));
public static bool IstvOS => RuntimeInformation.IsOSPlatform(OSPlatform.Create("TVOS"));
public static bool IsMacCatalyst => RuntimeInformation.IsOSPlatform(OSPlatform.Create("MACCATALYST"));
Expand Down
15 changes: 2 additions & 13 deletions src/libraries/Native/Unix/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -254,13 +254,8 @@ elseif(CLR_CMAKE_TARGET_TVOS)
#add_subdirectory(System.Net.Security.Native) # no gssapi on tvOS, see https://developer.apple.com/documentation/gss
# System.Security.Cryptography.Native is intentionally disabled on tvOS
# it is only used for interacting with OpenSSL which isn't useful there
elseif(CLR_CMAKE_TARGET_ANDROID AND NOT CROSS_ROOTFS)
#add_subdirectory(System.Net.Security.Native) # TODO: reenable
if (NOT "$ENV{ANDROID_OPENSSL_AAR}" STREQUAL "")
message("Using Android OpenSSL")
set(PREFER_OPENSSL_ANDROID 1)
add_subdirectory(System.Security.Cryptography.Native)
endif()
elseif(CLR_CMAKE_TARGET_ANDROID)
add_subdirectory(System.Security.Cryptography.Native.Android)
else()
add_subdirectory(System.Globalization.Native)
add_subdirectory(System.Net.Security.Native)
Expand All @@ -270,9 +265,3 @@ endif()
if(CLR_CMAKE_TARGET_OSX OR CLR_CMAKE_TARGET_MACCATALYST OR CLR_CMAKE_TARGET_IOS OR CLR_CMAKE_TARGET_TVOS)
add_subdirectory(System.Security.Cryptography.Native.Apple)
endif()

# if ANDROID_OPENSSL_AAR is not set - use Android Native Crypto (it's going to replace openssl eventually)
if(CLR_CMAKE_TARGET_ANDROID AND NOT PREFER_OPENSSL_ANDROID)
message("Using Android Native Crypto")
add_subdirectory(System.Security.Cryptography.Native.Android)
endif()
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,9 @@ macro(append_extra_cryptography_libs NativeLibsExtra)
if(CMAKE_STATIC_LIB_LINK)
set(CMAKE_FIND_LIBRARY_SUFFIXES .a)
endif(CMAKE_STATIC_LIB_LINK)

if(CLR_CMAKE_TARGET_ANDROID AND NOT CROSS_ROOTFS)
# TEMP: consume OpenSSL dependencies from external sources via env. variables
set(OPENSSL_FOUND 1)
set(OPENSSL_INCLUDE_DIR $ENV{ANDROID_OPENSSL_AAR}/prefab/modules/ssl/include)
if(CLR_CMAKE_TARGET_ARCH_ARM64)
set(OPENSSL_CRYPTO_LIBRARY $ENV{ANDROID_OPENSSL_AAR}/prefab/modules/crypto/libs/android.arm64-v8a/libcrypto.so)
set(OPENSSL_SSL_LIBRARY $ENV{ANDROID_OPENSSL_AAR}/prefab/modules/ssl/libs/android.arm64-v8a/libssl.so)
elseif(CLR_CMAKE_TARGET_ARCH_ARM)
set(OPENSSL_CRYPTO_LIBRARY $ENV{ANDROID_OPENSSL_AAR}/prefab/modules/crypto/libs/android.armeabi-v7a/libcrypto.so)
set(OPENSSL_SSL_LIBRARY $ENV{ANDROID_OPENSSL_AAR}/prefab/modules/ssl/libs/android.armeabi-v7a/libssl.so)
elseif(CLR_CMAKE_TARGET_ARCH_I386)
set(OPENSSL_CRYPTO_LIBRARY $ENV{ANDROID_OPENSSL_AAR}/prefab/modules/crypto/libs/android.x86/libcrypto.so)
set(OPENSSL_SSL_LIBRARY $ENV{ANDROID_OPENSSL_AAR}/prefab/modules/ssl/libs/android.x86/libssl.so)
else()
set(OPENSSL_CRYPTO_LIBRARY $ENV{ANDROID_OPENSSL_AAR}/prefab/modules/crypto/libs/android.x86_64/libcrypto.so)
set(OPENSSL_SSL_LIBRARY $ENV{ANDROID_OPENSSL_AAR}/prefab/modules/ssl/libs/android.x86_64/libssl.so)
endif()
else()
find_package(OpenSSL)
endif()


find_package(OpenSSL)

if(NOT OPENSSL_FOUND)
message(FATAL_ERROR "!!! Cannot find libssl and System.Security.Cryptography.Native cannot build without it. Try installing libssl-dev (on Linux, but this may vary by distro) or openssl (on macOS) !!!. See the requirements document for your specific operating system: https://github.com/dotnet/runtime/tree/main/docs/workflow/requirements.")
endif(NOT OPENSSL_FOUND)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1984,6 +1984,7 @@ public void IStructuralEquatableEqualsNullComparerInvalid()

[Theory]
[MemberData(nameof(IStructuralEquatableGetHashCodeData))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/37069", TestPlatforms.Android)]
public void IStructuralEquatableGetHashCode(IEnumerable<int> source, IEqualityComparer comparer)
{
var array = source.ToImmutableArray();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public void Ctor_CultureInfo_Compare(object a, object b, int expected)
}

[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/37069", TestPlatforms.Android)]
public void Ctor_CultureInfo_Compare_TurkishI()
{
var cultureNames = Helpers.TestCultureNames;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ public void Ctor_CultureInfo_ChangeCurrentCulture_GetHashCodeCompare(object a, o
}

[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/37069", TestPlatforms.Android)]
public void Ctor_CultureInfo_GetHashCodeCompare_TurkishI()
{
var cultureNames = Helpers.TestCultureNames;
Expand Down Expand Up @@ -150,6 +151,7 @@ public void Default_GetHashCodeCompare(object a, object b, bool expected)
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/37069", TestPlatforms.Android)]
public void Default_Compare_TurkishI()
{
// Turkish has lower-case and upper-case version of the dotted "i", so the upper case of "i" (U+0069) isn't "I" (U+0049)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public static IEnumerable<object[]> DecompressFailsWithWrapperStream_MemberData(
/// <summary>Test to pass GZipStream data and ZLibStream data to a DeflateStream</summary>
[Theory]
[MemberData(nameof(DecompressFailsWithWrapperStream_MemberData))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/36845", TestPlatforms.Android)]
public async Task DecompressFailsWithWrapperStream(string uncompressedPath, string newDirectory, string newSuffix)
{
string fileName = Path.Combine(newDirectory, Path.GetFileName(uncompressedPath) + newSuffix);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public class NamedPipeTest_UnixDomainSockets
{
[Fact]
[PlatformSpecific(TestPlatforms.AnyUnix)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/49873", TestPlatforms.Android)]
public void NamedPipeServer_Connects_With_UnixDomainSocketEndPointClient()
{
string pipeName = Path.Combine("/tmp", "pipe-tests-corefx-" + Path.GetRandomFileName());
Expand All @@ -28,6 +29,7 @@ public void NamedPipeServer_Connects_With_UnixDomainSocketEndPointClient()

[Fact]
[PlatformSpecific(TestPlatforms.AnyUnix)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/49873", TestPlatforms.Android)]
public async Task NamedPipeClient_Connects_With_UnixDomainSocketEndPointServer()
{
string pipeName = Path.Combine("/tmp", "pipe-tests-corefx-" + Path.GetRandomFileName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@
<GeneratePlatformNotSupportedAssemblyMessage Condition="'$(TargetsAnyOS)' == 'true'">SR.SystemNetSecurity_PlatformNotSupported</GeneratePlatformNotSupportedAssemblyMessage>
</PropertyGroup>
<PropertyGroup>
<!-- Use Android native crypto when ANDROID_OPENSSL_AAR is not defined.
TODO: [AndroidCrypto] Remove ANDROID_OPENSSL_AAR check once Android native crypto is complete -->
<UseAndroidCrypto Condition="'$(TargetsAndroid)' == 'true' and '$(ANDROID_OPENSSL_AAR)' == ''">true</UseAndroidCrypto>
<UseAndroidCrypto Condition="'$(TargetsAndroid)' == 'true'">true</UseAndroidCrypto>
<UseAppleCrypto Condition="'$(TargetsOSX)' == 'true' or '$(TargetsiOS)' == 'true' or '$(TargetstvOS)' == 'true'">true</UseAppleCrypto>
</PropertyGroup>
<PropertyGroup Condition="'$(UseAndroidCrypto)' == 'true' or '$(UseAppleCrypto)' == 'true'">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,7 @@ public static void Uri_CachesDnsSafeHost()
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/49932", TestPlatforms.Android)]
public static void Uri_DoesNotLockOnString()
{
// Don't intern the string we lock on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public void RelativeSearchPath_Is_Null()
[Fact]
[PlatformSpecific(~TestPlatforms.Browser)] // throws pNSE
[ActiveIssue("https://github.com/dotnet/runtime/issues/49568", typeof(PlatformDetection), nameof(PlatformDetection.IsMacOsAppleSilicon))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/49868", TestPlatforms.Android)]
public void TargetFrameworkTest()
{
const int ExpectedExitCode = 0;
Expand Down Expand Up @@ -794,7 +795,7 @@ public static void GetPermissionSet()
}

[Theory]
[ActiveIssue("https://github.com/dotnet/runtime/issues/34030", TestPlatforms.Linux | TestPlatforms.Browser, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/34030", TestPlatforms.Linux | TestPlatforms.Browser | TestPlatforms.Android, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[MemberData(nameof(TestingCreateInstanceFromObjectHandleData))]
public static void TestingCreateInstanceFromObjectHandle(string physicalFileName, string assemblyFile, string type, string returnedFullNameType, Type exceptionType)
{
Expand Down Expand Up @@ -894,7 +895,7 @@ public static void TestingCreateInstanceObjectHandle(string assemblyName, string
};

[Theory]
[ActiveIssue("https://github.com/dotnet/runtime/issues/34030", TestPlatforms.Linux | TestPlatforms.Browser, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/34030", TestPlatforms.Linux | TestPlatforms.Browser | TestPlatforms.Android, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
[MemberData(nameof(TestingCreateInstanceFromObjectHandleFullSignatureData))]
public static void TestingCreateInstanceFromObjectHandleFullSignature(string physicalFileName, string assemblyFile, string type, bool ignoreCase, BindingFlags bindingAttr, Binder binder, object[] args, CultureInfo culture, object[] activationAttributes, string returnedFullNameType)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public static void ExitCode_Roundtrips(int exitCode)
[InlineData(3)] // using Exit(exitCode)
[PlatformSpecific(~TestPlatforms.Browser)] // throws PNSE
[ActiveIssue("https://github.com/dotnet/runtime/issues/49568", typeof(PlatformDetection), nameof(PlatformDetection.IsMacOsAppleSilicon))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/49868", TestPlatforms.Android)]
public static void ExitCode_VoidMainAppReturnsSetValue(int mode)
{
int expectedExitCode = 123;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ public void GetSystemDirectory()
[InlineData(Environment.SpecialFolder.MyMusic, Environment.SpecialFolderOption.DoNotVerify)]
[InlineData(Environment.SpecialFolder.MyPictures, Environment.SpecialFolderOption.DoNotVerify)]
[InlineData(Environment.SpecialFolder.Fonts, Environment.SpecialFolderOption.DoNotVerify)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/49868", TestPlatforms.Android)]
public void GetFolderPath_Unix_NonEmptyFolderPaths(Environment.SpecialFolder folder, Environment.SpecialFolderOption option)
{
Assert.NotEmpty(Environment.GetFolderPath(folder, option));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ public static void IsOSPlatformVersionAtLeast_InvalidArgs_Throws()
public static void TestIsOSVersionAtLeast_FreeBSD() => TestIsOSVersionAtLeast("FreeBSD");

[Fact, PlatformSpecific(TestPlatforms.Android)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/49868", TestPlatforms.Android)]
public static void TestIsOSPlatform_Android() => TestIsOSPlatform("Android", OperatingSystem.IsAndroid);

[Fact, PlatformSpecific(TestPlatforms.Android)]
Expand Down
Loading

0 comments on commit bc5b227

Please sign in to comment.