blob: 68acf080a886800e6ffe74fd427b2f51722c13df [file] [log] [blame]
#
# Copyright (C) 2020 The Android Open Source 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.
#
"""Tools for verifying the presence or absence of flags in builds."""
from pathlib import Path
import shutil
import subprocess
from typing import List, Optional, Tuple
from ndk.abis import Abi
from ndk.hosts import Host
import ndk.paths
class FlagVerifierResult:
"""Base class for the result of FlagVerifier checks."""
def __init__(self, error_message: Optional[str]) -> None:
self.error_message = error_message
def failed(self) -> bool:
"""Returns True if verification failed."""
raise NotImplementedError
def make_test_result_tuple(self) -> Tuple[bool, Optional[str]]:
"""Creates a test result tuple in the format expect by run_test."""
return not self.failed(), self.error_message
class FlagVerifierSuccess(FlagVerifierResult):
"""A successful flag verification result."""
def __init__(self) -> None:
super().__init__(error_message=None)
def failed(self) -> bool:
return False
class FlagVerifierFailure(FlagVerifierResult):
"""An unsuccessful flag verification result."""
def __init__(self, error_message: str) -> None:
super().__init__(error_message)
def failed(self) -> bool:
return True
class FlagVerifier:
"""Verifies that a build receives the expected flags."""
def __init__(self, project: Path, ndk_path: Path, abi: Abi,
api: int) -> None:
self.project = project
self.ndk_path = ndk_path
self.abi = abi
self.api = api
self.expected_flags: List[str] = []
self.not_expected_flags: List[str] = []
def expect_flag(self, flag: str) -> None:
"""Verify that the given string is present in the build output.
Args:
flag: The literal string to search for in the output. Will be
matched against whole whitespace-separated words in the
output.
"""
if flag in self.not_expected_flags:
raise ValueError(f'Flag {flag} both expected and not expected')
self.expected_flags.append(flag)
def expect_not_flag(self, flag: str) -> None:
"""Verify that the given string is not present in the build output.
Args:
flag: The literal string to search for in the output. Will be
matched against whole whitespace-separated words in the
output.
"""
if flag in self.expected_flags:
raise ValueError(f'Flag {flag} both expected and not expected')
self.not_expected_flags.append(flag)
def _check_build(self, cmd: List[str]) -> FlagVerifierResult:
result = subprocess.run(cmd,
check=False,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
encoding='utf-8')
if result.returncode != 0:
return FlagVerifierFailure(result.stdout)
words = result.stdout.split(' ')
missing_flags: List[str] = []
wrong_flags: List[str] = []
for expected in self.expected_flags:
if expected not in words:
missing_flags.append(expected)
for not_expected in self.not_expected_flags:
if not_expected in words:
wrong_flags.append(not_expected)
if missing_flags:
return FlagVerifierFailure(
'Expected flags were not present in the build output: ' +
", ".join(missing_flags) + f'\n{result.stdout}')
if wrong_flags:
return FlagVerifierFailure(
'Unexpected flags were present in the build output: ' +
", ".join(wrong_flags) + f'\n{result.stdout}')
return FlagVerifierSuccess()
def verify(self) -> FlagVerifierResult:
"""Verifies that both ndk-build and CMake behave as specified.
Returns:
A FlagVerifierResult object describing the verification result.
"""
result = self.verify_cmake()
if result.failed():
return result
return self.verify_ndk_build()
def verify_ndk_build(self) -> FlagVerifierResult:
"""Verifies that ndk-build behaves as specified.
Returns:
A FlagVerifierResult object describing the verification result.
"""
ndk_build = self.ndk_path / 'ndk-build'
if Host.current() == Host.Windows64:
ndk_build = ndk_build.with_suffix('.cmd')
return self._check_build([
str(ndk_build),
'-C',
str(self.project),
'-B',
'V=1',
f'APP_ABI={self.abi}',
f'APP_PLATFORM=android-{self.api}',
])
def verify_cmake(self) -> FlagVerifierResult:
"""Verifies that CMake behaves as specified.
Returns:
A FlagVerifierResult object describing the verification result.
"""
host = Host.current()
if host == Host.Windows64:
tag = 'windows-x86'
else:
tag = f'{host.value}-x86'
cmake = ndk.paths.ANDROID_DIR / f'prebuilts/cmake/{tag}/bin/cmake'
ninja = ndk.paths.ANDROID_DIR / f'prebuilts/ninja/{tag}/ninja'
if host == Host.Windows64:
cmake = cmake.with_suffix('.exe')
ninja = ninja.with_suffix('.exe')
# PythonBuildTest ensures that we're cd'd into the test out directory.
build_dir = Path('build')
if build_dir.exists():
shutil.rmtree(build_dir)
build_dir.mkdir(parents=True)
toolchain_file = self.ndk_path / 'build/cmake/android.toolchain.cmake'
cmd = [
str(cmake),
'-S',
str(self.project),
'-B',
str(build_dir),
f'-DCMAKE_TOOLCHAIN_FILE={toolchain_file}',
f'-DANDROID_ABI={self.abi}',
f'-DANDROID_PLATFORM=android-{self.api}',
'-GNinja',
f'-DCMAKE_MAKE_PROGRAM={ninja}',
]
result = subprocess.run(cmd,
check=False,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
encoding='utf-8')
if result.returncode != 0:
return FlagVerifierFailure(result.stdout)
return self._check_build([str(ninja), '-C', str(build_dir), '-v'])