| # |
| # 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']) |