diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5ea803f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# See https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties +# for all available properties. + +# Top-most EditorConfig file for the firebase-ios-sdk repo. +root = true + +# Defaults for all files +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +# ObjC and Swift files +# See https://developer.apple.com/documentation/xcode-release-notes/xcode-16-release-notes#New-Features-in-Xcode-16-Beta +# for the subset of properties supported by Xcode. +[*.{h,m,mm,swift}] +indent_style = space +indent_size = 2 +max_line_length = 100 diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..79c5d63 --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,54 @@ +name: check + +on: + pull_request: + push: + branches: main + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +jobs: + check: + runs-on: macos-latest + env: + MINT_PATH: ${{ github.workspace }}/mint + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-python@v3 + with: + python-version: '3.10' + + - name: Cache Mint packages + uses: actions/cache@v3 + with: + path: ${{ env.MINT_PATH }} + key: ${{ runner.os }}-mint-${{ hashFiles('**/Mintfile') }} + restore-keys: ${{ runner.os }}-mint- + + - name: Setup Scripts Directory + run: ./setup-scripts.sh + + - name: Setup check + run: | + brew update + brew install clang-format@18 + brew install mint + mint bootstrap + + - name: Style + run: scripts/style.sh test-only + + - name: Whitespace + run: scripts/check_whitespace.sh + + - name: Filename spaces + run: scripts/check_filename_spaces.sh + + - name: Copyrights + run: scripts/check_copyright.sh + + - name: Imports + run: scripts/check_imports.swift diff --git a/.swiftformat b/.swiftformat new file mode 100644 index 0000000..7def2b7 --- /dev/null +++ b/.swiftformat @@ -0,0 +1,14 @@ +# Formatting Options - Mimic Google style +--indent 2 +--maxwidth 100 +--wrapparameters afterfirst + +# Disabled Rules + +# Too many of our swift files have simplistic examples. While technically +# it's correct to remove the unused argument labels, it makes our examples +# look wrong. +--disable unusedArguments + +# We prefer trailing braces. +--disable wrapMultilineStatementBraces diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/.swiftpm/xcode/xcuserdata/aashishp.xcuserdatad/xcschemes/xcschememanagement.plist b/.swiftpm/xcode/xcuserdata/aashishp.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index fb5eda9..0000000 --- a/.swiftpm/xcode/xcuserdata/aashishp.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,35 +0,0 @@ - - - - - SchemeUserState - - FirebaseDataConnect.xcscheme_^#shared#^_ - - orderHint - 0 - - Promises (Playground) 1.xcscheme - - isShown - - orderHint - 2 - - Promises (Playground) 2.xcscheme - - isShown - - orderHint - 3 - - Promises (Playground).xcscheme - - isShown - - orderHint - 1 - - - - diff --git a/Mintfile b/Mintfile new file mode 100644 index 0000000..640a5ca --- /dev/null +++ b/Mintfile @@ -0,0 +1 @@ +nicklockwood/SwiftFormat@0.54.0 diff --git a/Package.swift b/Package.swift index a786a70..b9b07ad 100644 --- a/Package.swift +++ b/Package.swift @@ -19,7 +19,7 @@ import class Foundation.ProcessInfo import PackageDescription -//let firebaseVersion = "10.25.0" +// let firebaseVersion = "10.25.0" let package = Package( name: "FirebaseDataConnect", @@ -28,7 +28,7 @@ let package = Package( .library( name: "FirebaseDataConnect", targets: ["FirebaseDataConnect"] - ) + ), ], dependencies: [ .package(url: "https://github.com/firebase/firebase-ios-sdk", @@ -44,7 +44,7 @@ let package = Package( dependencies: [ .product(name: "GRPC", package: "grpc-swift"), .product(name: "FirebaseAuth", package: "firebase-ios-sdk"), - .product(name: "FirebaseAppCheck", package: "firebase-ios-sdk") + .product(name: "FirebaseAppCheck", package: "firebase-ios-sdk"), ], path: "Sources" @@ -57,7 +57,6 @@ let package = Package( resources: [ .copy("Resources/fdc-kitchensink"), ] - ) + ), ] ) - diff --git a/Sources/OptionalVarWrapper.swift b/Sources/OptionalVarWrapper.swift index 2e289b5..a1391a1 100644 --- a/Sources/OptionalVarWrapper.swift +++ b/Sources/OptionalVarWrapper.swift @@ -17,7 +17,7 @@ import Foundation @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) @propertyWrapper public struct OptionalVariable where Value: Encodable { - public private(set) var isSet: Bool = false + public private(set) var isSet = false public var wrappedValue: Value? { didSet { diff --git a/Sources/Scalars/LocalDate.swift b/Sources/Scalars/LocalDate.swift index e51338a..d228e58 100644 --- a/Sources/Scalars/LocalDate.swift +++ b/Sources/Scalars/LocalDate.swift @@ -77,8 +77,8 @@ public struct LocalDate: Codable, Equatable, CustomStringConvertible { } @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -extension LocalDate { - public init(from decoder: any Decoder) throws { +public extension LocalDate { + init(from decoder: any Decoder) throws { let container = try decoder.singleValueContainer() let localDateString = try container.decode(String.self) @@ -87,7 +87,7 @@ extension LocalDate { dateComponents = calendar.dateComponents(components, from: date) } - public func encode(to encoder: any Encoder) throws { + func encode(to encoder: any Encoder) throws { var container = encoder.singleValueContainer() let formattedDate = dateFormatter.string(from: date) try container.encode(formattedDate) @@ -97,12 +97,12 @@ extension LocalDate { // MARK: Equatable, Comparable @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -extension LocalDate { - public static func < (lhs: LocalDate, rhs: LocalDate) -> Bool { +public extension LocalDate { + static func < (lhs: LocalDate, rhs: LocalDate) -> Bool { return lhs.date < rhs.date } - public static func == (lhs: LocalDate, rhs: LocalDate) -> Bool { + static func == (lhs: LocalDate, rhs: LocalDate) -> Bool { return lhs.date == rhs.date } } diff --git a/Tests/Integration/KitchenSinkClient.swift b/Tests/Integration/KitchenSinkClient.swift index 66d0cde..c816f4a 100644 --- a/Tests/Integration/KitchenSinkClient.swift +++ b/Tests/Integration/KitchenSinkClient.swift @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. - import FirebaseDataConnect import Foundation diff --git a/Tests/Integration/KitchenSinkKeys.swift b/Tests/Integration/KitchenSinkKeys.swift index e28b2e6..3630b13 100644 --- a/Tests/Integration/KitchenSinkKeys.swift +++ b/Tests/Integration/KitchenSinkKeys.swift @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. - import Foundation import FirebaseDataConnect diff --git a/Tests/Integration/KitchenSinkOperations.swift b/Tests/Integration/KitchenSinkOperations.swift index 383fbb5..7b9b349 100644 --- a/Tests/Integration/KitchenSinkOperations.swift +++ b/Tests/Integration/KitchenSinkOperations.swift @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. - import FirebaseDataConnect import Foundation diff --git a/scripts/README.md b/scripts/README.md deleted file mode 100644 index 0a7350b..0000000 --- a/scripts/README.md +++ /dev/null @@ -1,217 +0,0 @@ -# Firebase Apple Scripts - -This directory provides a set of scripts for development, test, and continuous -integration of the Firebase Apple SDKs. - -## [check.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/check.sh) - -Used by the -[check CI workflow](https://github.com/firebase/firebase-ios-sdk/blob/main/.github/workflows/check.yml) -to run several static analysis checks. It calls the following scripts: - -### [style.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/style.sh) - -Runs clang-format and swiftformat across the repo. - -### [check_whitespace.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/check_whitespace.sh) - -Verify there are no files with trailing whitespace. - -### [check_filename_spaces.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/check_filename_spaces.sh) - -Spaces in filenames are not allowed. - -### [check_copyright.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/check_copyright.sh) - -Verify existence and format of copyrights. - -### [check_test_inclusion.py](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/check_test_inclusion.py) - -Test existence check for the internal Firestore Xcode project. - -### [check_imports.swift](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/check_imports.swift) - -Verify import style complies with -[repo standards](https://github.com/firebase/firebase-ios-sdk/blob/main/HeadersImports.md). - -### [check_firestore_core_api_absl.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/check_firestore_core_api_absl.sh) - -Check Firestore `absl` usages for g3 build issues. - -### [check_lint.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/check_lint.sh) - -Run cpplint. - -### [sync_project.rb](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/sync_project.rb) - -Used by Firestore to to keep the Xcode project in sync after adding/removing tests. - -## Other Scripts -### [binary_to_array.py](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/binary_to_array.py) - -Firestore script to convert binary data into a C/C++ array. - -### [build.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/build.sh) - -Script used by CI jobs to wrap xcodebuild invocations with options. - -### [build_non_firebase_sdks.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/build.sh) - -CI script to build binary versions of non-Firebase SDKs for QuickStart testing. - -### [build_zip.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/build_zip.sh) - -CI script for building the zip distribution. - -### [buildcache.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/buildcache.sh) - -Clang options for the buildcache GitHub action. - -### [change_headers.swift](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/change_headers.swift) - -Utility script to update source to repo-relative headers. - -### [check_secrets.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/check_secrets.sh) - -CI script to test if secrets are available (not running on a fork). - -### [collect_metrics.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/collect_metrics.sh) - -CI script to collect project health metrics and upload them to a database. - -### [configure_test_keychain.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/configure_test_keychain.sh) - -CI script to setup the keychain for macOS and Catalyst testing. - -### [cpplint.py](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/cpplint.py) - -Firestore script for C++ linting. - -### [create_pull_request.rb](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/create_pull_request.rb) - -Utility used by CI scripts to create issues and PRs. - -### [decrypt_gha_secret.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/decrypt_gha_secret.sh) - -CI script to decrypt a GitHub Actions secret. - -### [encrypt_gha_secret.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/encrypt_gha_secret.sh) - -CI script to encrypt a GitHub Actions secret. - -### [fuzzing_ci.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/fuzzing_ci.sh) - -Firestore CI script to run fuzz testing. - -### [generate_access_token.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/generate_access_token.sh) - -Script to generate a Firebase access token used by Remote config integration tests. - -### [install_prereqs.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scriptsinstall_prereqs.sh) - -Utility CI script to provide configuration for build.sh - -### [localize_podfile.swift](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/localize_podfile.swift) - -Utility script to update a Podfile to point to local podspecs. - -### [make_release_notes.py](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/make_release_notes.py) - -Converts GitHub-flavored markdown changelogs to devsite-compatible release notes. - -### [pod_lib_lint.rb](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/pod_lib_lint.rb) - -Wrapper script for running `pod lib lint` tests to include dependencies from the monorepo. - -### [release_testing_setup.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/release_testing_setup.sh) - -Utility script for the release workflow. - -### [remove_data.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/remove_data.sh) - -Cleanup script for CI workflows. - -### [run_database_emulator.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/run_database_emulator.sh) - -Run the RTDB emulator. - -### [run_firestore_emulator.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/run_firestore_emulator.sh) - -Run the Firestore emulator. - -### [setup_bundler.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/setup_bundler.sh) - -Set up the Ruby bundler. - -### [setup_check.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/setup_check.sh) - -Install tooling for the check workflow. - -### [setup_quickstart.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/setup_quickstart.sh) - -Set up a QuickStart for integration testing. - -### [setup_quickstart_framework.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/setup_quickstart_framework.sh) - -Set up a QuickStart for zip distribution testing. - -### [setup_spm_tests.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/setup_spm_tests.sh) - -Configuration for SPM testing. - -### [spm_test_schemes/](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/spm_test_schemes) - -Schemes used by above script to enable test target schemes. - -### [test_archiving.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/test_archiving.sh) - -Test Xcode Archive build. - -### [test_catalyst.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/test_catalyst.sh) - -Test catalyst build. - -### [test_quickstart.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/test_quickstart.sh) - -Test QuickStart. - -### [test_quickstart_framework.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/test_quickstart_framework.sh) - -Test QuickStart with the zip distribution. - -### [update_xcode_target.rb](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/update_xcode_target.rb) - -Script to add a file to an Xcode target. - -### [xcresult_logs.py](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/xcresult_logs.py) - -Tooling used by `build.sh` to get the log output for an `xcodebuild` invocation. - -### [zip_quickstart_test.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/zip_quickstart_test.sh) - -Run the tests associated with a QuickStart with a zip distribution. - -## Script Subdirectories -### [create_spec_repo](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/create_spec_repo) - -Swift utility to build a podspec repo. - -### [gha-encrypted](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/gha-encrypted) - -Store for GitHub secret encrypted resources. - -### [health_metrics](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/health_metrics) - -Code coverage and binary size tooling. - -### [lib](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/lib) - -Support libraries for `xcresult_logs.py`. - -### [lldb](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/lldb) - -Firestore utilities. - -### [third_party](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/third_party) - -Use Travis's MIT licensed retry.sh script. diff --git a/scripts/api_diff_report/api_diff_report.py b/scripts/api_diff_report/api_diff_report.py deleted file mode 100644 index 4ec6a6d..0000000 --- a/scripts/api_diff_report/api_diff_report.py +++ /dev/null @@ -1,253 +0,0 @@ -# Copyright 2023 Google LLC -# -# 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. - -import json -import argparse -import logging -import os -import api_info - -STATUS_ADD = 'ADDED' -STATUS_REMOVED = 'REMOVED' -STATUS_MODIFIED = 'MODIFIED' -STATUS_ERROR = 'BUILD ERROR' -API_DIFF_FILE_NAME = 'api_diff_report.markdown' - - -def main(): - logging.getLogger().setLevel(logging.INFO) - - args = parse_cmdline_args() - - new_api_file = os.path.join(os.path.expanduser(args.pr_branch), - api_info.API_INFO_FILE_NAME) - old_api_file = os.path.join(os.path.expanduser(args.base_branch), - api_info.API_INFO_FILE_NAME) - if os.path.exists(new_api_file): - with open(new_api_file) as f: - new_api_json = json.load(f) - else: - new_api_json = {} - if os.path.exists(old_api_file): - with open(old_api_file) as f: - old_api_json = json.load(f) - else: - old_api_json = {} - - diff = generate_diff_json(new_api_json, old_api_json) - if diff: - logging.info(f'json diff: \n{json.dumps(diff, indent=2)}') - logging.info(f'plain text diff report: \n{generate_text_report(diff)}') - report = generate_markdown_report(diff) - logging.info(f'markdown diff report: \n{report}') - else: - logging.info('No API Diff Detected.') - report = "" - - output_dir = os.path.expanduser(args.output_dir) - if not os.path.exists(output_dir): - os.makedirs(output_dir) - api_report_path = os.path.join(output_dir, API_DIFF_FILE_NAME) - logging.info(f'Writing API diff report to {api_report_path}') - with open(api_report_path, 'w') as f: - f.write(report) - - -def generate_diff_json(new_api, old_api, level='module'): - """diff_json only contains module & api that has a change. - - format: - { - $(module_name_1): { - "api_types": { - $(api_type_1): { - "apis": { - $(api_1): { - "declaration": [ - $(api_1_declaration) - ], - "sub_apis": { - $(sub_api_1): { - "declaration": [ - $(sub_api_1_declaration) - ] - }, - }, - "status": $(diff_status) - } - } - } - } - } - } - """ - NEXT_LEVEL = {'module': 'api_types', 'api_types': 'apis', 'apis': 'sub_apis'} - next_level = NEXT_LEVEL.get(level) - - diff = {} - for key in set(new_api.keys()).union(old_api.keys()): - # Added API - if key not in old_api: - diff[key] = new_api[key] - diff[key]['status'] = STATUS_ADD - if diff[key].get('declaration'): - diff[key]['declaration'] = [STATUS_ADD] + diff[key]['declaration'] - # Removed API - elif key not in new_api: - diff[key] = old_api[key] - diff[key]['status'] = STATUS_REMOVED - if diff[key].get('declaration'): - diff[key]['declaration'] = [STATUS_REMOVED] + diff[key]['declaration'] - # Module Build Error. If a "module" exist but have no - # content (e.g. doc_path), it must have a build error. - elif level == 'module' and (not new_api[key]['path'] - or not old_api[key]['path']): - diff[key] = {'status': STATUS_ERROR} - # Check diff in child level and diff in declaration - else: - child_diff = generate_diff_json(new_api[key][next_level], - old_api[key][next_level], - level=next_level) if next_level else {} - declaration_diff = new_api[key].get('declaration') != old_api[key].get( - 'declaration') if level in ['apis', 'sub_apis'] else False - - # No diff - if not child_diff and not declaration_diff: - continue - - diff[key] = new_api[key] - # Changes at child level - if child_diff: - diff[key][next_level] = child_diff - - # Modified API (changes in API declaration) - if declaration_diff: - diff[key]['status'] = STATUS_MODIFIED - diff[key]['declaration'] = [STATUS_ADD] + \ - new_api[key]['declaration'] + \ - [STATUS_REMOVED] + \ - old_api[key]['declaration'] - - return diff - - -def generate_text_report(diff, level=0, print_key=True): - report = '' - indent_str = ' ' * level - for key, value in diff.items(): - # filter out ["path", "api_type_link", "api_link", "declaration", "status"] - if isinstance(value, dict): - if key in ['api_types', 'apis', 'sub_apis']: - report += generate_text_report(value, level=level) - else: - status_text = f"{value.get('status', '')}:" if 'status' in value else '' - if status_text: - if print_key: - report += f'{indent_str}{status_text} {key}\n' - else: - report += f'{indent_str}{status_text}\n' - if value.get('declaration'): - for d in value.get('declaration'): - report += f'{indent_str}{d}\n' - else: - report += f'{indent_str}{key}\n' - report += generate_text_report(value, level=level + 1) - - return report - - -def generate_markdown_report(diff, level=0): - report = '' - header_str = '#' * (level + 3) - for key, value in diff.items(): - if isinstance(value, dict): - if key in ['api_types', 'apis', 'sub_apis']: - report += generate_markdown_report(value, level=level) - else: - current_status = value.get('status') - if current_status: - # Module level: Always print out module name and class name as title - if level in [0, 2]: - report += f'{header_str} [{current_status}] {key}\n' - if current_status != STATUS_ERROR: # ADDED,REMOVED,MODIFIED - report += '
\n\n' - report += f'[{current_status}] {key}\n' - report += '\n\n' - declarations = value.get('declaration', []) - sub_report = generate_text_report(value, level=1, print_key=False) - detail = process_declarations(current_status, declarations, - sub_report) - report += f'```diff\n{detail}\n```\n\n
\n\n' - else: # no diff at current level - report += f'{header_str} {key}\n' - report += generate_markdown_report(value, level=level + 1) - # Module level: Always print out divider in the end - if level == 0: - report += '-----\n' - - return report - - -def process_declarations(current_status, declarations, sub_report): - """Diff syntax highlighting in Github Markdown.""" - detail = '' - if current_status == STATUS_MODIFIED: - for line in (declarations + sub_report.split('\n')): - if STATUS_ADD in line: - prefix = '+ ' - continue - elif STATUS_REMOVED in line: - prefix = '- ' - continue - if line: - detail += f'{prefix}{line}\n' - else: - prefix = '+ ' if current_status == STATUS_ADD else '- ' - for line in (declarations + sub_report.split('\n')): - if line: - detail += f'{prefix}{line}\n' - - return categorize_declarations(detail) - - -def categorize_declarations(detail): - """Categorize API info by Swift and Objective-C.""" - lines = detail.split('\n') - - swift_lines = [line.replace('Swift', '') for line in lines if 'Swift' in line] - objc_lines = [ - line.replace('Objective-C', '') for line in lines if 'Objective-C' in line - ] - - swift_detail = 'Swift:\n' + '\n'.join(swift_lines) if swift_lines else '' - objc_detail = 'Objective-C:\n' + '\n'.join(objc_lines) if objc_lines else '' - - if not swift_detail and not objc_detail: - return detail - else: - return f'{swift_detail}\n{objc_detail}'.strip() - - -def parse_cmdline_args(): - parser = argparse.ArgumentParser() - parser.add_argument('-p', '--pr_branch') - parser.add_argument('-b', '--base_branch') - parser.add_argument('-o', '--output_dir', default='output_dir') - - args = parser.parse_args() - return args - - -if __name__ == '__main__': - main() diff --git a/scripts/api_diff_report/api_info.py b/scripts/api_diff_report/api_info.py deleted file mode 100644 index bf9687e..0000000 --- a/scripts/api_diff_report/api_info.py +++ /dev/null @@ -1,221 +0,0 @@ -# Copyright 2023 Google LLC -# -# 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. - -import json -import argparse -import logging -import os -import subprocess -import icore_module -from urllib.parse import unquote -from bs4 import BeautifulSoup - -API_INFO_FILE_NAME = 'api_info.json' - - -def main(): - logging.getLogger().setLevel(logging.INFO) - - # Parse command-line arguments - args = parse_cmdline_args() - output_dir = os.path.expanduser(args.output_dir) - if not os.path.exists(output_dir): - os.makedirs(output_dir) - - # Detect changed modules based on changed files - changed_api_files = get_api_files(args.file_list) - if not changed_api_files: - logging.info('No Changed API File Detected') - exit(0) - changed_modules = icore_module.detect_changed_modules(changed_api_files) - if not changed_modules: - logging.info('No Changed Module Detected') - exit(0) - - # Generate API documentation and parse API declarations - # for each changed module - api_container = {} - for _, module in changed_modules.items(): - api_doc_dir = os.path.join(output_dir, 'doc', module['name']) - build_api_doc(module, api_doc_dir) - - if os.path.exists(api_doc_dir): - module_api_container = parse_module(api_doc_dir) - api_container[module['name']] = { - 'path': api_doc_dir, - 'api_types': module_api_container - } - else: # api doc fail to build. - api_container[module['name']] = {'path': '', 'api_types': {}} - - api_info_path = os.path.join(output_dir, API_INFO_FILE_NAME) - logging.info(f'Writing API data to {api_info_path}') - with open(api_info_path, 'w') as f: - f.write(json.dumps(api_container, indent=2)) - - -def get_api_files(file_list): - """Filter out non api files.""" - return [ - f for f in file_list - if f.endswith('.swift') or (f.endswith('.h') and 'Public' in f) - ] - - -def build_api_doc(module, output_dir): - """Use Jazzy to build API documentation for a specific module's source - code.""" - if module['language'] == icore_module.SWIFT: - logging.info('------------') - cmd = f'jazzy --module {module["name"]}'\ - + ' --swift-build-tool xcodebuild'\ - + ' --build-tool-arguments'\ - + f' -scheme,{module["scheme"]}'\ - + ',-destination,generic/platform=iOS,build'\ - + f' --output {output_dir}' - logging.info(cmd) - result = subprocess.Popen(cmd, - universal_newlines=True, - shell=True, - stdout=subprocess.PIPE) - logging.info(result.stdout.read()) - elif module['language'] == icore_module.OBJECTIVE_C: - logging.info('------------') - cmd = 'jazzy --objc'\ - + f' --framework-root {module["root_dir"]}'\ - + f' --umbrella-header {module["umbrella_header"]}'\ - + f' --output {output_dir}' - logging.info(cmd) - result = subprocess.Popen(cmd, - universal_newlines=True, - shell=True, - stdout=subprocess.PIPE) - logging.info(result.stdout.read()) - - -def parse_module(api_doc_path): - """Parse "${module}/index.html" and extract necessary information - e.g. - { - $(api_type_1): { - "api_type_link": $(api_type_link), - "apis": { - $(api_name_1): { - "api_link": $(api_link_1), - "declaration": [$(swift_declaration), $(objc_declaration)], - "sub_apis": { - $(sub_api_name_1): {"declaration": [$(swift_declaration)]}, - $(sub_api_name_2): {"declaration": [$(objc_declaration)]}, - ... - } - }, - $(api_name_2): { - ... - }, - } - }, - $(api_type_2): { - .. - }, - } - """ - module_api_container = {} - # Read the HTML content from the file - index_link = f'{api_doc_path}/index.html' - with open(index_link, 'r') as file: - html_content = file.read() - - # Parse the HTML content - soup = BeautifulSoup(html_content, 'html.parser') - - # Locate the element with class="nav-groups" - nav_groups_element = soup.find('ul', class_='nav-groups') - # Extract data and convert to JSON format - for nav_group in nav_groups_element.find_all('li', class_='nav-group-name'): - api_type = nav_group.find('a').text - api_type_link = nav_group.find('a')['href'] - - apis = {} - for nav_group_task in nav_group.find_all('li', class_='nav-group-task'): - api_name = nav_group_task.find('a').text - api_link = nav_group_task.find('a')['href'] - apis[api_name] = {'api_link': api_link, 'declaration': [], 'sub_apis': {}} - - module_api_container[api_type] = { - 'api_type_link': api_type_link, - 'apis': apis - } - - parse_api(api_doc_path, module_api_container) - - return module_api_container - - -def parse_api(doc_path, module_api_container): - """Parse API html and extract necessary information. - - e.g. ${module}/Classes.html - """ - for api_type, api_type_abstract in module_api_container.items(): - api_type_link = f'{doc_path}/{unquote(api_type_abstract["api_type_link"])}' - api_data_container = module_api_container[api_type]['apis'] - with open(api_type_link, 'r') as file: - html_content = file.read() - - # Parse the HTML content - soup = BeautifulSoup(html_content, 'html.parser') - for api in soup.find('div', class_='task-group').find_all('li', - class_='item'): - api_name = api.find('a', class_='token').text - for api_declaration in api.find_all('div', class_='language'): - api_declaration_text = ' '.join(api_declaration.stripped_strings) - api_data_container[api_name]['declaration'].append(api_declaration_text) - - for api, api_abstruct in api_type_abstract['apis'].items(): - if api_abstruct['api_link'].endswith('.html'): - parse_sub_api(f'{doc_path}/{unquote(api_abstruct["api_link"])}', - api_data_container[api]['sub_apis']) - - -def parse_sub_api(api_link, sub_api_data_container): - """Parse SUB_API html and extract necessary information. - - e.g. ${module}/Classes/${class_name}.html - """ - with open(api_link, 'r') as file: - html_content = file.read() - - # Parse the HTML content - soup = BeautifulSoup(html_content, 'html.parser') - for s_api_group in soup.find_all('div', class_='task-group'): - for s_api in s_api_group.find_all('li', class_='item'): - api_name = s_api.find('a', class_='token').text - sub_api_data_container[api_name] = {'declaration': []} - for api_declaration in s_api.find_all('div', class_='language'): - api_declaration_text = ' '.join(api_declaration.stripped_strings) - sub_api_data_container[api_name]['declaration'].append( - api_declaration_text) - - -def parse_cmdline_args(): - parser = argparse.ArgumentParser() - parser.add_argument('-f', '--file_list', nargs='+', default=[]) - parser.add_argument('-o', '--output_dir', default='output_dir') - - args = parser.parse_args() - return args - - -if __name__ == '__main__': - main() diff --git a/scripts/api_diff_report/icore_module.py b/scripts/api_diff_report/icore_module.py deleted file mode 100644 index 47ef00b..0000000 --- a/scripts/api_diff_report/icore_module.py +++ /dev/null @@ -1,183 +0,0 @@ -# Copyright 2023 Google LLC -# -# 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. - -import os -import logging -import json -import subprocess - -SWIFT = 'Swift' -OBJECTIVE_C = 'Objective-C' - -# List of Swift and Objective-C modules -MODULE_LIST = [ - 'FirebaseABTesting', - 'FirebaseAnalytics', # Not buildable from source - 'FirebaseAnalyticsOnDeviceConversion', # Not buildable. - 'FirebaseAnalyticsSwift', - 'FirebaseAppCheck', - 'FirebaseAppDistribution', - 'FirebaseAuth', - 'FirebaseCore', - 'FirebaseCrashlytics', - 'FirebaseDatabase', - 'FirebaseDatabaseSwift', - 'FirebaseDynamicLinks', - 'FirebaseFirestore', - 'FirebaseFirestoreSwift', - 'FirebaseFunctions', - 'FirebaseInAppMessaging' - 'FirebaseInAppMessagingSwift', - 'FirebaseInstallations', - 'FirebaseMessaging', - 'FirebaseMLModelDownloader', - 'FirebasePerformance', - 'FirebaseRemoteConfig', - 'FirebaseRemoteConfigSwift', - # Not buildable. No scheme named "FirebaseSharedSwift" - 'FirebaseSharedSwift', - 'FirebaseStorage', - # Not buildable. NO "source_files" - 'GoogleAppMeasurement', - # Not buildable. NO "source_files" - 'GoogleAppMeasurementOnDeviceConversion' -] - - -def main(): - module_info() - - -def detect_changed_modules(changed_api_files): - """Detect changed modules based on changed API files.""" - all_modules = module_info() - changed_modules = {} - for file_path in changed_api_files: - for k, v in all_modules.items(): - if v['root_dir'] and v['root_dir'] in file_path: - changed_modules[k] = v - break - - logging.info(f'changed_modules:\n{json.dumps(changed_modules, indent=4)}') - return changed_modules - - -def module_info(): - """retrieve module info in MODULE_LIST from `.podspecs` - The module info helps to build Jazzy - includes: module name, source_files, public_header_files, - language, umbrella_header, framework_root - """ - module_from_podspecs = module_info_from_podspecs() - module_list = {} - for k, v in module_from_podspecs.items(): - if k in MODULE_LIST: - if k not in module_list: - module_list[k] = v - module_list[k]['language'] = OBJECTIVE_C if v.get( - 'public_header_files') else SWIFT - module_list[k]['scheme'] = get_scheme(k) - module_list[k]['umbrella_header'] = get_umbrella_header( - k, v.get('public_header_files')) - module_list[k]['root_dir'] = get_root_dir(k, v.get('source_files')) - - logging.info(f'all_module:\n{json.dumps(module_list, indent=4)}') - return module_list - - -def get_scheme(module_name): - """Jazzy documentation Info SWIFT only. - - Get scheme from module name in .podspecs Assume the scheme is the - same as the module name: - """ - MODULE_SCHEME_PATCH = { - 'FirebaseInAppMessagingSwift': 'FirebaseInAppMessagingSwift-Beta', - } - if module_name in MODULE_SCHEME_PATCH: - return MODULE_SCHEME_PATCH[module_name] - return module_name - - -def get_umbrella_header(module_name, public_header_files): - """Jazzy documentation Info OBJC only Get umbrella_header from - public_header_files in .podspecs Assume the umbrella_header is with the - format: - - {module_name}/Sources/Public/{module_name}/{module_name}.h - """ - if public_header_files: - if isinstance(public_header_files, list): - return public_header_files[0].replace('*', module_name) - elif isinstance(public_header_files, str): - return public_header_files.replace('*', module_name) - return '' - - -def get_root_dir(module_name, source_files): - """Get source code root_dir from source_files in .podspecs Assume the - root_dir is with the format: - - {module_name}/Sources or {module_name}/Source - """ - MODULE_ROOT_PATCH = { - 'FirebaseFirestore': 'Firestore/Source', - 'FirebaseFirestoreSwift': 'Firestore/Swift/Source', - 'FirebaseCrashlytics': 'Crashlytics/Crashlytics', - 'FirebaseInAppMessagingSwift': 'FirebaseInAppMessaging/Swift/Source', - } - if module_name in MODULE_ROOT_PATCH: - return MODULE_ROOT_PATCH[module_name] - if source_files: - for source_file in source_files: - if f'{module_name}/Sources' in source_file: - return f'{module_name}/Sources' - if f'{module_name}/Source' in source_file: - return f'{module_name}/Source' - return '' - - -def module_info_from_podspecs(root_dir=os.getcwd()): - result = {} - for filename in os.listdir(root_dir): - if filename.endswith('.podspec'): - podspec_data = parse_podspec(filename) - source_files = podspec_data.get('source_files') - if not podspec_data.get('source_files') and podspec_data.get('ios'): - source_files = podspec_data.get('ios').get('source_files') - result[podspec_data['name']] = { - 'name': podspec_data['name'], - 'source_files': source_files, - 'public_header_files': podspec_data.get('public_header_files') - } - return result - - -def parse_podspec(podspec_file): - result = subprocess.run(f'pod ipc spec {podspec_file}', - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - shell=True) - if result.returncode != 0: - logging.info(f'Error: {result.stderr}') - return None - - # Parse the JSON output - podspec_data = json.loads(result.stdout) - return podspec_data - - -if __name__ == '__main__': - main() diff --git a/scripts/api_diff_report/pr_commenter.py b/scripts/api_diff_report/pr_commenter.py deleted file mode 100644 index 3b3dda7..0000000 --- a/scripts/api_diff_report/pr_commenter.py +++ /dev/null @@ -1,215 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright 2023 Google LLC -# -# 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. - -import os -import json -import logging -import requests -import argparse -import api_diff_report -import datetime -import pytz - -from requests.adapters import HTTPAdapter -from requests.packages.urllib3.util.retry import Retry - -STAGES_PROGRESS = "progress" -STAGES_END = "end" - -TITLE_PROGESS = "## ⏳  Detecting API diff in progress...\n" -TITLE_END_DIFF = '## Apple API Diff Report\n' -TITLE_END_NO_DIFF = "## ✅  No API diff detected\n" - -COMMENT_HIDDEN_IDENTIFIER = '\r\n\r\n' -GITHUB_API_URL = 'https://api.github.com/repos/firebase/firebase-ios-sdk' -PR_LABEL = "public-api-change" - - -def main(): - logging.getLogger().setLevel(logging.INFO) - - # Parse command-line arguments - args = parse_cmdline_args() - - stage = args.stage - token = args.token - pr_number = args.pr_number - commit = args.commit - run_id = args.run_id - - report = "" - comment_id = get_comment_id(token, pr_number, COMMENT_HIDDEN_IDENTIFIER) - if stage == STAGES_PROGRESS: - if comment_id: - report = COMMENT_HIDDEN_IDENTIFIER - report += generate_markdown_title(TITLE_PROGESS, commit, run_id) - update_comment(token, comment_id, report) - delete_label(token, pr_number, PR_LABEL) - elif stage == STAGES_END: - diff_report_file = os.path.join(os.path.expanduser(args.report), - api_diff_report.API_DIFF_FILE_NAME) - with open(diff_report_file, 'r') as file: - report_content = file.read() - if report_content: # Diff detected - report = COMMENT_HIDDEN_IDENTIFIER + generate_markdown_title( - TITLE_END_DIFF, commit, run_id) + report_content - if comment_id: - update_comment(token, comment_id, report) - else: - add_comment(token, pr_number, report) - add_label(token, pr_number, PR_LABEL) - else: # No diff - if comment_id: - report = COMMENT_HIDDEN_IDENTIFIER + generate_markdown_title( - TITLE_END_NO_DIFF, commit, run_id) - update_comment(token, comment_id, report) - delete_label(token, pr_number, PR_LABEL) - - -def generate_markdown_title(title, commit, run_id): - pst_now = datetime.datetime.utcnow().astimezone( - pytz.timezone('America/Los_Angeles')) - return ( - title + 'Commit: %s\n' % commit - + 'Last updated: %s \n' % pst_now.strftime('%a %b %e %H:%M %Z %G') - + '**[View workflow logs & download artifacts]' - + '(https://github.com/firebase/firebase-ios-sdk/actions/runs/%s)**\n\n' - % run_id + '-----\n') - - -RETRIES = 3 -BACKOFF = 5 -RETRY_STATUS = (403, 500, 502, 504) -TIMEOUT = 5 - - -def requests_retry_session(retries=RETRIES, - backoff_factor=BACKOFF, - status_forcelist=RETRY_STATUS): - session = requests.Session() - retry = Retry(total=retries, - read=retries, - connect=retries, - backoff_factor=backoff_factor, - status_forcelist=status_forcelist) - adapter = HTTPAdapter(max_retries=retry) - session.mount('http://', adapter) - session.mount('https://', adapter) - return session - - -def get_comment_id(token, issue_number, comment_identifier): - comments = list_comments(token, issue_number) - for comment in comments: - if comment_identifier in comment['body']: - return comment['id'] - return None - - -def list_comments(token, issue_number): - """https://docs.github.com/en/rest/reference/issues#list-issue-comments""" - url = f'{GITHUB_API_URL}/issues/{issue_number}/comments' - headers = { - 'Accept': 'application/vnd.github.v3+json', - 'Authorization': f'token {token}' - } - with requests_retry_session().get(url, headers=headers, - timeout=TIMEOUT) as response: - logging.info("list_comments: %s response: %s", url, response) - return response.json() - - -def add_comment(token, issue_number, comment): - """https://docs.github.com/en/rest/reference/issues#create-an-issue-comment""" - url = f'{GITHUB_API_URL}/issues/{issue_number}/comments' - headers = { - 'Accept': 'application/vnd.github.v3+json', - 'Authorization': f'token {token}' - } - data = {'body': comment} - with requests.post(url, - headers=headers, - data=json.dumps(data), - timeout=TIMEOUT) as response: - logging.info("add_comment: %s response: %s", url, response) - - -def update_comment(token, comment_id, comment): - """https://docs.github.com/en/rest/reference/issues#update-an-issue-comment""" - url = f'{GITHUB_API_URL}/issues/comments/{comment_id}' - headers = { - 'Accept': 'application/vnd.github.v3+json', - 'Authorization': f'token {token}' - } - data = {'body': comment} - with requests_retry_session().patch(url, - headers=headers, - data=json.dumps(data), - timeout=TIMEOUT) as response: - logging.info("update_comment: %s response: %s", url, response) - - -def delete_comment(token, comment_id): - """https://docs.github.com/en/rest/reference/issues#delete-an-issue-comment""" - url = f'{GITHUB_API_URL}/issues/comments/{comment_id}' - headers = { - 'Accept': 'application/vnd.github.v3+json', - 'Authorization': f'token {token}' - } - with requests.delete(url, headers=headers, timeout=TIMEOUT) as response: - logging.info("delete_comment: %s response: %s", url, response) - - -def add_label(token, issue_number, label): - """https://docs.github.com/en/rest/reference/issues#add-labels-to-an-issue""" - url = f'{GITHUB_API_URL}/issues/{issue_number}/labels' - headers = { - 'Accept': 'application/vnd.github.v3+json', - 'Authorization': f'token {token}' - } - data = [label] - with requests.post(url, - headers=headers, - data=json.dumps(data), - timeout=TIMEOUT) as response: - logging.info("add_label: %s response: %s", url, response) - - -def delete_label(token, issue_number, label): - """https://docs.github.com/en/rest/reference/issues#delete-a-label""" - url = f'{GITHUB_API_URL}/issues/{issue_number}/labels/{label}' - headers = { - 'Accept': 'application/vnd.github.v3+json', - 'Authorization': f'token {token}' - } - with requests.delete(url, headers=headers, timeout=TIMEOUT) as response: - logging.info("delete_label: %s response: %s", url, response) - - -def parse_cmdline_args(): - parser = argparse.ArgumentParser() - parser.add_argument('-s', '--stage') - parser.add_argument('-r', '--report') - parser.add_argument('-t', '--token') - parser.add_argument('-n', '--pr_number') - parser.add_argument('-c', '--commit') - parser.add_argument('-i', '--run_id') - - args = parser.parse_args() - return args - - -if __name__ == '__main__': - main() diff --git a/scripts/binary_to_array.py b/scripts/binary_to_array.py deleted file mode 100755 index 3101806..0000000 --- a/scripts/binary_to_array.py +++ /dev/null @@ -1,313 +0,0 @@ -#!/usr/bin/env python2 - -# Copyright 2018 Google LLC -# -# 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. -# - -"""Utility to convert binary data into a C/C++ array. - -Usage: %s --input=input_file.bin [--output_source=output_source.cc] - [--output_header=output_header.h] [--cpp_namespace=namespace] - [--header_guard=HEADER_GUARD_TEXT] [--array=array_c_identifier] - [--array_size=array_size_c_identifier] [--filename=override_filename] - [--filename_identifier=filename_c_identifier] - -By default, the output source file will be named the same as the input file, -but with .cc as the extension; the output header file will be named the -same as the input file but with .h as the extension. - -By default, the data will be in an array named $NAME_data and the size will -be in a constant named $NAME_length, and the filename will be stored in -$NAME_filename. In all these cases, $NAME is the input filename (sans path and -extension) with runs of non-alphanumeric characters changed to underscores. The -header guard will be generated from the output header filename in a similar way. - -By default, the data will be placed in the root namespace. If the data is placed -in the root namespace, it will be declared as a C array (using extern "C" if -compiled in C++ mode). - -The actual size of $NAME_data is $NAME_length + 1, where it contains an extra -0x00 at the end. When data is actually text, $NAME_data can be used as a valid C -string directly. -""" - -from os import path -from re import sub -import argparse -import logging -import os - -arg_parser = argparse.ArgumentParser() - -arg_parser.add_argument("input", - help="Input file containing binary data to embed.") -arg_parser.add_argument("--output_source", - help="Output source file, defining the array data.") -arg_parser.add_argument("--output_header", - help="Output header file, declaring the array data.") -arg_parser.add_argument("--array", help="Identifier for the array.") -arg_parser.add_argument("--array_size", help="Identifier for the array size.") -arg_parser.add_argument("--filename", help="Override file name in code.") -arg_parser.add_argument("--filename_identifier", - help="Where to put the filename.") -arg_parser.add_argument("--header_guard", - help="Header guard to #define in the output header.") -arg_parser.add_argument("--cpp_namespace", - help="C++ namespace to use. " - "If blank, will generate a C array.") - -# How many hex bytes to display in a line. Each "0x00, " takes 6 characters, so -# a width of 12 lets us fit within 80 characters. -WIDTH = 12 - - -def header(header_guard, namespaces, array_name, array_size_name, fileid): - """Return a C/C++ header for the given array. - - Args: - header_guard: Name of the HEADER_GUARD to define. - namespaces: List of namespaces, outer to inner. - array_name: Name of the array. - array_size_name: Name of the array size constant. - fileid: Name of the identifier containing the file name. - - Returns: - A list of strings containing the C/C++ header file, line-by-line. - """ - - data = [] - data.extend([ - "// Copyright 2019 Google Inc. All Rights Reserved.", - "", - "#ifndef %s" % header_guard, - "#define %s" % header_guard, - "", - "#include ", - "" - ]) - if namespaces: - data.extend([ - "namespace %s {" % ns for ns in namespaces - ]) - else: - data.extend([ - "#if defined(__cplusplus)", - "extern \"C\" {", - "#endif // defined(__cplusplus)"]) - - data.extend([ - "", - "extern const size_t %s;" % array_size_name, - "extern const unsigned char %s[];" % array_name, - "extern const char %s[];" % fileid, - ]) - - data.extend([ - "" - ]) - if namespaces: - data.extend([ - "} // namespace %s" % ns for ns in reversed(namespaces) - ]) - else: - data.extend([ - "#if defined(__cplusplus)", - "} // extern \"C\"", - "#endif // defined(__cplusplus)" - ]) - data.extend([ - "", - "#endif // %s" % header_guard, - "" - ]) - return data - - -def source(namespaces, array_name, array_size_name, fileid, filename, - input_bytes, include_name): - """Return a C/C++ source file for the given array. - - Args: - namespaces: List of namespaces, outer to inner. - array_name: Name of the array. - array_size_name: Name of the array size constant. - fileid: Name of the identifier containing the filename. - filename: The original data filename itself. - input_bytes: Binary data to put into the array. - include_name: Name of the corresponding header file to include. - - Returns: - A string containing the C/C++ source file. - """ - - if os.name == 'nt': - # Force forward slashes on Windows - include_name = include_name.replace('\\', '/') - - data = [] - data.extend([ - "// Copyright 2019 Google Inc. All Rights Reserved.", - "", - "#include \"%s\"" % include_name, - "", - "#include ", - "" - ]) - if namespaces: - data.extend([ - "namespace %s {" % ns for ns in namespaces - ]) - else: - data.extend([ - "#if defined(__cplusplus)", - "extern \"C\" {", - "#endif // defined(__cplusplus)"]) - - data.extend([ - "", - "extern const size_t %s;" % array_size_name, - "extern const char %s[];" % fileid, - "extern const unsigned char %s[];" % array_name, "", - "const unsigned char %s[] = {" % array_name - ]) - length = len(input_bytes) - line = "" - for idx in range(0, length): - if idx % WIDTH == 0: - line += " " - else: - line += " " - line += "0x%02x," % input_bytes[idx] - if idx % WIDTH == WIDTH - 1: - data.append(line) - line = "" - data.append(line) - data.append(" 0x00 // Extra \\0 to make it a C string") - - data.extend([ - "};", - "", - "const size_t %s =" % array_size_name, - " sizeof(%s) - 1;" % array_name, - "", - "const char %s[] = \"%s\";" % (fileid, filename), - "", - ]) - - if namespaces: - data.extend([ - "} // namespace %s" % ns for ns in namespaces - ][::-1]) # close namespaces in reverse order - else: - data.extend([ - "#if defined(__cplusplus)", - "} // extern \"C\"", - "#endif // defined(__cplusplus)" - ]) - data.extend([ - "" - ]) - return data - - -def _get_repo_root(): - """Returns the root of the source repository. - """ - - scripts_dir = os.path.abspath(os.path.dirname(__file__)) - assert os.path.basename(scripts_dir) == 'scripts' - - root_dir = os.path.dirname(scripts_dir) - assert os.path.isdir(os.path.join(root_dir, '.github')) - - return root_dir - - -def main(): - """Read an binary input file and output to a C/C++ source file as an array. - """ - - args = arg_parser.parse_args() - - input_file = args.input - input_file_base = os.path.splitext(args.input)[0] - - output_source = args.output_source - if not output_source: - output_source = input_file_base + ".cc" - logging.debug("Using default --output_source='%s'", output_source) - - output_header = args.output_header - if not output_header: - output_header = input_file_base + ".h" - logging.debug("Using default --output_header='%s'", output_header) - - root_dir = _get_repo_root() - absolute_dir = path.dirname(output_header) - - relative_dir = path.relpath(absolute_dir, root_dir) - relative_header_path = path.join(relative_dir, path.basename(output_header)) - - identifier_base = sub("[^0-9a-zA-Z]+", "_", path.basename(input_file_base)) - array_name = args.array - if not array_name: - array_name = identifier_base + "_data" - logging.debug("Using default --array='%s'", array_name) - - array_size_name = args.array_size - if not array_size_name: - array_size_name = identifier_base + "_size" - logging.debug("Using default --array_size='%s'", array_size_name) - - fileid = args.filename_identifier - if not fileid: - fileid = identifier_base + "_filename" - logging.debug("Using default --filename_identifier='%s'", fileid) - - filename = args.filename - if filename is None: # but not if it's the empty string - filename = path.basename(input_file) - logging.debug("Using default --filename='%s'", filename) - - header_guard = args.header_guard - if not header_guard: - header_guard = sub("[^0-9a-zA-Z]+", "_", relative_header_path).upper() + '_' - # Avoid double underscores to stay compliant with the Standard. - header_guard = sub("[_]+", "_", header_guard) - logging.debug("Using default --header_guard='%s'", header_guard) - - namespace = args.cpp_namespace - namespaces = namespace.split("::") if namespace else [] - - with open(input_file, "rb") as infile: - input_bytes = bytearray(infile.read()) - logging.debug("Read %d bytes from %s", len(input_bytes), input_file) - - header_text = "\n".join(header(header_guard, namespaces, array_name, - array_size_name, fileid)) - source_text = "\n".join(source(namespaces, array_name, array_size_name, - fileid, filename, input_bytes, - relative_header_path)) - - with open(output_header, "w") as hdr: - hdr.write(header_text) - logging.debug("Wrote header file %s", output_header) - - with open(output_source, "w") as src: - src.write(source_text) - logging.debug("Wrote source file %s", output_source) - - -if __name__ == "__main__": - main() diff --git a/scripts/build.sh b/scripts/build.sh deleted file mode 100755 index f44d3d2..0000000 --- a/scripts/build.sh +++ /dev/null @@ -1,701 +0,0 @@ -#!/usr/bin/env bash - -# Copyright 2018 Google -# -# 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. - -# USAGE: build.sh product [platform] [method] -# -# Builds the given product for the given platform using the given build method - -function pod_gen() { - # Call pod gen with a podspec and additional optional arguments. - bundle exec pod gen --local-sources=./ --sources=https://github.com/firebase/SpecsDev.git,https://github.com/firebase/SpecsStaging.git,https://cdn.cocoapods.org/ "$@" -} - -set -euo pipefail - -if [[ $# -lt 1 ]]; then - cat 1>&2 <&2 - sleep 5 - - result=0 - xcodebuild "$@" | tee xcodebuild.log | "${xcpretty_cmd[@]}" || result=$? - fi - - if [[ $result != 0 ]]; then - echo "xcodebuild exited with $result" 1>&2 - - ExportLogs "$@" - return $result - fi -} - -# Exports any logs output captured in the xcresult -function ExportLogs() { - python "${scripts_dir}/xcresult_logs.py" "$@" -} - -if [[ "$xcode_major" -lt 15 ]]; then - ios_flags=( - -sdk 'iphonesimulator' - -destination 'platform=iOS Simulator,name=iPhone 14' - ) -else - ios_flags=( - -sdk 'iphonesimulator' - -destination 'platform=iOS Simulator,name=iPhone 15' - ) -fi - -ios_device_flags=( - -sdk 'iphoneos' - -destination 'generic/platform=iOS' -) - -ipad_flags=( - -sdk 'iphonesimulator' - -destination 'platform=iOS Simulator,name=iPad Pro (9.7-inch)' -) - -macos_flags=( - -sdk 'macosx' - -destination 'platform=OS X,arch=x86_64' -) -tvos_flags=( - -sdk "appletvsimulator" - -destination 'platform=tvOS Simulator,name=Apple TV' -) -watchos_flags=( - -destination 'platform=watchOS Simulator,name=Apple Watch Series 7 (45mm)' -) -visionos_flags=( - -destination 'platform=visionOS Simulator' -) -catalyst_flags=( - ARCHS=x86_64 VALID_ARCHS=x86_64 SUPPORTS_MACCATALYST=YES -sdk macosx - -destination platform="macOS,variant=Mac Catalyst,arch=x86_64" TARGETED_DEVICE_FAMILY=2 - CODE_SIGN_IDENTITY=- CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO -) - -# Compute standard flags for all platforms -case "$platform" in - iOS) - xcb_flags=("${ios_flags[@]}") - gen_platform=ios - ;; - - iOS-device) - xcb_flags=("${ios_device_flags[@]}") - gen_platform=ios - ;; - - iPad) - xcb_flags=("${ipad_flags[@]}") - ;; - - macOS) - xcb_flags=("${macos_flags[@]}") - gen_platform=macos - ;; - - tvOS) - xcb_flags=("${tvos_flags[@]}") - gen_platform=tvos - ;; - - watchOS) - xcb_flags=("${watchos_flags[@]}") - ;; - - visionOS) - xcb_flags=("${visionos_flags[@]}") - ;; - - catalyst) - xcb_flags=("${catalyst_flags[@]}") - ;; - - all) - xcb_flags=() - ;; - - Linux) - xcb_flags=() - ;; - - *) - echo "Unknown platform '$platform'" 1>&2 - exit 1 - ;; -esac - -xcb_flags+=( - ONLY_ACTIVE_ARCH=YES - CODE_SIGNING_REQUIRED=NO - CODE_SIGNING_ALLOWED=YES - COMPILER_INDEX_STORE_ENABLE=NO -) - -source scripts/buildcache.sh -xcb_flags=("${xcb_flags[@]}" "${buildcache_xcb_flags[@]}") - -# TODO(varconst): Add --warn-unused-vars and --warn-uninitialized. -# Right now, it makes the log overflow on Travis because many of our -# dependencies don't build cleanly this way. -cmake_options=( - -Wdeprecated - -DCMAKE_BUILD_TYPE=Debug -) - -if [[ -n "${SANITIZERS:-}" ]]; then - for sanitizer in $SANITIZERS; do - case "$sanitizer" in - asan) - xcb_flags+=( - -enableAddressSanitizer YES - ) - cmake_options+=( - -DWITH_ASAN=ON - ) - ;; - - tsan) - xcb_flags+=( - -enableThreadSanitizer YES - ) - cmake_options+=( - -DWITH_TSAN=ON - ) - ;; - - ubsan) - xcb_flags+=( - -enableUndefinedBehaviorSanitizer YES - ) - cmake_options+=( - -DWITH_UBSAN=ON - ) - ;; - - *) - echo "Unknown sanitizer '$sanitizer'" 1>&2 - exit 1 - ;; - esac - done -fi - - -case "$product-$platform-$method" in - FirebasePod-iOS-*) - RunXcodebuild \ - -workspace 'CoreOnly/Tests/FirebasePodTest/FirebasePodTest.xcworkspace' \ - -scheme "FirebasePodTest" \ - "${xcb_flags[@]}" \ - build - ;; - - Auth-*-xcodebuild) - if check_secrets; then - RunXcodebuild \ - -workspace 'FirebaseAuth/Tests/Sample/AuthSample.xcworkspace' \ - -scheme "Auth_ApiTests" \ - "${xcb_flags[@]}" \ - test - - RunXcodebuild \ - -workspace 'FirebaseAuth/Tests/Sample/AuthSample.xcworkspace' \ - -scheme "SwiftApiTests" \ - "${xcb_flags[@]}" \ - test - fi - ;; - - CombineSwift-*-xcodebuild) - pod_gen FirebaseCombineSwift.podspec --platforms=ios - RunXcodebuild \ - -workspace 'gen/FirebaseCombineSwift/FirebaseCombineSwift.xcworkspace' \ - -scheme "FirebaseCombineSwift-Unit-unit" \ - "${xcb_flags[@]}" \ - build \ - test - ;; - - InAppMessaging-*-xcodebuild) - RunXcodebuild \ - -workspace 'FirebaseInAppMessaging/Tests/Integration/DefaultUITestApp/InAppMessagingDisplay-Sample.xcworkspace' \ - -scheme 'FiamDisplaySwiftExample' \ - "${xcb_flags[@]}" \ - test - ;; - - Firestore-*-xcodebuild) - "${firestore_emulator}" start - trap '"${firestore_emulator}" stop' ERR EXIT - - RunXcodebuild \ - -workspace 'Firestore/Example/Firestore.xcworkspace' \ - -scheme "Firestore_IntegrationTests_$platform" \ - -enableCodeCoverage YES \ - "${xcb_flags[@]}" \ - test - ;; - - Firestore-macOS-cmake | Firestore-Linux-cmake) - "${firestore_emulator}" start - trap '"${firestore_emulator}" stop' ERR EXIT - - ( - test -d build || mkdir build - cd build - - echo "Preparing cmake build ..." - cmake -G Ninja "${cmake_options[@]}" .. - - echo "Building cmake build ..." - ninja -k 10 all - ctest --verbose - ) - ;; - - SymbolCollision-*-*) - RunXcodebuild \ - -workspace 'SymbolCollisionTest/SymbolCollisionTest.xcworkspace' \ - -scheme "SymbolCollisionTest" \ - "${xcb_flags[@]}" \ - build - ;; - - # TODO(#12205) Restore this test to "test" instead of "build" - Messaging-*-xcodebuild) - pod_gen FirebaseMessaging.podspec --platforms=ios - - # Add GoogleService-Info.plist to generated Test Wrapper App. - ruby ./scripts/update_xcode_target.rb gen/FirebaseMessaging/Pods/Pods.xcodeproj \ - AppHost-FirebaseMessaging-Unit-Tests \ - ../../../FirebaseMessaging/Tests/IntegrationTests/Resources/GoogleService-Info.plist - - if check_secrets; then - # Integration tests are only run on iOS to minimize flake failures. - RunXcodebuild \ - -workspace 'gen/FirebaseMessaging/FirebaseMessaging.xcworkspace' \ - -scheme "FirebaseMessaging-Unit-integration" \ - "${ios_flags[@]}" \ - "${xcb_flags[@]}" \ - build - fi - ;; - - MessagingSample-*-*) - if check_secrets; then - RunXcodebuild \ - -workspace 'FirebaseMessaging/Apps/Sample/Sample.xcworkspace' \ - -scheme "Sample" \ - "${xcb_flags[@]}" \ - build - fi - ;; - - SwiftUISample-*-*) - if check_secrets; then - RunXcodebuild \ - -workspace 'FirebaseMessaging/Apps/SwiftUISample/SwiftUISample.xcworkspace' \ - -scheme "SwiftUISample" \ - "${xcb_flags[@]}" \ - build - fi - ;; - - MessagingSampleStandaloneWatchApp-*-*) - if check_secrets; then - RunXcodebuild \ - -workspace 'FirebaseMessaging/Apps/SampleStandaloneWatchApp/SampleStandaloneWatchApp.xcworkspace' \ - -scheme "SampleStandaloneWatchApp Watch App" \ - "${xcb_flags[@]}" \ - build - fi - ;; - - MLModelDownloaderSample-*-*) - if check_secrets; then - RunXcodebuild \ - -workspace 'FirebaseMLModelDownloader/Apps/Sample/MLDownloaderTestApp.xcworkspace' \ - -scheme "MLDownloaderTestApp" \ - "${xcb_flags[@]}" \ - build - fi - ;; - - WatchOSSample-*-*) - RunXcodebuild \ - -workspace 'Example/watchOSSample/SampleWatchApp.xcworkspace' \ - -scheme "SampleWatchAppWatchKitApp" \ - "${xcb_flags[@]}" \ - build - ;; - - Database-*-integration) - "${database_emulator}" start - trap '"${database_emulator}" stop' ERR EXIT - pod_gen FirebaseDatabase.podspec --platforms="${gen_platform}" - - RunXcodebuild \ - -workspace 'gen/FirebaseDatabase/FirebaseDatabase.xcworkspace' \ - -scheme "FirebaseDatabase-Unit-integration" \ - "${xcb_flags[@]}" \ - build \ - test - ;; - - RemoteConfig-*-fakeconsole) - pod_gen FirebaseRemoteConfigSwift.podspec --platforms="${gen_platform}" - - RunXcodebuild \ - -workspace 'gen/FirebaseRemoteConfigSwift/FirebaseRemoteConfigSwift.xcworkspace' \ - -scheme "FirebaseRemoteConfigSwift-Unit-fake-console-tests" \ - "${xcb_flags[@]}" \ - build \ - test - ;; - - RemoteConfig-*-integration) - pod_gen FirebaseRemoteConfigSwift.podspec --platforms="${gen_platform}" - - # Add GoogleService-Info.plist to generated Test Wrapper App. - ruby ./scripts/update_xcode_target.rb gen/FirebaseRemoteConfigSwift/Pods/Pods.xcodeproj \ - AppHost-FirebaseRemoteConfigSwift-Unit-Tests \ - ../../../FirebaseRemoteConfigSwift/Tests/SwiftAPI/GoogleService-Info.plist - - # Add AccessToken to generated Test Wrapper App. - ruby ./scripts/update_xcode_target.rb gen/FirebaseRemoteConfigSwift/Pods/Pods.xcodeproj \ - AppHost-FirebaseRemoteConfigSwift-Unit-Tests \ - ../../../FirebaseRemoteConfigSwift/Tests/AccessToken.json - - RunXcodebuild \ - -workspace 'gen/FirebaseRemoteConfigSwift/FirebaseRemoteConfigSwift.xcworkspace' \ - -scheme "FirebaseRemoteConfigSwift-Unit-swift-api-tests" \ - "${xcb_flags[@]}" \ - build \ - test - ;; - - RemoteConfigSample-*-*) - RunXcodebuild \ - -workspace 'FirebaseRemoteConfig/Tests/Sample/RemoteConfigSampleApp.xcworkspace' \ - -scheme "RemoteConfigSampleApp" \ - "${xcb_flags[@]}" \ - build - ;; - - Sessions-*-integration) - # Perform "pod install" to install the relevant dependencies - # ./FirebaseSessions/generate_testapp.sh - pod_gen FirebaseSessions.podspec --platforms=ios --clean - cd FirebaseSessions/Tests/TestApp; pod install; cd - - - # Run E2E Integration Tests for Prod. - RunXcodebuild \ - -workspace 'FirebaseSessions/Tests/TestApp/AppQualityDevApp.xcworkspace' \ - -scheme "AppQualityDevApp_iOS" \ - "${ios_flags[@]}" \ - "${xcb_flags[@]}" \ - build \ - test - - # Run E2E Integration Tests for Staging. - RunXcodebuild \ - -workspace 'FirebaseSessions/Tests/TestApp/AppQualityDevApp.xcworkspace' \ - -scheme "AppQualityDevApp_iOS" \ - FirebaseSessionsRunEnvironment=STAGING \ - "${ios_flags[@]}" \ - "${xcb_flags[@]}" \ - build \ - test - - # Run E2E Integration Tests for Autopush. - RunXcodebuild \ - -workspace 'FirebaseSessions/Tests/TestApp/AppQualityDevApp.xcworkspace' \ - -scheme "AppQualityDevApp_iOS" \ - FirebaseSessionsRunEnvironment=AUTOPUSH \ - "${ios_flags[@]}" \ - "${xcb_flags[@]}" \ - build \ - test - ;; - - StorageSwift-*-xcodebuild) - pod_gen FirebaseStorage.podspec --platforms=ios - - # Add GoogleService-Info.plist to generated Test Wrapper App. - ruby ./scripts/update_xcode_target.rb gen/FirebaseStorage/Pods/Pods.xcodeproj \ - AppHost-FirebaseStorage-Unit-Tests \ - ../../../FirebaseStorage/Tests/Integration/Resources/GoogleService-Info.plist - - if check_secrets; then - # Integration tests are only run on iOS to minimize flake failures. - RunXcodebuild \ - -workspace 'gen/FirebaseStorage/FirebaseStorage.xcworkspace' \ - -scheme "FirebaseStorage-Unit-integration" \ - "${ios_flags[@]}" \ - "${xcb_flags[@]}" \ - test - fi - ;; - - StorageObjC-*-xcodebuild) - pod_gen FirebaseStorage.podspec --platforms=ios - - # Add GoogleService-Info.plist to generated Test Wrapper App. - ruby ./scripts/update_xcode_target.rb gen/FirebaseStorage/Pods/Pods.xcodeproj \ - AppHost-FirebaseStorage-Unit-Tests \ - ../../../FirebaseStorage/Tests/Integration/Resources/GoogleService-Info.plist - - if check_secrets; then - # Integration tests are only run on iOS to minimize flake failures. - RunXcodebuild \ - -workspace 'gen/FirebaseStorage/FirebaseStorage.xcworkspace' \ - -scheme "FirebaseStorage-Unit-ObjCIntegration" \ - "${ios_flags[@]}" \ - "${xcb_flags[@]}" \ - test - fi - ;; - - StorageCombine-*-xcodebuild) - pod_gen FirebaseCombineSwift.podspec --platforms=ios - - # Add GoogleService-Info.plist to generated Test Wrapper App. - ruby ./scripts/update_xcode_target.rb gen/FirebaseCombineSwift/Pods/Pods.xcodeproj \ - AppHost-FirebaseCombineSwift-Unit-Tests \ - ../../../FirebaseStorage/Tests/Integration/Resources/GoogleService-Info.plist - - if check_secrets; then - # Integration tests are only run on iOS to minimize flake failures. - RunXcodebuild \ - -workspace 'gen/FirebaseCombineSwift/FirebaseCombineSwift.xcworkspace' \ - -scheme "FirebaseCombineSwift-Unit-integration" \ - "${ios_flags[@]}" \ - "${xcb_flags[@]}" \ - test - fi - ;; - - GoogleDataTransport-watchOS-xcodebuild) - RunXcodebuild \ - -workspace 'GoogleDataTransport/GDTWatchOSTestApp/GDTWatchOSTestApp.xcworkspace' \ - -scheme "GDTWatchOSTestAppWatchKitApp" \ - "${xcb_flags[@]}" \ - build - - RunXcodebuild \ - -workspace 'GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSTestApp.xcworkspace' \ - -scheme "GDTCCTWatchOSIndependentTestAppWatchKitApp" \ - "${xcb_flags[@]}" \ - build - - RunXcodebuild \ - -workspace 'GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSTestApp.xcworkspace' \ - -scheme "GDTCCTWatchOSCompanionTestApp" \ - "${xcb_flags[@]}" \ - build - ;; - - Performance-*-unit) - # Run unit tests on prod environment with unswizzle capabilities. - export FPR_UNSWIZZLE_AVAILABLE="1" - export FPR_AUTOPUSH_ENV="0" - pod_gen FirebasePerformance.podspec --platforms="${gen_platform}" - RunXcodebuild \ - -workspace 'gen/FirebasePerformance/FirebasePerformance.xcworkspace' \ - -scheme "FirebasePerformance-Unit-unit" \ - "${xcb_flags[@]}" \ - build \ - test - ;; - - Performance-*-proddev) - # Build the prod dev test app. - export FPR_UNSWIZZLE_AVAILABLE="0" - export FPR_AUTOPUSH_ENV="0" - pod_gen FirebasePerformance.podspec --platforms="${gen_platform}" - RunXcodebuild \ - -workspace 'gen/FirebasePerformance/FirebasePerformance.xcworkspace' \ - -scheme "FirebasePerformance-TestApp" \ - "${xcb_flags[@]}" \ - build - ;; - - Performance-*-integration) - # Generate the workspace for the SDK to generate Protobuf files. - export FPR_UNSWIZZLE_AVAILABLE="0" - pod_gen FirebasePerformance.podspec --platforms=ios --clean - - # Perform "pod install" to install the relevant dependencies - cd FirebasePerformance/Tests/FIRPerfE2E; pod install; cd - - - # Run E2E Integration Tests for Autopush. - RunXcodebuild \ - -workspace 'FirebasePerformance/Tests/FIRPerfE2E/FIRPerfE2E.xcworkspace' \ - -scheme "FIRPerfE2EAutopush" \ - FPR_AUTOPUSH_ENV=1 \ - "${ios_flags[@]}" \ - "${xcb_flags[@]}" \ - build \ - test - - # Run E2E Integration Tests for Prod. - RunXcodebuild \ - -workspace 'FirebasePerformance/Tests/FIRPerfE2E/FIRPerfE2E.xcworkspace' \ - -scheme "FIRPerfE2EProd" \ - "${ios_flags[@]}" \ - "${xcb_flags[@]}" \ - build \ - test - ;; - - # Note that the combine tests require setting the minimum iOS and tvOS version to 13.0 - *-*-spm) - RunXcodebuild \ - -scheme $product \ - "${xcb_flags[@]}" \ - IPHONEOS_DEPLOYMENT_TARGET=13.0 \ - TVOS_DEPLOYMENT_TARGET=13.0 \ - test - ;; - - *-*-spmbuildonly) - RunXcodebuild \ - -scheme $product \ - "${xcb_flags[@]}" \ - build - ;; - - ClientApp-iOS-xcodebuild | ClientApp-iOS13-iOS-xcodebuild) - RunXcodebuild \ - -project 'IntegrationTesting/ClientApp/ClientApp.xcodeproj' \ - -scheme $product \ - "${xcb_flags[@]}" \ - build - ;; - - ClientApp-CocoaPods*-iOS-xcodebuild) - RunXcodebuild \ - -workspace 'IntegrationTesting/ClientApp/ClientApp.xcworkspace' \ - -scheme $product \ - "${xcb_flags[@]}" \ - build - ;; - - *) - - echo "Don't know how to build this product-platform-method combination" 1>&2 - echo " product=$product" 1>&2 - echo " platform=$platform" 1>&2 - echo " method=$method" 1>&2 - exit 1 - ;; - -esac diff --git a/scripts/build_non_firebase_sdks.sh b/scripts/build_non_firebase_sdks.sh deleted file mode 100755 index d3544e1..0000000 --- a/scripts/build_non_firebase_sdks.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash - -# Copyright 2020 Google LLC -# -# 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. - -set -ex - -cd "${REPO}"/ReleaseTooling - -# This file will have non Firebase SDKs that will be built by ZipBuilder. -ZIP_POD_JSON="non_firebase_sdk.json" -rm -f "${ZIP_POD_JSON}" -IFS=' ,' read -a NON_FIREBASE_SDKS <<< "${NON_FIREBASE_SDKS}" - -num_sdk="${#NON_FIREBASE_SDKS[@]}" -echo "[" >> "${ZIP_POD_JSON}" -for sdk in "${NON_FIREBASE_SDKS[@]}" -do - echo "{\"name\":\"${sdk}\"}" >> "${ZIP_POD_JSON}" - if [ "$num_sdk" -ne 1 ]; then - echo ",">> "${ZIP_POD_JSON}" - fi - num_sdk=$((num_sdk-1)) -done -echo "]" >> "${ZIP_POD_JSON}" -mkdir -p "${REPO}"/sdk_zip -swift run zip-builder --keep-build-artifacts --update-pod-repo --platforms ios \ - --zip-pods "${ZIP_POD_JSON}" --output-dir "${REPO}"/sdk_zip --disable-build-dependencies - -unzip -o "${REPO}"/sdk_zip/Frameworks.zip -d "${HOME}"/ios_frameworks/Firebase/ - -# Move Frameworks to Firebase dir, so be align with Firebase SDKs. -mv -n "${HOME}"/ios_frameworks/Firebase/Binaries "${HOME}"/ios_frameworks/Firebase/NonFirebaseSDKs/ diff --git a/scripts/build_zip.sh b/scripts/build_zip.sh deleted file mode 100755 index c71579c..0000000 --- a/scripts/build_zip.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash - -# Copyright 2020 Google LLC -# 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. - -set -x -REPO=`pwd` - -if [[ $# -lt 2 ]]; then - cat 2>&2 < [String: String] { - var headerMap = [String: String]() - for root in findHeaders { - let rootURL = url.appendingPathComponent(root) - let enumerator = FileManager.default.enumerator(atPath: rootURL.path) - while let file = enumerator?.nextObject() as? String { - if let fType = enumerator?.fileAttributes?[FileAttributeKey.type] as? FileAttributeType, - fType == .typeRegular { - if let url = URL(string: file) { - let filename = url.lastPathComponent - if filename.hasSuffix(".h") { - headerMap[filename] = root + "/" + file - } - } - } - } - } - return headerMap -} - -func getImportFile(_ line: String) -> String? { - return line.components(separatedBy: " ")[1] - .replacingOccurrences(of: "\"", with: "") - .replacingOccurrences(of: "<", with: "") - .replacingOccurrences(of: ">", with: "") - .components(separatedBy: "/").last -} - -func transformFile(_ file: String) { - var fileContents = "" - do { - fileContents = try String(contentsOfFile: file, encoding: .utf8) - } catch { - print("Could not read \(file). \(error)") - // Not a source file, give up and return. - return - } - var outBuffer = "" - var inSwiftPackage = false - let lines = fileContents.components(separatedBy: .newlines) - for line in lines { - if line.starts(with: "#if SWIFT_PACKAGE") { - inSwiftPackage = true - } else if inSwiftPackage, line.starts(with: "#else") { - inSwiftPackage = false - } else if line.starts(with: "@import") { - if !inSwiftPackage { - fatalError("@import should not be used in CocoaPods library code: \(file):\(line)") - } - } - if line.starts(with: "#import"), - let importFile = getImportFile(line), - let path = headerMap[importFile] { - outBuffer += "#import \"\(path)\"\n" - } else if line.starts(with: "#include"), - let importFile = getImportFile(line), - let path = headerMap[importFile] { - outBuffer += "#include \"\(path)\"\n" - } else { - outBuffer += line + "\n" - } - } - // Write out the changed file. - do { - try outBuffer.dropLast() - .write(toFile: file, atomically: false, encoding: String.Encoding.utf8) - } catch { - fatalError("Failed to write \(file). \(error)") - } -} - -// Search the path upwards to find the root of the firebase-ios-sdk repo. -var url = URL(fileURLWithPath: FileManager().currentDirectoryPath) -while url.path != "/", url.lastPathComponent != "firebase-ios-sdk" { - url = url.deletingLastPathComponent() -} - -print(url) - -// Build map of all headers. - -let headerMap = getHeaderMap(url) - -// print(headerMap) - -for root in changeImports { - let rootURL = url.appendingPathComponent(root) - let enumerator = FileManager.default.enumerator(atPath: rootURL.path) - whileLoop: while let file = enumerator?.nextObject() as? String { - if let fType = enumerator?.fileAttributes?[FileAttributeKey.type] as? FileAttributeType, - fType == .typeRegular { - if file.starts(with: ".") { - continue - } - if !(file.hasSuffix(".h") || - file.hasSuffix(".m") || - file.hasSuffix(".mm") || - file.hasSuffix(".c")) { - continue - } - if file.range(of: "/Public/") != nil { - continue - } - let fullTransformPath = root + "/" + file - for dirPattern in skipDirPatterns { - if fullTransformPath.range(of: dirPattern) != nil { - continue whileLoop - } - } - transformFile(fullTransformPath) - } - } -} diff --git a/scripts/check.sh b/scripts/check.sh deleted file mode 100755 index 2cd2e12..0000000 --- a/scripts/check.sh +++ /dev/null @@ -1,300 +0,0 @@ -#!/bin/bash - -# Copyright 2019 Google -# -# 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. - -# Checks that the current state of the tree is sane and optionally auto-fixes -# errors automatically. Meant for interactive use. - -function usage() { - cat <] - -Runs auto-formatting scripts, source-tree checks, and linters on any files that -have changed since origin/main. - -By default, any changes are left as uncommited changes in the working tree. You -can review them with git diff. Pass --commit to automatically commit any changes. - -Pass an alternate revision to use as the basis for checking changes. - -OPTIONS: - - --allow-dirty - By default, check.sh requires a clean working tree to keep any generated - changes separate from logical changes. - - --commit - Commit any auto-generated changes with a message indicating which tool made - the changes. - - --amend - Commit any auto-generated changes by amending the HEAD commit. - - --fixup - Commit any auto-generated changes with a fixup! message for the HEAD - commit. The next rebase will squash these fixup commits. - - --test-only - Run all checks without making any changes to local files. - - - Specifies a starting revision other than the default of origin/main. - - -EXAMPLES: - - check.sh - Runs automated checks and formatters on all changed files since - origin/main. Check for changes with git diff. - - check.sh --commit - Runs automated checks and formatters on all changed files since - origin/main and commits the results. - - check.sh --amend HEAD - Runs automated checks and formatters on all changed files since the last - commit and amends the last commit with the difference. - - check.sh --allow-dirty HEAD - Runs automated checks and formatters on all changed files since the last - commit and intermingles the changes with any pending changes. Useful for - interactive use from an editor. - -EOF -} -set -euo pipefail -unset CDPATH - -# Change to the top-directory of the working tree -top_dir=$(git rev-parse --show-toplevel) -cd "${top_dir}" - -ALLOW_DIRTY=false -COMMIT_METHOD="none" -CHECK_DIFF=true -START_REVISION="origin/main" -TEST_ONLY=false -VERBOSE=false - -# Default to verbose operation if this isn't an interactive build. -if [[ ! -t 1 ]]; then - VERBOSE=true -fi - -# When travis clones a repo for building, it uses a shallow clone. After the -# first commit on a non-main branch, TRAVIS_COMMIT_RANGE is not set, main -# is not available and we need to compute the START_REVISION from the common -# ancestor of $TRAVIS_COMMIT and origin/main. -if [[ -n "${TRAVIS_COMMIT_RANGE:-}" ]] ; then - CHECK_DIFF=true - START_REVISION="$TRAVIS_COMMIT_RANGE" -elif [[ -n "${TRAVIS_COMMIT:-}" ]] ; then - if ! git rev-parse origin/main >& /dev/null; then - git remote set-branches --add origin main - git fetch origin - fi - CHECK_DIFF=true - START_REVISION=$(git merge-base origin/main "${TRAVIS_COMMIT}") -fi - -while [[ $# -gt 0 ]]; do - case "$1" in - --) - # Do nothing: explicitly allow this, but ignore it - ;; - - -h | --help) - usage - exit 1 - ;; - - --allow-dirty) - ALLOW_DIRTY=true - ;; - - --amend) - COMMIT_METHOD=amend - ;; - - --fixup) - COMMIT_METHOD=fixup - ;; - - --commit) - COMMIT_METHOD=message - ;; - - --verbose) - VERBOSE=true - ;; - - --test-only) - # In test-only mode, no changes are made, so there's no reason to - # require a clean source tree. - ALLOW_DIRTY=true - TEST_ONLY=true - ;; - - *) - START_REVISION="$1" - shift - break - ;; - esac - shift -done - -if [[ "${TEST_ONLY}" == true && "${COMMIT_METHOD}" != "none" ]]; then - echo "--test-only cannot be combined with --amend, --fixup, or --commit" - exit 1 -fi - -if [[ "${ALLOW_DIRTY}" == true && "${COMMIT_METHOD}" == "message" ]]; then - echo "--allow-dirty and --commit are mutually exclusive" - exit 1 -fi - -if ! git diff-index --quiet HEAD --; then - if [[ "${ALLOW_DIRTY}" != true ]]; then - echo "You have local changes that could be overwritten by this script." - echo "Please commit your changes first or pass --allow-dirty." - exit 2 - fi -fi - -# Show Travis-related environment variables, to help with debuging failures. -if [[ "${VERBOSE}" == true ]]; then - env | egrep '^TRAVIS_(BRANCH|COMMIT|PULL|REPO)' | sort || true -fi - -if [[ "${START_REVISION}" == *..* ]]; then - RANGE_START="${START_REVISION/..*/}" - RANGE_END="${START_REVISION/*../}" - - # Figure out if we have access to main. If not add it to the repo. - if ! git rev-parse origin/main >& /dev/null; then - git remote set-branches --add origin main - git fetch origin - fi - - # Try to come up with a more accurate representation of the merge, so that - # checks will operate on just the differences the PR would merge into main. - # The start of the revision range that Travis supplies can sometimes be a - # seemingly random value. - NEW_RANGE_START=$(git merge-base origin/main "${RANGE_END}" || echo "") - if [[ -n "$NEW_RANGE_START" ]]; then - START_REVISION="${NEW_RANGE_START}..${RANGE_END}" - START_SHA="${START_REVISION}" - else - # In the shallow clone that Travis has created there's no merge base - # between the PR and main. In this case just fall back on checking - # everything. - echo "Unable to detect base commit for change detection." - echo "Falling back on just checking everything." - CHECK_DIFF=false - START_REVISION="origin/main" - START_SHA="origin/main" - fi - -else - START_SHA=$(git rev-parse "${START_REVISION}") -fi - -if [[ "${VERBOSE}" == true ]]; then - echo "START_REVISION=$START_REVISION" - echo "START_SHA=$START_SHA" -fi - -# If committing --fixup, avoid messages with fixup! fixup! that might come from -# multiple fixup commits. -HEAD_SHA=$(git rev-parse HEAD) - -function maybe_commit() { - local message="$1" - - if [[ "${COMMIT_METHOD}" == "none" ]]; then - return - fi - - echo "${message}" - case "${COMMIT_METHOD}" in - amend) - git commit -a --amend -C "${HEAD_SHA}" - ;; - - fixup) - git commit -a --fixup="${HEAD_SHA}" - ;; - - message) - git commit -a -m "${message}" - ;; - - *) - echo "Unknown commit method ${COMMIT_METHOD}" 1>&2 - exit 2 - ;; - esac -} - -style_cmd=("${top_dir}/scripts/style.sh") -if [[ "${TEST_ONLY}" == true ]]; then - style_cmd+=(test-only) -fi -if [[ "$CHECK_DIFF" == true ]]; then - style_cmd+=("${START_SHA}") -fi - -# Restyle and commit any changes -"${style_cmd[@]}" -if ! git diff --quiet; then - maybe_commit "style.sh generated changes" -fi - -# If there are changes to the Firestore project, ensure they're ordered -# correctly to minimize conflicts. -if [ -z "${GITHUB_WORKFLOW-}" ]; then - if [[ "$CHECK_DIFF" == "false" ]] || \ - ! git diff --quiet "${START_SHA}" -- Firestore; then - - sync_project_cmd=("${top_dir}/scripts/sync_project.rb") - if [[ "${TEST_ONLY}" == true ]]; then - sync_project_cmd+=(--test-only) - fi - "${sync_project_cmd[@]}" - if ! git diff --quiet; then - maybe_commit "sync_project.rb generated changes" - fi - fi -fi - -set -x - -# Print the versions of tools being used. -python --version - -# Check lint errors. -"${top_dir}/scripts/check_whitespace.sh" -"${top_dir}/scripts/check_filename_spaces.sh" -"${top_dir}/scripts/check_copyright.sh" -"${top_dir}/scripts/check_test_inclusion.py" -"${top_dir}/scripts/check_imports.swift" - -# Google C++ style -lint_cmd=("${top_dir}/scripts/check_lint.py") -if [[ "$CHECK_DIFF" == true ]]; then - lint_cmd+=("${START_SHA}") -fi -"${lint_cmd[@]}" diff --git a/scripts/check_copyright.sh b/scripts/check_copyright.sh deleted file mode 100755 index 2624720..0000000 --- a/scripts/check_copyright.sh +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright 2018 Google -# -# 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. - -# Check source files for copyright notices - -options=( - -E # Use extended regexps - -I # Exclude binary files - -L # Show files that don't have a match - 'Copyright [0-9]{4}.*Google LLC' -) - -list=$(git grep "${options[@]}" -- \ - '*.'{c,cc,cmake,h,js,m,mm,py,rb,sh,swift} \ - CMakeLists.txt '**/CMakeLists.txt' \ - ':(exclude)**/third_party/**') - -# Allow copyrights before 2020 without LLC. -if [[ $list ]]; then - result=$(grep -L 'Copyright 20[0-1][0-9].*Google' "$list") -fi - -if [[ $result ]]; then - echo "$result" - echo "ERROR: Missing copyright notices in the files above. Please fix." - exit 1 -fi diff --git a/scripts/check_filename_spaces.sh b/scripts/check_filename_spaces.sh deleted file mode 100755 index 5e48c48..0000000 --- a/scripts/check_filename_spaces.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash - -# Copyright 2019 Google -# -# 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. - -# Fail on spaces in file names, excluding the patterns listed below. - -# A sed program that removes filename patterns that are allowed to have spaces -# in them. -function remove_valid_names() { - sed ' - # Xcode-generated asset files - /Assets.xcassets/ d - - # Files without spaces - /^[^ ]*$/ d - ' -} - -count=$(git ls-files | remove_valid_names | wc -l | xargs) - -if [[ ${count} != 0 ]]; then - echo 'ERROR: Spaces in filenames are not permitted in this repo. Please fix.' - echo '' - - git ls-files | remove_valid_names - exit 1 -fi diff --git a/scripts/check_firestore_symbols.sh b/scripts/check_firestore_symbols.sh deleted file mode 100755 index 5fb2de7..0000000 --- a/scripts/check_firestore_symbols.sh +++ /dev/null @@ -1,171 +0,0 @@ -#!/bin/bash - -# Copyright 2023 Google LLC -# -# 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. - -# DESCRIPTION: This script identifies Objective-C symbols within the -# `FirebaseFirestoreInternal.xcframework` that are not automatically linked -# when used in a client target. Because the -# `FirebaseFirestoreInternal.xcframework` should function without clients -# needing to pass the `-ObjC` flag, this script catches potential regressions -# that break that requirement. -# -# DEPENDENCIES: This script depends on the given Firebase repo's `Package.swift` -# using the `FIREBASECI_USE_LOCAL_FIRESTORE_ZIP` env var to swap the Firestore -# target definition out to instead reference a *local* binary using the -# `.binaryTarget(path:)` API. -# -# DESIGN: This script creates an executable package that depends on Firestore -# via a local binary SPM target. The package is built twice, once with the -# -ObjC flag and once without. The linked Objective-C symbols are then -# stripped from each build's resulting executable. The symbols are then diffed -# to determine if there exists symbols that were only linked due to the -ObjC -# flag. -# -# USAGE: ./check_firestore_symbols.sh - -set -euo pipefail - -if [[ $# -ne 2 ]]; then - echo "Usage: ./check_firestore_symbols.sh " - exit 1 -fi - -# Check if the given repo path is valid. -FIREBASE_REPO_PATH=$1 - -if [[ "$FIREBASE_REPO_PATH" != /* ]]; then - echo "The given path should be an absolute path." - exit 1 -fi - -if [[ ! -d "$FIREBASE_REPO_PATH" ]]; then - echo "The given repo does not exist: $FIREBASE_REPO_PATH" - exit 1 -fi - -# Check if the given xcframework path is valid. -FIRESTORE_XCFRAMEWORK_PATH=$2 - -if [ "$(basename $FIRESTORE_XCFRAMEWORK_PATH)" != 'FirebaseFirestoreInternal.xcframework' ]; then - echo "The given xcframework is not a FirebaseFirestoreInternal.xcframework." - exit 1 -fi - -if [[ ! -d "$FIRESTORE_XCFRAMEWORK_PATH" ]]; then - echo "The given xcframework does not exist: $FIRESTORE_XCFRAMEWORK_PATH" - exit 1 -fi - -# Copy the given Firestore framework to the root of the given Firebase repo. -# This script uses an env var that will alter the repo's `Package.swift` to -# pick up the copied Firestore framework. See -# `FIREBASECI_USE_LOCAL_FIRESTORE_ZIP` in Firebase's `Package.swift` for more. -cp -r "$FIRESTORE_XCFRAMEWORK_PATH" "$FIREBASE_REPO_PATH" - -# Create a temporary directory for the test package. The test package defines an -# executable and has the following directory structure: -# -# TestPkg -# ├── Package.swift -# └── Sources -#    └── TestPkg -#     └── main.swift -TEST_PKG_ROOT=$(mktemp -d -t TestPkg) -echo "Test package root: $TEST_PKG_ROOT" - -# Create the package's subdirectories. -mkdir -p "$TEST_PKG_ROOT/Sources/TestPkg" - -# Generate the package's `Package.swift`. -cat > "$TEST_PKG_ROOT/Package.swift" <<- EOM -// swift-tools-version: 5.6 -import PackageDescription - -let package = Package( - name: "TestPkg", - platforms: [.macOS(.v10_13)], - dependencies: [ - .package(path: "${FIREBASE_REPO_PATH}") - ], - targets: [ - .executableTarget( - name: "TestPkg", - dependencies: [ - .product( - name: "FirebaseFirestore", - package: "firebase-ios-sdk" - ) - ] - ) - ] -) -EOM - -# Generate the package's `main.swift`. -cat > "$TEST_PKG_ROOT/Sources/TestPkg/main.swift" <<- EOM -import FirebaseFirestore - -let db = Firestore.firestore() -EOM - -# Change to the test package's root directory in order to build the package. -cd "$TEST_PKG_ROOT" - -# Build the test package *without* the `-ObjC` linker flag, and dump the -# resulting executable file's Objective-C symbols into a text file. -echo "Building test package without -ObjC linker flag..." -FIREBASECI_USE_LOCAL_FIRESTORE_ZIP=1 xcodebuild -scheme 'TestPkg' \ - -destination 'generic/platform=macOS' \ - -derivedDataPath "$HOME/Library/Developer/Xcode/DerivedData/TestPkg" \ - | xcpretty - -nm ~/Library/Developer/Xcode/DerivedData/TestPkg/Build/Products/Debug/TestPkg \ - | grep -o "[-+]\[.*\]" > objc_symbols_without_linker_flag.txt - -# Build the test package *with* the -ObjC linker flag, and dump the -# resulting executable file's Objective-C symbols into a text file. -echo "Building test package with -ObjC linker flag..." -FIREBASECI_USE_LOCAL_FIRESTORE_ZIP=1 xcodebuild -scheme 'TestPkg' \ - -destination 'generic/platform=macOS' \ - -derivedDataPath "$HOME/Library/Developer/Xcode/DerivedData/TestPkg-ObjC" \ - OTHER_LDFLAGS='-ObjC' \ - | xcpretty - -nm ~/Library/Developer/Xcode/DerivedData/TestPkg-ObjC/Build/Products/Debug/TestPkg \ - | grep -o "[-+]\[.*\]" > objc_symbols_with_linker_flag.txt - -# Compare the two text files to see if the -ObjC linker flag caused additional -# symbols to link. -# -# Note: In the case where the diff is non-empty, the diff command will -# return exit code 1, which will cause the set pipefail to terminate execution. -# To avoid this, `|| true` ensures the exit code always indicates success. -DIFF=$( - git diff --no-index --output-indicator-new="?" \ - objc_symbols_without_linker_flag.txt \ - objc_symbols_with_linker_flag.txt \ - || true -) -if [[ -n "$DIFF" ]]; then - echo "Failure: Unlinked Objective-C symbols have been detected:" - echo "$DIFF" - echo -n "💡 To fix, follow the process shown in " - echo -n "https://github.com/firebase/firebase-ios-sdk/pull/12534 for the " - echo "above symbols that are prefixed with ?" - exit 1 -else - echo "Success: No unlinked Objective-C symbols have been detected." - exit 0 -fi diff --git a/scripts/check_imports.swift b/scripts/check_imports.swift deleted file mode 100755 index 04e84ae..0000000 --- a/scripts/check_imports.swift +++ /dev/null @@ -1,234 +0,0 @@ -#!/usr/bin/swift -/* - * Copyright 2020 Google LLC - * - * 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. - */ - -// Utility script for verifying `import` and `include` syntax. This ensures a -// consistent style as well as functionality across multiple package managers. - -// For more context, see https://github.com/firebase/firebase-ios-sdk/blob/main/HeadersImports.md. - -import Foundation - -// Skip these directories. Imports should only be repo-relative in libraries -// and unit tests. -let skipDirPatterns = ["/Sample/", "/Pods/", - "FirebaseDynamicLinks/Tests/Integration", - "FirebaseInAppMessaging/Tests/Integration/", - "SymbolCollisionTest/", "/gen/", - "IntegrationTesting/CocoapodsIntegrationTest/", - "FirebasePerformance/Tests/TestApp/", - "cmake-build-debug/", "build/", "ObjCIntegration/", - "FirebasePerformance/Tests/FIRPerfE2E/"] + - [ - "CoreOnly/Sources", // Skip Firebase.h. - "SwiftPMTests", // The SwiftPM tests test module imports. - "IntegrationTesting/ClientApp", // The ClientApp tests module imports. - "FirebaseSessions/Protogen/", // Generated nanopb code with imports - ] + - - // The following are temporary skips pending working through a first pass of the repo: - [ - "FirebaseDatabase/Sources/third_party/Wrap-leveldb", // Pending SwiftPM for leveldb. - "Example", - "Firestore", - "GoogleUtilitiesComponents", - "FirebasePerformance/ProtoSupport/", - ] - -// Skip existence test for patterns that start with the following: -let skipImportPatterns = [ - "FBLPromise", - "OCMock", - "OCMStubRecorder", -] - -private class ErrorLogger { - var foundError = false - func log(_ message: String) { - print(message) - foundError = true - } - - func importLog(_ message: String, _ file: String, _ line: Int) { - log("Import Error: \(file):\(line) \(message)") - } -} - -private func checkFile(_ file: String, logger: ErrorLogger, inRepo repoURL: URL, - isSwiftFile: Bool) { - var fileContents = "" - do { - fileContents = try String(contentsOfFile: file, encoding: .utf8) - } catch { - logger.log("Could not read \(file). \(error)") - // Not a source file, give up and return. - return - } - - guard !isSwiftFile else { - // Swift specific checks. - fileContents.components(separatedBy: .newlines) - .enumerated() // [(lineNum, line), ...] - .filter { $1.starts(with: "import FirebaseCoreExtension") } - .forEach { lineNum, line in - logger - .importLog( - "Use `@_implementationOnly import FirebaseCoreExtension` when importing `FirebaseCoreExtension`.", - file, lineNum - ) - } - return - } - - let isPublic = file.range(of: "/Public/") != nil && - // TODO: Skip legacy GDTCCTLibrary file that isn't Public and should be moved. - // This test is used in the GoogleDataTransport's repo's CI clone of this repo. - file.range(of: "GDTCCTLibrary/Public/GDTCOREvent+GDTCCTSupport.h") == nil - let isPrivate = file.range(of: "/Sources/Private/") != nil || - // Delete when FirebaseInstallations fixes directory structure. - file.range(of: "Source/Library/Private/FirebaseInstallationsInternal.h") != nil || - file.range(of: "FirebaseCore/Extension") != nil - - // Treat all files with names finishing on "Test" or "Tests" as files with tests. - let isTestFile = file.contains("Test.m") || file.contains("Tests.m") || - file.contains("Test.swift") || file.contains("Tests.swift") - let isBridgingHeader = file.contains("Bridging-Header.h") - var inSwiftPackage = false - var inSwiftPackageElse = false - let lines = fileContents.components(separatedBy: .newlines) - var lineNum = 0 - nextLine: for rawLine in lines { - let line = rawLine.trimmingCharacters(in: .whitespaces) - lineNum += 1 - if line.starts(with: "#if SWIFT_PACKAGE") { - inSwiftPackage = true - } else if inSwiftPackage, line.starts(with: "#else") { - inSwiftPackage = false - inSwiftPackageElse = true - } else if inSwiftPackageElse, line.starts(with: "#endif") { - inSwiftPackageElse = false - } else if inSwiftPackage { - continue - } else if file.contains("FirebaseTestingSupport") { - // Module imports ok in SPM only test infrastructure. - continue - } - - // "The #else of a SWIFT_PACKAGE check should only do CocoaPods module-style imports." - if line.starts(with: "#import") || line.starts(with: "#include") { - let importFile = line.components(separatedBy: " ")[1] - if inSwiftPackageElse { - if importFile.first != "<" { - logger - .importLog("Import in SWIFT_PACKAGE #else should start with \"<\".", file, lineNum) - } - continue - } - let importFileRaw = importFile.replacingOccurrences(of: "\"", with: "") - .replacingOccurrences(of: "<", with: "") - .replacingOccurrences(of: ">", with: "") - - if importFile.first == "\"" { - // Public Headers should only use simple file names without paths. - if isPublic { - if importFile.contains("/") { - logger.importLog("Public header import should not include \"/\"", file, lineNum) - } - - } else if !FileManager.default.fileExists(atPath: repoURL.path + "/" + importFileRaw) { - // Non-public header imports should be repo-relative paths. Unqualified imports are - // allowed in private headers. - if !isPrivate || importFile.contains("/") { - for skip in skipImportPatterns { - if importFileRaw.starts(with: skip) { - continue nextLine - } - } - logger.importLog("Import \(importFileRaw) does not exist.", file, lineNum) - } - } - } else if importFile.first == "<", !isPrivate, !isTestFile, !isBridgingHeader, !isPublic { - // Verify that double quotes are always used for intra-module imports. - if importFileRaw.starts(with: "Firebase"), - // Allow intra-module imports of FirebaseAppCheckInterop. - // TODO: Remove the FirebaseAppCheckInterop exception when it's moved to a separate repo. - importFile.range(of: "FirebaseAppCheckInterop/FirebaseAppCheckInterop.h") == nil { - logger - .importLog("Imports internal to the repo should use double quotes not \"<\"", file, - lineNum) - } - } - } - } -} - -private func main() -> Int32 { - let logger = ErrorLogger() - // Search the path upwards to find the root of the firebase-ios-sdk repo. - var url = URL(fileURLWithPath: FileManager().currentDirectoryPath) - while url.path != "/" { - let script = url.appendingPathComponent("scripts/check_imports.swift") - if FileManager.default.fileExists(atPath: script.path) { - break - } - url = url.deletingLastPathComponent() - } - let repoURL = url - guard let contents = try? FileManager.default.contentsOfDirectory(at: repoURL, - includingPropertiesForKeys: nil, - options: [.skipsHiddenFiles]) - else { - logger.log("Failed to get repo contents \(repoURL)") - return 1 - } - - for rootURL in contents { - if !rootURL.hasDirectoryPath { - continue - } - let enumerator = FileManager.default.enumerator(atPath: rootURL.path) - whileLoop: while let file = enumerator?.nextObject() as? String { - if let fType = enumerator?.fileAttributes?[FileAttributeKey.type] as? FileAttributeType, - fType == .typeRegular { - if file.starts(with: ".") { - continue - } - if !(file.hasSuffix(".h") || - file.hasSuffix(".m") || - file.hasSuffix(".mm") || - file.hasSuffix(".c") || - file.hasSuffix(".swift")) { - continue - } - let fullTransformPath = rootURL.path + "/" + file - for dirPattern in skipDirPatterns { - if fullTransformPath.range(of: dirPattern) != nil { - continue whileLoop - } - } - checkFile( - fullTransformPath, - logger: logger, - inRepo: repoURL, - isSwiftFile: file.hasSuffix(".swift") - ) - } - } - } - return logger.foundError ? 1 : 0 -} - -exit(main()) diff --git a/scripts/check_lint.py b/scripts/check_lint.py deleted file mode 100755 index 3337d1e..0000000 --- a/scripts/check_lint.py +++ /dev/null @@ -1,218 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2019 Google -# -# 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. - -"""Lints source files for conformance with the style guide that applies. - -Currently supports linting Objective-C, Objective-C++, C++, and Python source. -""" - -import argparse -import logging -import os -import subprocess -import sys -import textwrap - -from lib import checker -from lib import command_trace -from lib import git -from lib import source - -_logger = logging.getLogger('lint') - - -_dry_run = False - - -_CPPLINT_OBJC_FILTERS = [ - # Objective-C uses #import and does not use header guards - '-build/header_guard', - - # Inline definitions of Objective-C blocks confuse - '-readability/braces', - - # C-style casts are acceptable in Objective-C++ - '-readability/casting', - - # Objective-C needs use type 'long' for interop between types like NSInteger - # and printf-style functions. - '-runtime/int', - - # cpplint is generally confused by Objective-C mixing with C++. - # * Objective-C method invocations in a for loop make it think its a - # range-for - # * Objective-C dictionary literals confuse brace spacing - # * Empty category declarations ("@interface Foo ()") look like function - # invocations - '-whitespace', -] - -_CPPLINT_OBJC_OPTIONS = [ - # cpplint normally excludes Objective-C++ - '--extensions=h,m,mm', - - # Objective-C style allows longer lines - '--linelength=100', - - '--filter=' + ','.join(_CPPLINT_OBJC_FILTERS), -] - - -def main(): - global _dry_run - - parser = argparse.ArgumentParser(description='Lint source files.') - parser.add_argument('--dry-run', '-n', action='store_true', - help='Show what the linter would do without doing it') - parser.add_argument('--all', action='store_true', - help='run the linter over all known sources') - parser.add_argument('rev_or_files', nargs='*', - help='A single revision that specifies a point in time ' - 'from which to look for changes. Defaults to ' - 'origin/main. Alternatively, a list of specific ' - 'files or git pathspecs to lint.') - args = command_trace.parse_args(parser) - - if args.dry_run: - _dry_run = True - command_trace.enable_tracing() - - pool = checker.Pool() - - sources = _unique(source.CC_DIRS + source.OBJC_DIRS + source.PYTHON_DIRS) - patterns = git.make_patterns(sources) - - files = git.find_changed_or_files(args.all, args.rev_or_files, patterns) - check(pool, files) - - pool.exit() - - -def check(pool, files): - group = source.categorize_files(files) - - for kind, files in group.kinds.items(): - for chunk in checker.shard(files): - if not chunk: - continue - - linter = _linters[kind] - pool.submit(linter, chunk) - - -def lint_cc(files): - return _run_cpplint([], files) - - -def lint_objc(files): - return _run_cpplint(_CPPLINT_OBJC_OPTIONS, files) - - -def _run_cpplint(options, files): - scripts_dir = os.path.dirname(os.path.abspath(__file__)) - cpplint = os.path.join(scripts_dir, 'cpplint.py') - - command = [sys.executable, cpplint, '--quiet'] - command.extend(options) - command.extend(files) - - return _read_output(command) - - -_flake8_warned = False - - -def lint_py(files): - flake8 = which('flake8') - if flake8 is None: - global _flake8_warned - if not _flake8_warned: - _flake8_warned = True - _logger.warn(textwrap.dedent( - """ - Could not find flake8 on $PATH; skipping python lint. - Install with: - - pip install --user flake8 - """)) - return - - command = [flake8] - command.extend(files) - - return _read_output(command) - - -def _read_output(command): - command_trace.log(command) - - if _dry_run: - return checker.Result(0, '') - - proc = subprocess.Popen( - command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - output = proc.communicate('')[0] - sc = proc.wait() - - return checker.Result(sc, output) - - -_linters = { - 'cc': lint_cc, - 'objc': lint_objc, - 'py': lint_py, -} - - -def _unique(items): - return list(set(items)) - - -def which(executable): - """Finds the executable with the given name. - - Returns: - The fully qualified path to the executable or None if the executable isn't - found. - """ - if executable.startswith('/'): - return executable - - path = os.environ['PATH'].split(os.pathsep) - - for executable_with_ext in _executable_names(executable): - for entry in path: - joined = os.path.join(entry, executable_with_ext) - if os.path.isfile(joined) and os.access(joined, os.X_OK): - return joined - - return None - - -def _executable_names(executable): - """Yields a sequence of all possible executable names.""" - - if os.name == 'nt': - pathext = os.environ.get('PATHEXT', '').split(os.pathsep) - for ext in pathext: - yield executable + ext - - else: - yield executable - - -if __name__ == '__main__': - main() diff --git a/scripts/check_secrets.sh b/scripts/check_secrets.sh deleted file mode 100755 index 4db1540..0000000 --- a/scripts/check_secrets.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/sh - -# Copyright 2020 Google LLC -# -# 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. - - -# Check if secrets are available for multiple CI's - -set -x -echo "GITHUB_BASE_REF: ${GITHUB_BASE_REF:-}" -echo "GITHUB_HEAD_REF: ${GITHUB_HEAD_REF:-}" - -check_secrets() -{ - # Travis: Secrets are available if we're not running on a fork. - if [[ -n "${TRAVIS_PULL_REQUEST:-}" ]]; then - if [[ "$TRAVIS_PULL_REQUEST" == "false" || - "$TRAVIS_PULL_REQUEST_SLUG" == "$TRAVIS_REPO_SLUG" ]]; then - return 0 - fi - fi - # GitHub Actions: Secrets are available if we're not running on a fork. - # See https://help.github.com/en/actions/automating-your-workflow-with-github-actions/using-environment-variables - # TODO- Both GITHUB_BASE_REF and GITHUB_HEAD_REF are set in main repo - # PRs even thought the docs say otherwise. They are not set in cron jobs on main. - # Investigate how do to distinguish fork PRs from main repo PRs. - if [[ -n "${GITHUB_WORKFLOW:-}" ]]; then - return 0 - fi - return 1 -} diff --git a/scripts/check_test_inclusion.py b/scripts/check_test_inclusion.py deleted file mode 100755 index 327f902..0000000 --- a/scripts/check_test_inclusion.py +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2018 Google -# -# 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. - -"""Verifies that all tests are a part of the project file. -""" - -from __future__ import print_function -import os -import os.path -import re -import sys - - -# Tests that are known not to compile in Xcode and can't be added there. -EXCLUDED = frozenset([ -]) - - -def Main(): - """Runs the style check.""" - - tests = FindTestFiles("Firestore/Example/Tests", "Firestore/core/test") - problems = CheckProject( - "Firestore/Example/Firestore.xcodeproj/project.pbxproj", tests) - - if problems: - Error("Test files exist that are unreferenced in Xcode project files:") - for problem in problems: - Error(problem) - sys.exit(1) - - sys.exit(0) - - -def FindTestFiles(*test_dirs): - """Searches the given source roots for test files. - - Args: - *test_dirs: A list of directories containing test sources. - - Returns: - A list of test source filenames. - """ - - test_file_pattern = re.compile(r"(?:Tests?\.mm?|_test\.(?:cc|mm))$") - - result = [] - for test_dir in test_dirs: - for root, dirs, files in os.walk(test_dir): - del dirs # unused - for basename in files: - filename = os.path.join(root, basename) - if filename not in EXCLUDED and test_file_pattern.search(basename): - result.append(filename) - return result - - -def CheckProject(project_file, test_files): - """Checks the given project file for tests in the given test_dirs. - - Args: - project_file: The path to an Xcode pbxproj file. - test_files: A list of all tests source files in the project. - - Returns: - A sorted list of filenames that aren't referenced in the project_file. - """ - - # An dict of basename to filename - basenames = {os.path.basename(f): f for f in test_files} - - file_list_pattern = re.compile(r"/\* (\S+) in Sources \*/") - with open(project_file, "r") as fd: - for line in fd: - line = line.rstrip() - m = file_list_pattern.search(line) - if m: - basename = m.group(1) - if basename in basenames: - del basenames[basename] - - return sorted(basenames.values()) - - -def Error(message, *args): - message %= args - print(message, file=sys.stderr) - - -if __name__ == "__main__": - Main() diff --git a/scripts/check_whitespace.sh b/scripts/check_whitespace.sh deleted file mode 100755 index 0867180..0000000 --- a/scripts/check_whitespace.sh +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright 2018 Google -# -# 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. - -# Fail on an trailing whitespace characters, excluding -# * binary files (-I) -# * nanopb-generated files -# * protoc-generated files -# -# Note: specifying revisions we care about makes this go slower than just -# grepping through the whole repo. -options=( - -n # show line numbers - -I # exclude binary files - ' $' -) - -git grep "${options[@]}" -- \ - ':(exclude)cmake/external/leveldb-1.22_windows_paths.patch' \ - ':(exclude)cmake/external/leveldb-1.23_windows_paths.patch' \ - ':(exclude)cmake/external/nanopb.patch' \ - ':(exclude)cmake/external/snappy.patch' \ - ':(exclude)Crashlytics/ProtoSupport' \ - ':(exclude)Crashlytics/UnitTests/Data' \ - ':(exclude)CoreOnly/NOTICES' \ - ':(exclude)Firebase/Firebase/NOTICES' \ - ':(exclude)Firebase/InAppMessaging/ProtoSupport' \ - ':(exclude)Firestore/Protos/nanopb' \ - ':(exclude)Firestore/Protos/cpp' \ - ':(exclude)Firestore/Protos/objc' \ - ':(exclude)Firestore/third_party/abseil-cpp' \ - ':(exclude)GoogleDataTransport/ProtoSupport' \ - ':(exclude)ReleaseTooling/Template/NOTICES' - -if [[ $? == 0 ]]; then - echo "ERROR: Trailing whitespace found in the files above. Please fix." - exit 1 -fi diff --git a/scripts/collect_metrics.sh b/scripts/collect_metrics.sh deleted file mode 100755 index c71bd9b..0000000 --- a/scripts/collect_metrics.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env bash - -# Copyright 2019 Google -# -# 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. - -# USAGE: ./collect_metrics.sh workspace scheme -# -# Collects project health metrics and uploads them to a database. Currently just collects code -# coverage for the provided workspace and scheme. Assumes that those tests have already been -# executed. - -set -euo pipefail - -if [[ $# -ne 2 ]]; then - cat 1>&2 < [file] ... - - Style checker for C/C++ source files. - This is a fork of the Google style checker with minor extensions. - - The style guidelines this tries to follow are those in - https://google.github.io/styleguide/cppguide.html - - Every problem is given a confidence score from 1-5, with 5 meaning we are - certain of the problem, and 1 meaning it could be a legitimate construct. - This will miss some errors, and is not a substitute for a code review. - - To suppress false-positive errors of a certain category, add a - 'NOLINT(category)' comment to the line. NOLINT or NOLINT(*) - suppresses errors of all categories on that line. - - The files passed in will be linted; at least one file must be provided. - Default linted extensions are %s. - Other file types will be ignored. - Change the extensions with the --extensions flag. - - Flags: - - output=emacs|eclipse|vs7|junit|sed|gsed - By default, the output is formatted to ease emacs parsing. Visual Studio - compatible output (vs7) may also be used. Further support exists for - eclipse (eclipse), and JUnit (junit). XML parsers such as those used - in Jenkins and Bamboo may also be used. - The sed format outputs sed commands that should fix some of the errors. - Note that this requires gnu sed. If that is installed as gsed on your - system (common e.g. on macOS with homebrew) you can use the gsed output - format. Sed commands are written to stdout, not stderr, so you should be - able to pipe output straight to a shell to run the fixes. - - verbose=# - Specify a number 0-5 to restrict errors to certain verbosity levels. - Errors with lower verbosity levels have lower confidence and are more - likely to be false positives. - - quiet - Don't print anything if no errors are found. - - filter=-x,+y,... - Specify a comma-separated list of category-filters to apply: only - error messages whose category names pass the filters will be printed. - (Category names are printed with the message and look like - "[whitespace/indent]".) Filters are evaluated left to right. - "-FOO" and "FOO" means "do not print categories that start with FOO". - "+FOO" means "do print categories that start with FOO". - - Examples: --filter=-whitespace,+whitespace/braces - --filter=whitespace,runtime/printf,+runtime/printf_format - --filter=-,+build/include_what_you_use - - To see a list of all the categories used in cpplint, pass no arg: - --filter= - - counting=total|toplevel|detailed - The total number of errors found is always printed. If - 'toplevel' is provided, then the count of errors in each of - the top-level categories like 'build' and 'whitespace' will - also be printed. If 'detailed' is provided, then a count - is provided for each category like 'build/class'. - - repository=path - The top level directory of the repository, used to derive the header - guard CPP variable. By default, this is determined by searching for a - path that contains .git, .hg, or .svn. When this flag is specified, the - given path is used instead. This option allows the header guard CPP - variable to remain consistent even if members of a team have different - repository root directories (such as when checking out a subdirectory - with SVN). In addition, users of non-mainstream version control systems - can use this flag to ensure readable header guard CPP variables. - - Examples: - Assuming that Alice checks out ProjectName and Bob checks out - ProjectName/trunk and trunk contains src/chrome/ui/browser.h, then - with no --repository flag, the header guard CPP variable will be: - - Alice => TRUNK_SRC_CHROME_BROWSER_UI_BROWSER_H_ - Bob => SRC_CHROME_BROWSER_UI_BROWSER_H_ - - If Alice uses the --repository=trunk flag and Bob omits the flag or - uses --repository=. then the header guard CPP variable will be: - - Alice => SRC_CHROME_BROWSER_UI_BROWSER_H_ - Bob => SRC_CHROME_BROWSER_UI_BROWSER_H_ - - root=subdir - The root directory used for deriving header guard CPP variable. - This directory is relative to the top level directory of the repository - which by default is determined by searching for a directory that contains - .git, .hg, or .svn but can also be controlled with the --repository flag. - If the specified directory does not exist, this flag is ignored. - - Examples: - Assuming that src is the top level directory of the repository (and - cwd=top/src), the header guard CPP variables for - src/chrome/browser/ui/browser.h are: - - No flag => CHROME_BROWSER_UI_BROWSER_H_ - --root=chrome => BROWSER_UI_BROWSER_H_ - --root=chrome/browser => UI_BROWSER_H_ - --root=.. => SRC_CHROME_BROWSER_UI_BROWSER_H_ - - linelength=digits - This is the allowed line length for the project. The default value is - 80 characters. - - Examples: - --linelength=120 - - recursive - Search for files to lint recursively. Each directory given in the list - of files to be linted is replaced by all files that descend from that - directory. Files with extensions not in the valid extensions list are - excluded. - - exclude=path - Exclude the given path from the list of files to be linted. Relative - paths are evaluated relative to the current directory and shell globbing - is performed. This flag can be provided multiple times to exclude - multiple files. - - Examples: - --exclude=one.cc - --exclude=src/*.cc - --exclude=src/*.cc --exclude=test/*.cc - - extensions=extension,extension,... - The allowed file extensions that cpplint will check - - Examples: - --extensions=%s - - includeorder=default|standardcfirst - For the build/include_order rule, the default is to blindly assume angle - bracket includes with file extension are c-system-headers (default), - even knowing this will have false classifications. - The default is established at google. - standardcfirst means to instead use an allow-list of known c headers and - treat all others as separate group of "other system headers". The C headers - included are those of the C-standard lib and closely related ones. - - headers=x,y,... - The header extensions that cpplint will treat as .h in checks. Values are - automatically added to --extensions list. - (by default, only files with extensions %s will be assumed to be headers) - - Examples: - --headers=%s - --headers=hpp,hxx - --headers=hpp - - cpplint.py supports per-directory configurations specified in CPPLINT.cfg - files. CPPLINT.cfg file can contain a number of key=value pairs. - Currently the following options are supported: - - set noparent - filter=+filter1,-filter2,... - exclude_files=regex - linelength=80 - root=subdir - headers=x,y,... - - "set noparent" option prevents cpplint from traversing directory tree - upwards looking for more .cfg files in parent directories. This option - is usually placed in the top-level project directory. - - The "filter" option is similar in function to --filter flag. It specifies - message filters in addition to the |_DEFAULT_FILTERS| and those specified - through --filter command-line flag. - - "exclude_files" allows to specify a regular expression to be matched against - a file name. If the expression matches, the file is skipped and not run - through the linter. - - "linelength" allows to specify the allowed line length for the project. - - The "root" option is similar in function to the --root flag (see example - above). Paths are relative to the directory of the CPPLINT.cfg. - - The "headers" option is similar in function to the --headers flag - (see example above). - - CPPLINT.cfg has an effect on files in the same directory and all - sub-directories, unless overridden by a nested configuration file. - - Example file: - filter=-build/include_order,+build/include_alpha - exclude_files=.*\\.cc - - The above example disables build/include_order warning and enables - build/include_alpha as well as excludes all .cc from being - processed by linter, in the current directory (where the .cfg - file is located) and all sub-directories. -""" - -# We categorize each error message we print. Here are the categories. -# We want an explicit list so we can list them all in cpplint --filter=. -# If you add a new error message with a new category, add it to the list -# here! cpplint_unittest.py should tell you if you forget to do this. -_ERROR_CATEGORIES = [ - 'build/class', - 'build/c++11', - 'build/c++14', - 'build/c++tr1', - 'build/deprecated', - 'build/endif_comment', - 'build/explicit_make_pair', - 'build/forward_decl', - 'build/header_guard', - 'build/include', - 'build/include_subdir', - 'build/include_alpha', - 'build/include_order', - 'build/include_what_you_use', - 'build/namespaces_headers', - 'build/namespaces_literals', - 'build/namespaces', - 'build/printf_format', - 'build/storage_class', - 'legal/copyright', - 'readability/alt_tokens', - 'readability/braces', - 'readability/casting', - 'readability/check', - 'readability/constructors', - 'readability/fn_size', - 'readability/inheritance', - 'readability/multiline_comment', - 'readability/multiline_string', - 'readability/namespace', - 'readability/nolint', - 'readability/nul', - 'readability/strings', - 'readability/todo', - 'readability/utf8', - 'runtime/arrays', - 'runtime/casting', - 'runtime/explicit', - 'runtime/int', - 'runtime/init', - 'runtime/invalid_increment', - 'runtime/member_string_references', - 'runtime/memset', - 'runtime/indentation_namespace', - 'runtime/operator', - 'runtime/printf', - 'runtime/printf_format', - 'runtime/references', - 'runtime/string', - 'runtime/threadsafe_fn', - 'runtime/vlog', - 'whitespace/blank_line', - 'whitespace/braces', - 'whitespace/comma', - 'whitespace/comments', - 'whitespace/empty_conditional_body', - 'whitespace/empty_if_body', - 'whitespace/empty_loop_body', - 'whitespace/end_of_line', - 'whitespace/ending_newline', - 'whitespace/forcolon', - 'whitespace/indent', - 'whitespace/line_length', - 'whitespace/newline', - 'whitespace/operators', - 'whitespace/parens', - 'whitespace/semicolon', - 'whitespace/tab', - 'whitespace/todo', - ] - -# keywords to use with --outputs which generate stdout for machine processing -_MACHINE_OUTPUTS = [ - 'junit', - 'sed', - 'gsed' -] - -# These error categories are no longer enforced by cpplint, but for backwards- -# compatibility they may still appear in NOLINT comments. -_LEGACY_ERROR_CATEGORIES = [ - 'readability/streams', - 'readability/function', - ] - -# The default state of the category filter. This is overridden by the --filter= -# flag. By default all errors are on, so only add here categories that should be -# off by default (i.e., categories that must be enabled by the --filter= flags). -# All entries here should start with a '-' or '+', as in the --filter= flag. -_DEFAULT_FILTERS = ['-build/include_alpha'] - -# The default list of categories suppressed for C (not C++) files. -_DEFAULT_C_SUPPRESSED_CATEGORIES = [ - 'readability/casting', - ] - -# The default list of categories suppressed for Linux Kernel files. -_DEFAULT_KERNEL_SUPPRESSED_CATEGORIES = [ - 'whitespace/tab', - ] - -# We used to check for high-bit characters, but after much discussion we -# decided those were OK, as long as they were in UTF-8 and didn't represent -# hard-coded international strings, which belong in a separate i18n file. - -# C++ headers -_CPP_HEADERS = frozenset([ - # Legacy - 'algobase.h', - 'algo.h', - 'alloc.h', - 'builtinbuf.h', - 'bvector.h', - 'complex.h', - 'defalloc.h', - 'deque.h', - 'editbuf.h', - 'fstream.h', - 'function.h', - 'hash_map', - 'hash_map.h', - 'hash_set', - 'hash_set.h', - 'hashtable.h', - 'heap.h', - 'indstream.h', - 'iomanip.h', - 'iostream.h', - 'istream.h', - 'iterator.h', - 'list.h', - 'map.h', - 'multimap.h', - 'multiset.h', - 'ostream.h', - 'pair.h', - 'parsestream.h', - 'pfstream.h', - 'procbuf.h', - 'pthread_alloc', - 'pthread_alloc.h', - 'rope', - 'rope.h', - 'ropeimpl.h', - 'set.h', - 'slist', - 'slist.h', - 'stack.h', - 'stdiostream.h', - 'stl_alloc.h', - 'stl_relops.h', - 'streambuf.h', - 'stream.h', - 'strfile.h', - 'strstream.h', - 'tempbuf.h', - 'tree.h', - 'type_traits.h', - 'vector.h', - # 17.6.1.2 C++ library headers - 'algorithm', - 'array', - 'atomic', - 'bitset', - 'chrono', - 'codecvt', - 'complex', - 'condition_variable', - 'deque', - 'exception', - 'forward_list', - 'fstream', - 'functional', - 'future', - 'initializer_list', - 'iomanip', - 'ios', - 'iosfwd', - 'iostream', - 'istream', - 'iterator', - 'limits', - 'list', - 'locale', - 'map', - 'memory', - 'mutex', - 'new', - 'numeric', - 'ostream', - 'queue', - 'random', - 'ratio', - 'regex', - 'scoped_allocator', - 'set', - 'sstream', - 'stack', - 'stdexcept', - 'streambuf', - 'string', - 'strstream', - 'system_error', - 'thread', - 'tuple', - 'typeindex', - 'typeinfo', - 'type_traits', - 'unordered_map', - 'unordered_set', - 'utility', - 'valarray', - 'vector', - # 17.6.1.2 C++14 headers - 'shared_mutex', - # 17.6.1.2 C++17 headers - 'any', - 'charconv', - 'codecvt', - 'execution', - 'filesystem', - 'memory_resource', - 'optional', - 'string_view', - 'variant', - # 17.6.1.2 C++ headers for C library facilities - 'cassert', - 'ccomplex', - 'cctype', - 'cerrno', - 'cfenv', - 'cfloat', - 'cinttypes', - 'ciso646', - 'climits', - 'clocale', - 'cmath', - 'csetjmp', - 'csignal', - 'cstdalign', - 'cstdarg', - 'cstdbool', - # 'cstddef', https://github.com/firebase/firebase-ios-sdk/pull/7563 - 'cstdint', - 'cstdio', - # 'cstdlib', https://github.com/firebase/firebase-ios-sdk/pull/7563 - 'cstring', - 'ctgmath', - 'ctime', - 'cuchar', - 'cwchar', - 'cwctype', - ]) - -# C headers -_C_HEADERS = frozenset([ - # System C headers - 'assert.h', - 'complex.h', - 'ctype.h', - 'errno.h', - 'fenv.h', - 'float.h', - 'inttypes.h', - 'iso646.h', - 'limits.h', - 'locale.h', - 'math.h', - 'setjmp.h', - 'signal.h', - 'stdalign.h', - 'stdarg.h', - 'stdatomic.h', - 'stdbool.h', - 'stddef.h', - 'stdint.h', - 'stdio.h', - 'stdlib.h', - 'stdnoreturn.h', - 'string.h', - 'tgmath.h', - 'threads.h', - 'time.h', - 'uchar.h', - 'wchar.h', - 'wctype.h', - # additional POSIX C headers - 'aio.h', - 'arpa/inet.h', - 'cpio.h', - 'dirent.h', - 'dlfcn.h', - 'fcntl.h', - 'fmtmsg.h', - 'fnmatch.h', - 'ftw.h', - 'glob.h', - 'grp.h', - 'iconv.h', - 'langinfo.h', - 'libgen.h', - 'monetary.h', - 'mqueue.h', - 'ndbm.h', - 'net/if.h', - 'netdb.h', - 'netinet/in.h', - 'netinet/tcp.h', - 'nl_types.h', - 'poll.h', - 'pthread.h', - 'pwd.h', - 'regex.h', - 'sched.h', - 'search.h', - 'semaphore.h', - 'setjmp.h', - 'signal.h', - 'spawn.h', - 'strings.h', - 'stropts.h', - 'syslog.h', - 'tar.h', - 'termios.h', - 'trace.h', - 'ulimit.h', - 'unistd.h', - 'utime.h', - 'utmpx.h', - 'wordexp.h', - # additional GNUlib headers - 'a.out.h', - 'aliases.h', - 'alloca.h', - 'ar.h', - 'argp.h', - 'argz.h', - 'byteswap.h', - 'crypt.h', - 'endian.h', - 'envz.h', - 'err.h', - 'error.h', - 'execinfo.h', - 'fpu_control.h', - 'fstab.h', - 'fts.h', - 'getopt.h', - 'gshadow.h', - 'ieee754.h', - 'ifaddrs.h', - 'libintl.h', - 'mcheck.h', - 'mntent.h', - 'obstack.h', - 'paths.h', - 'printf.h', - 'pty.h', - 'resolv.h', - 'shadow.h', - 'sysexits.h', - 'ttyent.h', - # Additional linux glibc headers - 'dlfcn.h', - 'elf.h', - 'features.h', - 'gconv.h', - 'gnu-versions.h', - 'lastlog.h', - 'libio.h', - 'link.h', - 'malloc.h', - 'memory.h', - 'netash/ash.h', - 'netatalk/at.h', - 'netax25/ax25.h', - 'neteconet/ec.h', - 'netipx/ipx.h', - 'netiucv/iucv.h', - 'netpacket/packet.h', - 'netrom/netrom.h', - 'netrose/rose.h', - 'nfs/nfs.h', - 'nl_types.h', - 'nss.h', - 're_comp.h', - 'regexp.h', - 'sched.h', - 'sgtty.h', - 'stab.h', - 'stdc-predef.h', - 'stdio_ext.h', - 'syscall.h', - 'termio.h', - 'thread_db.h', - 'ucontext.h', - 'ustat.h', - 'utmp.h', - 'values.h', - 'wait.h', - 'xlocale.h', - # Hardware specific headers - 'arm_neon.h', - 'emmintrin.h', - 'xmmintin.h', - ]) - -# Folders of C libraries so commonly used in C++, -# that they have parity with standard C libraries. -C_STANDARD_HEADER_FOLDERS = frozenset([ - # standard C library - "sys", - # glibc for linux - "arpa", - "asm-generic", - "bits", - "gnu", - "net", - "netinet", - "protocols", - "rpc", - "rpcsvc", - "scsi", - # linux kernel header - "drm", - "linux", - "misc", - "mtd", - "rdma", - "sound", - "video", - "xen", - ]) - -_C_SYSTEM_DIRECTORIES = frozenset([ - 'SystemConfiguration', - 'dispatch', - 'libkern', - 'mach', - 'netinet', - 'objc', - 'sys', -]) - -# Type names -_TYPES = re.compile( - r'^(?:' - # [dcl.type.simple] - r'(char(16_t|32_t)?)|wchar_t|' - r'bool|short|int|long|signed|unsigned|float|double|' - # [support.types] - r'(ptrdiff_t|size_t|max_align_t|nullptr_t)|' - # [cstdint.syn] - r'(u?int(_fast|_least)?(8|16|32|64)_t)|' - r'(u?int(max|ptr)_t)|' - r')$') - - -# These headers are excluded from [build/include] and [build/include_order] -# checks: -# - Anything not following google file name conventions (containing an -# uppercase character, such as Python.h or nsStringAPI.h, for example). -# - Lua headers. -_THIRD_PARTY_HEADERS_PATTERN = re.compile( - r'^(?:[^/]*[A-Z][^/]*\.h|lua\.h|lauxlib\.h|lualib\.h)$') - -# Pattern for matching FileInfo.BaseName() against test file name -_test_suffixes = ['_test', '_regtest', '_unittest'] -_TEST_FILE_SUFFIX = '(' + '|'.join(_test_suffixes) + r')$' - -# Pattern that matches only complete whitespace, possibly across multiple lines. -_EMPTY_CONDITIONAL_BODY_PATTERN = re.compile(r'^\s*$', re.DOTALL) - -# Assertion macros. These are defined in base/logging.h and -# testing/base/public/gunit.h. -_CHECK_MACROS = [ - 'DCHECK', 'CHECK', - 'EXPECT_TRUE', 'ASSERT_TRUE', - 'EXPECT_FALSE', 'ASSERT_FALSE', - ] - -# Replacement macros for CHECK/DCHECK/EXPECT_TRUE/EXPECT_FALSE -_CHECK_REPLACEMENT = dict([(macro_var, {}) for macro_var in _CHECK_MACROS]) - -for op, replacement in [('==', 'EQ'), ('!=', 'NE'), - ('>=', 'GE'), ('>', 'GT'), - ('<=', 'LE'), ('<', 'LT')]: - _CHECK_REPLACEMENT['DCHECK'][op] = 'DCHECK_%s' % replacement - _CHECK_REPLACEMENT['CHECK'][op] = 'CHECK_%s' % replacement - _CHECK_REPLACEMENT['EXPECT_TRUE'][op] = 'EXPECT_%s' % replacement - _CHECK_REPLACEMENT['ASSERT_TRUE'][op] = 'ASSERT_%s' % replacement - -for op, inv_replacement in [('==', 'NE'), ('!=', 'EQ'), - ('>=', 'LT'), ('>', 'LE'), - ('<=', 'GT'), ('<', 'GE')]: - _CHECK_REPLACEMENT['EXPECT_FALSE'][op] = 'EXPECT_%s' % inv_replacement - _CHECK_REPLACEMENT['ASSERT_FALSE'][op] = 'ASSERT_%s' % inv_replacement - -# Alternative tokens and their replacements. For full list, see section 2.5 -# Alternative tokens [lex.digraph] in the C++ standard. -# -# Digraphs (such as '%:') are not included here since it's a mess to -# match those on a word boundary. -_ALT_TOKEN_REPLACEMENT = { - 'and': '&&', - 'bitor': '|', - 'or': '||', - 'xor': '^', - 'compl': '~', - 'bitand': '&', - 'and_eq': '&=', - 'or_eq': '|=', - 'xor_eq': '^=', - 'not': '!', - 'not_eq': '!=' - } - -# Compile regular expression that matches all the above keywords. The "[ =()]" -# bit is meant to avoid matching these keywords outside of boolean expressions. -# -# False positives include C-style multi-line comments and multi-line strings -# but those have always been troublesome for cpplint. -_ALT_TOKEN_REPLACEMENT_PATTERN = re.compile( - r'[ =()](' + ('|'.join(_ALT_TOKEN_REPLACEMENT.keys())) + r')(?=[ (]|$)') - - -# These constants define types of headers for use with -# _IncludeState.CheckNextIncludeOrder(). -_C_SYS_HEADER = 1 -_CPP_SYS_HEADER = 2 -_OTHER_SYS_HEADER = 3 -_LIKELY_MY_HEADER = 4 -_POSSIBLE_MY_HEADER = 5 -_OTHER_HEADER = 6 - -# These constants define the current inline assembly state -_NO_ASM = 0 # Outside of inline assembly block -_INSIDE_ASM = 1 # Inside inline assembly block -_END_ASM = 2 # Last line of inline assembly block -_BLOCK_ASM = 3 # The whole block is an inline assembly block - -# Match start of assembly blocks -_MATCH_ASM = re.compile(r'^\s*(?:asm|_asm|__asm|__asm__)' - r'(?:\s+(volatile|__volatile__))?' - r'\s*[{(]') - -# Match strings that indicate we're working on a C (not C++) file. -_SEARCH_C_FILE = re.compile(r'\b(?:LINT_C_FILE|' - r'vim?:\s*.*(\s*|:)filetype=c(\s*|:|$))') - -# Match string that indicates we're working on a Linux Kernel file. -_SEARCH_KERNEL_FILE = re.compile(r'\b(?:LINT_KERNEL_FILE)') - -# Commands for sed to fix the problem -_SED_FIXUPS = { - 'Remove spaces around =': r's/ = /=/', - 'Remove spaces around !=': r's/ != /!=/', - 'Remove space before ( in if (': r's/if (/if(/', - 'Remove space before ( in for (': r's/for (/for(/', - 'Remove space before ( in while (': r's/while (/while(/', - 'Remove space before ( in switch (': r's/switch (/switch(/', - 'Should have a space between // and comment': r's/\/\//\/\/ /', - 'Missing space before {': r's/\([^ ]\){/\1 {/', - 'Tab found, replace by spaces': r's/\t/ /g', - 'Line ends in whitespace. Consider deleting these extra spaces.': r's/\s*$//', - 'You don\'t need a ; after a }': r's/};/}/', - 'Missing space after ,': r's/,\([^ ]\)/, \1/g', -} - -_regexp_compile_cache = {} - -# {str, set(int)}: a map from error categories to sets of linenumbers -# on which those errors are expected and should be suppressed. -_error_suppressions = {} - -# The root directory used for deriving header guard CPP variable. -# This is set by --root flag. -_root = None -_root_debug = False - -# The top level repository directory. If set, _root is calculated relative to -# this directory instead of the directory containing version control artifacts. -# This is set by the --repository flag. -_repository = None - -# Files to exclude from linting. This is set by the --exclude flag. -_excludes = None - -# Whether to supress all PrintInfo messages, UNRELATED to --quiet flag -_quiet = False - -# The allowed line length of files. -# This is set by --linelength flag. -_line_length = 80 - -# This allows to use different include order rule than default -_include_order = "default" - -try: - unicode -except NameError: - # -- pylint: disable=redefined-builtin - basestring = unicode = str - -try: - long -except NameError: - # -- pylint: disable=redefined-builtin - long = int - -if sys.version_info < (3,): - # -- pylint: disable=no-member - # BINARY_TYPE = str - itervalues = dict.itervalues - iteritems = dict.iteritems -else: - # BINARY_TYPE = bytes - itervalues = dict.values - iteritems = dict.items - -def unicode_escape_decode(x): - if sys.version_info < (3,): - return codecs.unicode_escape_decode(x)[0] - else: - return x - -# Treat all headers starting with 'h' equally: .h, .hpp, .hxx etc. -# This is set by --headers flag. -_hpp_headers = set([]) - -# {str, bool}: a map from error categories to booleans which indicate if the -# category should be suppressed for every line. -_global_error_suppressions = {} - -def ProcessHppHeadersOption(val): - global _hpp_headers - try: - _hpp_headers = {ext.strip() for ext in val.split(',')} - except ValueError: - PrintUsage('Header extensions must be comma separated list.') - -def ProcessIncludeOrderOption(val): - if val is None or val == "default": - pass - elif val == "standardcfirst": - global _include_order - _include_order = val - else: - PrintUsage('Invalid includeorder value %s. Expected default|standardcfirst') - -def IsHeaderExtension(file_extension): - return file_extension in GetHeaderExtensions() - -def GetHeaderExtensions(): - if _hpp_headers: - return _hpp_headers - if _valid_extensions: - return {h for h in _valid_extensions if 'h' in h} - return set(['h', 'hh', 'hpp', 'hxx', 'h++', 'cuh']) - -# The allowed extensions for file names -# This is set by --extensions flag -def GetAllExtensions(): - return GetHeaderExtensions().union(_valid_extensions or set( - ['c', 'cc', 'cpp', 'cxx', 'c++', 'cu', 'm', 'mm'])) - -def ProcessExtensionsOption(val): - global _valid_extensions - try: - extensions = [ext.strip() for ext in val.split(',')] - _valid_extensions = set(extensions) - except ValueError: - PrintUsage('Extensions should be a comma-separated list of values;' - 'for example: extensions=hpp,cpp\n' - 'This could not be parsed: "%s"' % (val,)) - -def GetNonHeaderExtensions(): - return GetAllExtensions().difference(GetHeaderExtensions()) - -def ParseNolintSuppressions(filename, raw_line, linenum, error): - """Updates the global list of line error-suppressions. - - Parses any NOLINT comments on the current line, updating the global - error_suppressions store. Reports an error if the NOLINT comment - was malformed. - - Args: - filename: str, the name of the input file. - raw_line: str, the line of input text, with comments. - linenum: int, the number of the current line. - error: function, an error handler. - """ - matched = Search(r'\bNOLINT(NEXTLINE)?\b(\([^)]+\))?', raw_line) - if matched: - if matched.group(1): - suppressed_line = linenum + 1 - else: - suppressed_line = linenum - category = matched.group(2) - if category in (None, '(*)'): # => "suppress all" - _error_suppressions.setdefault(None, set()).add(suppressed_line) - else: - if category.startswith('(') and category.endswith(')'): - category = category[1:-1] - if category in _ERROR_CATEGORIES: - _error_suppressions.setdefault(category, set()).add(suppressed_line) - elif category not in _LEGACY_ERROR_CATEGORIES: - error(filename, linenum, 'readability/nolint', 5, - 'Unknown NOLINT error category: %s' % category) - - -def ProcessGlobalSuppresions(lines): - """Updates the list of global error suppressions. - - Parses any lint directives in the file that have global effect. - - Args: - lines: An array of strings, each representing a line of the file, with the - last element being empty if the file is terminated with a newline. - """ - for line in lines: - if _SEARCH_C_FILE.search(line): - for category in _DEFAULT_C_SUPPRESSED_CATEGORIES: - _global_error_suppressions[category] = True - if _SEARCH_KERNEL_FILE.search(line): - for category in _DEFAULT_KERNEL_SUPPRESSED_CATEGORIES: - _global_error_suppressions[category] = True - - -def ResetNolintSuppressions(): - """Resets the set of NOLINT suppressions to empty.""" - _error_suppressions.clear() - _global_error_suppressions.clear() - - -def IsErrorSuppressedByNolint(category, linenum): - """Returns true if the specified error category is suppressed on this line. - - Consults the global error_suppressions map populated by - ParseNolintSuppressions/ProcessGlobalSuppresions/ResetNolintSuppressions. - - Args: - category: str, the category of the error. - linenum: int, the current line number. - Returns: - bool, True iff the error should be suppressed due to a NOLINT comment or - global suppression. - """ - return (_global_error_suppressions.get(category, False) or - linenum in _error_suppressions.get(category, set()) or - linenum in _error_suppressions.get(None, set())) - - -def Match(pattern, s): - """Matches the string with the pattern, caching the compiled regexp.""" - # The regexp compilation caching is inlined in both Match and Search for - # performance reasons; factoring it out into a separate function turns out - # to be noticeably expensive. - if pattern not in _regexp_compile_cache: - _regexp_compile_cache[pattern] = sre_compile.compile(pattern) - return _regexp_compile_cache[pattern].match(s) - - -def ReplaceAll(pattern, rep, s): - """Replaces instances of pattern in a string with a replacement. - - The compiled regex is kept in a cache shared by Match and Search. - - Args: - pattern: regex pattern - rep: replacement text - s: search string - - Returns: - string with replacements made (or original string if no replacements) - """ - if pattern not in _regexp_compile_cache: - _regexp_compile_cache[pattern] = sre_compile.compile(pattern) - return _regexp_compile_cache[pattern].sub(rep, s) - - -def Search(pattern, s): - """Searches the string for the pattern, caching the compiled regexp.""" - if pattern not in _regexp_compile_cache: - _regexp_compile_cache[pattern] = sre_compile.compile(pattern) - return _regexp_compile_cache[pattern].search(s) - - -def _IsSourceExtension(s): - """File extension (excluding dot) matches a source file extension.""" - return s in GetNonHeaderExtensions() - - -def _IsSourceFilename(filename): - ext = os.path.splitext(filename)[-1].lower() - ext = ext[1:] # leading dot - return _IsSourceExtension(ext) - - -class _IncludeState(object): - """Tracks line numbers for includes, and the order in which includes appear. - - include_list contains list of lists of (header, line number) pairs. - It's a lists of lists rather than just one flat list to make it - easier to update across preprocessor boundaries. - - Call CheckNextIncludeOrder() once for each header in the file, passing - in the type constants defined above. Calls in an illegal order will - raise an _IncludeError with an appropriate error message. - - """ - # self._section will move monotonically through this set. If it ever - # needs to move backwards, CheckNextIncludeOrder will raise an error. - _INITIAL_SECTION = 0 - _MY_H_SECTION = 1 - _C_SECTION = 2 - _CPP_SECTION = 3 - _OTHER_SYS_SECTION = 4 - _OTHER_H_SECTION = 5 - - _TYPE_NAMES = { - _C_SYS_HEADER: 'C system header', - _CPP_SYS_HEADER: 'C++ system header', - _OTHER_SYS_HEADER: 'other system header', - _LIKELY_MY_HEADER: 'header this file implements', - _POSSIBLE_MY_HEADER: 'header this file may implement', - _OTHER_HEADER: 'other header', - } - _SECTION_NAMES = { - _INITIAL_SECTION: "... nothing. (This can't be an error.)", - _MY_H_SECTION: 'a header this file implements', - _C_SECTION: 'C system header', - _CPP_SECTION: 'C++ system header', - _OTHER_SYS_SECTION: 'other system header', - _OTHER_H_SECTION: 'other header', - } - - def __init__(self): - self.include_list = [[]] - self._section = None - self._last_header = None - self.ResetSection('') - - def FindHeader(self, header): - """Check if a header has already been included. - - Args: - header: header to check. - Returns: - Line number of previous occurrence, or -1 if the header has not - been seen before. - """ - for section_list in self.include_list: - for f in section_list: - if f[0] == header: - return f[1] - return -1 - - def ResetSection(self, directive): - """Reset section checking for preprocessor directive. - - Args: - directive: preprocessor directive (e.g. "if", "else"). - """ - # The name of the current section. - self._section = self._INITIAL_SECTION - # The path of last found header. - self._last_header = '' - - # Update list of includes. Note that we never pop from the - # include list. - if directive in ('if', 'ifdef', 'ifndef'): - self.include_list.append([]) - elif directive in ('else', 'elif'): - self.include_list[-1] = [] - - def SetLastHeader(self, header_path): - self._last_header = header_path - - def CanonicalizeAlphabeticalOrder(self, header_path): - """Returns a path canonicalized for alphabetical comparison. - - - replaces "-" with "_" so they both cmp the same. - - removes '-inl' since we don't require them to be after the main header. - - lowercase everything, just in case. - - Args: - header_path: Path to be canonicalized. - - Returns: - Canonicalized path. - """ - return header_path.replace('-inl.h', '.h').replace('-', '_').lower() - - def IsInAlphabeticalOrder(self, clean_lines, linenum, header_path): - """Check if a header is in alphabetical order with the previous header. - - Args: - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - header_path: Canonicalized header to be checked. - - Returns: - Returns true if the header is in alphabetical order. - """ - # If previous section is different from current section, _last_header will - # be reset to empty string, so it's always less than current header. - # - # If previous line was a blank line, assume that the headers are - # intentionally sorted the way they are. - if (self._last_header > header_path and - Match(r'^\s*#\s*include\b', clean_lines.elided[linenum - 1])): - return False - return True - - def CheckNextIncludeOrder(self, header_type): - """Returns a non-empty error message if the next header is out of order. - - This function also updates the internal state to be ready to check - the next include. - - Args: - header_type: One of the _XXX_HEADER constants defined above. - - Returns: - The empty string if the header is in the right order, or an - error message describing what's wrong. - - """ - error_message = ('Found %s after %s' % - (self._TYPE_NAMES[header_type], - self._SECTION_NAMES[self._section])) - - last_section = self._section - - if header_type == _C_SYS_HEADER: - if self._section <= self._C_SECTION: - self._section = self._C_SECTION - else: - self._last_header = '' - return error_message - elif header_type == _CPP_SYS_HEADER: - if self._section <= self._CPP_SECTION: - self._section = self._CPP_SECTION - else: - self._last_header = '' - return error_message - elif header_type == _OTHER_SYS_HEADER: - if self._section <= self._OTHER_SYS_SECTION: - self._section = self._OTHER_SYS_SECTION - else: - self._last_header = '' - return error_message - elif header_type == _LIKELY_MY_HEADER: - if self._section <= self._MY_H_SECTION: - self._section = self._MY_H_SECTION - else: - self._section = self._OTHER_H_SECTION - elif header_type == _POSSIBLE_MY_HEADER: - if self._section <= self._MY_H_SECTION: - self._section = self._MY_H_SECTION - else: - # This will always be the fallback because we're not sure - # enough that the header is associated with this file. - self._section = self._OTHER_H_SECTION - else: - assert header_type == _OTHER_HEADER - self._section = self._OTHER_H_SECTION - - if last_section != self._section: - self._last_header = '' - - return '' - - -class _CppLintState(object): - """Maintains module-wide state..""" - - def __init__(self): - self.verbose_level = 1 # global setting. - self.error_count = 0 # global count of reported errors - # filters to apply when emitting error messages - self.filters = _DEFAULT_FILTERS[:] - # backup of filter list. Used to restore the state after each file. - self._filters_backup = self.filters[:] - self.counting = 'total' # In what way are we counting errors? - self.errors_by_category = {} # string to int dict storing error counts - self.quiet = False # Suppress non-error messagess? - - # output format: - # "emacs" - format that emacs can parse (default) - # "eclipse" - format that eclipse can parse - # "vs7" - format that Microsoft Visual Studio 7 can parse - # "junit" - format that Jenkins, Bamboo, etc can parse - # "sed" - returns a gnu sed command to fix the problem - # "gsed" - like sed, but names the command gsed, e.g. for macOS homebrew users - self.output_format = 'emacs' - - # For JUnit output, save errors and failures until the end so that they - # can be written into the XML - self._junit_errors = [] - self._junit_failures = [] - - def SetOutputFormat(self, output_format): - """Sets the output format for errors.""" - self.output_format = output_format - - def SetQuiet(self, quiet): - """Sets the module's quiet settings, and returns the previous setting.""" - last_quiet = self.quiet - self.quiet = quiet - return last_quiet - - def SetVerboseLevel(self, level): - """Sets the module's verbosity, and returns the previous setting.""" - last_verbose_level = self.verbose_level - self.verbose_level = level - return last_verbose_level - - def SetCountingStyle(self, counting_style): - """Sets the module's counting options.""" - self.counting = counting_style - - def SetFilters(self, filters): - """Sets the error-message filters. - - These filters are applied when deciding whether to emit a given - error message. - - Args: - filters: A string of comma-separated filters (eg "+whitespace/indent"). - Each filter should start with + or -; else we die. - - Raises: - ValueError: The comma-separated filters did not all start with '+' or '-'. - E.g. "-,+whitespace,-whitespace/indent,whitespace/badfilter" - """ - # Default filters always have less priority than the flag ones. - self.filters = _DEFAULT_FILTERS[:] - self.AddFilters(filters) - - def AddFilters(self, filters): - """ Adds more filters to the existing list of error-message filters. """ - for filt in filters.split(','): - clean_filt = filt.strip() - if clean_filt: - self.filters.append(clean_filt) - for filt in self.filters: - if not (filt.startswith('+') or filt.startswith('-')): - raise ValueError('Every filter in --filters must start with + or -' - ' (%s does not)' % filt) - - def BackupFilters(self): - """ Saves the current filter list to backup storage.""" - self._filters_backup = self.filters[:] - - def RestoreFilters(self): - """ Restores filters previously backed up.""" - self.filters = self._filters_backup[:] - - def ResetErrorCounts(self): - """Sets the module's error statistic back to zero.""" - self.error_count = 0 - self.errors_by_category = {} - - def IncrementErrorCount(self, category): - """Bumps the module's error statistic.""" - self.error_count += 1 - if self.counting in ('toplevel', 'detailed'): - if self.counting != 'detailed': - category = category.split('/')[0] - if category not in self.errors_by_category: - self.errors_by_category[category] = 0 - self.errors_by_category[category] += 1 - - def PrintErrorCounts(self): - """Print a summary of errors by category, and the total.""" - for category, count in sorted(iteritems(self.errors_by_category)): - self.PrintInfo('Category \'%s\' errors found: %d\n' % - (category, count)) - if self.error_count > 0: - self.PrintInfo('Total errors found: %d\n' % self.error_count) - - def PrintInfo(self, message): - # _quiet does not represent --quiet flag. - # Hide infos from stdout to keep stdout pure for machine consumption - if not _quiet and self.output_format not in _MACHINE_OUTPUTS: - sys.stdout.write(message) - - def PrintError(self, message): - if self.output_format == 'junit': - self._junit_errors.append(message) - else: - sys.stderr.write(message) - - def AddJUnitFailure(self, filename, linenum, message, category, confidence): - self._junit_failures.append((filename, linenum, message, category, - confidence)) - - def FormatJUnitXML(self): - num_errors = len(self._junit_errors) - num_failures = len(self._junit_failures) - - testsuite = xml.etree.ElementTree.Element('testsuite') - testsuite.attrib['errors'] = str(num_errors) - testsuite.attrib['failures'] = str(num_failures) - testsuite.attrib['name'] = 'cpplint' - - if num_errors == 0 and num_failures == 0: - testsuite.attrib['tests'] = str(1) - xml.etree.ElementTree.SubElement(testsuite, 'testcase', name='passed') - - else: - testsuite.attrib['tests'] = str(num_errors + num_failures) - if num_errors > 0: - testcase = xml.etree.ElementTree.SubElement(testsuite, 'testcase') - testcase.attrib['name'] = 'errors' - error = xml.etree.ElementTree.SubElement(testcase, 'error') - error.text = '\n'.join(self._junit_errors) - if num_failures > 0: - # Group failures by file - failed_file_order = [] - failures_by_file = {} - for failure in self._junit_failures: - failed_file = failure[0] - if failed_file not in failed_file_order: - failed_file_order.append(failed_file) - failures_by_file[failed_file] = [] - failures_by_file[failed_file].append(failure) - # Create a testcase for each file - for failed_file in failed_file_order: - failures = failures_by_file[failed_file] - testcase = xml.etree.ElementTree.SubElement(testsuite, 'testcase') - testcase.attrib['name'] = failed_file - failure = xml.etree.ElementTree.SubElement(testcase, 'failure') - template = '{0}: {1} [{2}] [{3}]' - texts = [template.format(f[1], f[2], f[3], f[4]) for f in failures] - failure.text = '\n'.join(texts) - - xml_decl = '\n' - return xml_decl + xml.etree.ElementTree.tostring(testsuite, 'utf-8').decode('utf-8') - - -_cpplint_state = _CppLintState() - - -def _OutputFormat(): - """Gets the module's output format.""" - return _cpplint_state.output_format - - -def _SetOutputFormat(output_format): - """Sets the module's output format.""" - _cpplint_state.SetOutputFormat(output_format) - -def _Quiet(): - """Return's the module's quiet setting.""" - return _cpplint_state.quiet - -def _SetQuiet(quiet): - """Set the module's quiet status, and return previous setting.""" - return _cpplint_state.SetQuiet(quiet) - - -def _VerboseLevel(): - """Returns the module's verbosity setting.""" - return _cpplint_state.verbose_level - - -def _SetVerboseLevel(level): - """Sets the module's verbosity, and returns the previous setting.""" - return _cpplint_state.SetVerboseLevel(level) - - -def _SetCountingStyle(level): - """Sets the module's counting options.""" - _cpplint_state.SetCountingStyle(level) - - -def _Filters(): - """Returns the module's list of output filters, as a list.""" - return _cpplint_state.filters - - -def _SetFilters(filters): - """Sets the module's error-message filters. - - These filters are applied when deciding whether to emit a given - error message. - - Args: - filters: A string of comma-separated filters (eg "whitespace/indent"). - Each filter should start with + or -; else we die. - """ - _cpplint_state.SetFilters(filters) - -def _AddFilters(filters): - """Adds more filter overrides. - - Unlike _SetFilters, this function does not reset the current list of filters - available. - - Args: - filters: A string of comma-separated filters (eg "whitespace/indent"). - Each filter should start with + or -; else we die. - """ - _cpplint_state.AddFilters(filters) - -def _BackupFilters(): - """ Saves the current filter list to backup storage.""" - _cpplint_state.BackupFilters() - -def _RestoreFilters(): - """ Restores filters previously backed up.""" - _cpplint_state.RestoreFilters() - -class _FunctionState(object): - """Tracks current function name and the number of lines in its body.""" - - _NORMAL_TRIGGER = 250 # for --v=0, 500 for --v=1, etc. - _TEST_TRIGGER = 400 # about 50% more than _NORMAL_TRIGGER. - - def __init__(self): - self.in_a_function = False - self.lines_in_function = 0 - self.current_function = '' - - def Begin(self, function_name): - """Start analyzing function body. - - Args: - function_name: The name of the function being tracked. - """ - self.in_a_function = True - self.lines_in_function = 0 - self.current_function = function_name - - def Count(self): - """Count line in current function body.""" - if self.in_a_function: - self.lines_in_function += 1 - - def Check(self, error, filename, linenum): - """Report if too many lines in function body. - - Args: - error: The function to call with any errors found. - filename: The name of the current file. - linenum: The number of the line to check. - """ - if not self.in_a_function: - return - - if Match(r'T(EST|est)', self.current_function): - base_trigger = self._TEST_TRIGGER - else: - base_trigger = self._NORMAL_TRIGGER - trigger = base_trigger * 2**_VerboseLevel() - - if self.lines_in_function > trigger: - error_level = int(math.log(self.lines_in_function / base_trigger, 2)) - # 50 => 0, 100 => 1, 200 => 2, 400 => 3, 800 => 4, 1600 => 5, ... - if error_level > 5: - error_level = 5 - error(filename, linenum, 'readability/fn_size', error_level, - 'Small and focused functions are preferred:' - ' %s has %d non-comment lines' - ' (error triggered by exceeding %d lines).' % ( - self.current_function, self.lines_in_function, trigger)) - - def End(self): - """Stop analyzing function body.""" - self.in_a_function = False - - -class _IncludeError(Exception): - """Indicates a problem with the include order in a file.""" - pass - - -class FileInfo(object): - """Provides utility functions for filenames. - - FileInfo provides easy access to the components of a file's path - relative to the project root. - """ - - def __init__(self, filename): - self._filename = filename - - def FullName(self): - """Make Windows paths like Unix.""" - return os.path.abspath(self._filename).replace('\\', '/') - - def RepositoryName(self): - r"""FullName after removing the local path to the repository. - - If we have a real absolute path name here we can try to do something smart: - detecting the root of the checkout and truncating /path/to/checkout from - the name so that we get header guards that don't include things like - "C:\\Documents and Settings\\..." or "/home/username/..." in them and thus - people on different computers who have checked the source out to different - locations won't see bogus errors. - """ - fullname = self.FullName() - - if os.path.exists(fullname): - project_dir = os.path.dirname(fullname) - - # If the user specified a repository path, it exists, and the file is - # contained in it, use the specified repository path - if _repository: - repo = FileInfo(_repository).FullName() - root_dir = project_dir - while os.path.exists(root_dir): - # allow case insensitive compare on Windows - if os.path.normcase(root_dir) == os.path.normcase(repo): - return os.path.relpath(fullname, root_dir).replace('\\', '/') - one_up_dir = os.path.dirname(root_dir) - if one_up_dir == root_dir: - break - root_dir = one_up_dir - - if os.path.exists(os.path.join(project_dir, ".svn")): - # If there's a .svn file in the current directory, we recursively look - # up the directory tree for the top of the SVN checkout - root_dir = project_dir - one_up_dir = os.path.dirname(root_dir) - while os.path.exists(os.path.join(one_up_dir, ".svn")): - root_dir = os.path.dirname(root_dir) - one_up_dir = os.path.dirname(one_up_dir) - - prefix = os.path.commonprefix([root_dir, project_dir]) - return fullname[len(prefix) + 1:] - - # Not SVN <= 1.6? Try to find a git, hg, or svn top level directory by - # searching up from the current path. - root_dir = current_dir = os.path.dirname(fullname) - while current_dir != os.path.dirname(current_dir): - if (os.path.exists(os.path.join(current_dir, ".git")) or - os.path.exists(os.path.join(current_dir, ".hg")) or - os.path.exists(os.path.join(current_dir, ".svn"))): - root_dir = current_dir - current_dir = os.path.dirname(current_dir) - - if (os.path.exists(os.path.join(root_dir, ".git")) or - os.path.exists(os.path.join(root_dir, ".hg")) or - os.path.exists(os.path.join(root_dir, ".svn"))): - prefix = os.path.commonprefix([root_dir, project_dir]) - return fullname[len(prefix) + 1:] - - # Don't know what to do; header guard warnings may be wrong... - return fullname - - def Split(self): - """Splits the file into the directory, basename, and extension. - - For 'chrome/browser/browser.cc', Split() would - return ('chrome/browser', 'browser', '.cc') - - Returns: - A tuple of (directory, basename, extension). - """ - - googlename = self.RepositoryName() - project, rest = os.path.split(googlename) - return (project,) + os.path.splitext(rest) - - def BaseName(self): - """File base name - text after the final slash, before the final period.""" - return self.Split()[1] - - def Extension(self): - """File extension - text following the final period, includes that period.""" - return self.Split()[2] - - def NoExtension(self): - """File has no source file extension.""" - return '/'.join(self.Split()[0:2]) - - def IsSource(self): - """File has a source file extension.""" - return _IsSourceExtension(self.Extension()[1:]) - - -def _ShouldPrintError(category, confidence, linenum): - """If confidence >= verbose, category passes filter and is not suppressed.""" - - # There are three ways we might decide not to print an error message: - # a "NOLINT(category)" comment appears in the source, - # the verbosity level isn't high enough, or the filters filter it out. - if IsErrorSuppressedByNolint(category, linenum): - return False - - if confidence < _cpplint_state.verbose_level: - return False - - is_filtered = False - for one_filter in _Filters(): - if one_filter.startswith('-'): - if category.startswith(one_filter[1:]): - is_filtered = True - elif one_filter.startswith('+'): - if category.startswith(one_filter[1:]): - is_filtered = False - else: - assert False # should have been checked for in SetFilter. - if is_filtered: - return False - - return True - - -def Error(filename, linenum, category, confidence, message): - """Logs the fact we've found a lint error. - - We log where the error was found, and also our confidence in the error, - that is, how certain we are this is a legitimate style regression, and - not a misidentification or a use that's sometimes justified. - - False positives can be suppressed by the use of - "cpplint(category)" comments on the offending line. These are - parsed into _error_suppressions. - - Args: - filename: The name of the file containing the error. - linenum: The number of the line containing the error. - category: A string used to describe the "category" this bug - falls under: "whitespace", say, or "runtime". Categories - may have a hierarchy separated by slashes: "whitespace/indent". - confidence: A number from 1-5 representing a confidence score for - the error, with 5 meaning that we are certain of the problem, - and 1 meaning that it could be a legitimate construct. - message: The error message. - """ - if _ShouldPrintError(category, confidence, linenum): - _cpplint_state.IncrementErrorCount(category) - if _cpplint_state.output_format == 'vs7': - _cpplint_state.PrintError('%s(%s): error cpplint: [%s] %s [%d]\n' % ( - filename, linenum, category, message, confidence)) - elif _cpplint_state.output_format == 'eclipse': - sys.stderr.write('%s:%s: warning: %s [%s] [%d]\n' % ( - filename, linenum, message, category, confidence)) - elif _cpplint_state.output_format == 'junit': - _cpplint_state.AddJUnitFailure(filename, linenum, message, category, - confidence) - elif _cpplint_state.output_format in ['sed', 'gsed']: - if message in _SED_FIXUPS: - sys.stdout.write(_cpplint_state.output_format + " -i '%s%s' %s # %s [%s] [%d]\n" % ( - linenum, _SED_FIXUPS[message], filename, message, category, confidence)) - else: - sys.stderr.write('# %s:%s: "%s" [%s] [%d]\n' % ( - filename, linenum, message, category, confidence)) - else: - final_message = '%s:%s: %s [%s] [%d]\n' % ( - filename, linenum, message, category, confidence) - sys.stderr.write(final_message) - -# Matches standard C++ escape sequences per 2.13.2.3 of the C++ standard. -_RE_PATTERN_CLEANSE_LINE_ESCAPES = re.compile( - r'\\([abfnrtv?"\\\']|\d+|x[0-9a-fA-F]+)') -# Match a single C style comment on the same line. -_RE_PATTERN_C_COMMENTS = r'/\*(?:[^*]|\*(?!/))*\*/' -# Matches multi-line C style comments. -# This RE is a little bit more complicated than one might expect, because we -# have to take care of space removals tools so we can handle comments inside -# statements better. -# The current rule is: We only clear spaces from both sides when we're at the -# end of the line. Otherwise, we try to remove spaces from the right side, -# if this doesn't work we try on left side but only if there's a non-character -# on the right. -_RE_PATTERN_CLEANSE_LINE_C_COMMENTS = re.compile( - r'(\s*' + _RE_PATTERN_C_COMMENTS + r'\s*$|' + - _RE_PATTERN_C_COMMENTS + r'\s+|' + - r'\s+' + _RE_PATTERN_C_COMMENTS + r'(?=\W)|' + - _RE_PATTERN_C_COMMENTS + r')') - - -def IsCppString(line): - """Does line terminate so, that the next symbol is in string constant. - - This function does not consider single-line nor multi-line comments. - - Args: - line: is a partial line of code starting from the 0..n. - - Returns: - True, if next character appended to 'line' is inside a - string constant. - """ - - line = line.replace(r'\\', 'XX') # after this, \\" does not match to \" - return ((line.count('"') - line.count(r'\"') - line.count("'\"'")) & 1) == 1 - - -def CleanseRawStrings(raw_lines): - """Removes C++11 raw strings from lines. - - Before: - static const char kData[] = R"( - multi-line string - )"; - - After: - static const char kData[] = "" - (replaced by blank line) - ""; - - Args: - raw_lines: list of raw lines. - - Returns: - list of lines with C++11 raw strings replaced by empty strings. - """ - - delimiter = None - lines_without_raw_strings = [] - for line in raw_lines: - if delimiter: - # Inside a raw string, look for the end - end = line.find(delimiter) - if end >= 0: - # Found the end of the string, match leading space for this - # line and resume copying the original lines, and also insert - # a "" on the last line. - leading_space = Match(r'^(\s*)\S', line) - line = leading_space.group(1) + '""' + line[end + len(delimiter):] - delimiter = None - else: - # Haven't found the end yet, append a blank line. - line = '""' - - # Look for beginning of a raw string, and replace them with - # empty strings. This is done in a loop to handle multiple raw - # strings on the same line. - while delimiter is None: - # Look for beginning of a raw string. - # See 2.14.15 [lex.string] for syntax. - # - # Once we have matched a raw string, we check the prefix of the - # line to make sure that the line is not part of a single line - # comment. It's done this way because we remove raw strings - # before removing comments as opposed to removing comments - # before removing raw strings. This is because there are some - # cpplint checks that requires the comments to be preserved, but - # we don't want to check comments that are inside raw strings. - matched = Match(r'^(.*?)\b(?:R|u8R|uR|UR|LR)"([^\s\\()]*)\((.*)$', line) - if (matched and - not Match(r'^([^\'"]|\'(\\.|[^\'])*\'|"(\\.|[^"])*")*//', - matched.group(1))): - delimiter = ')' + matched.group(2) + '"' - - end = matched.group(3).find(delimiter) - if end >= 0: - # Raw string ended on same line - line = (matched.group(1) + '""' + - matched.group(3)[end + len(delimiter):]) - delimiter = None - else: - # Start of a multi-line raw string - line = matched.group(1) + '""' - else: - break - - lines_without_raw_strings.append(line) - - # TODO(unknown): if delimiter is not None here, we might want to - # emit a warning for unterminated string. - return lines_without_raw_strings - - -def FindNextMultiLineCommentStart(lines, lineix): - """Find the beginning marker for a multiline comment.""" - while lineix < len(lines): - if lines[lineix].strip().startswith('/*'): - # Only return this marker if the comment goes beyond this line - if lines[lineix].strip().find('*/', 2) < 0: - return lineix - lineix += 1 - return len(lines) - - -def FindNextMultiLineCommentEnd(lines, lineix): - """We are inside a comment, find the end marker.""" - while lineix < len(lines): - if lines[lineix].strip().endswith('*/'): - return lineix - lineix += 1 - return len(lines) - - -def RemoveMultiLineCommentsFromRange(lines, begin, end): - """Clears a range of lines for multi-line comments.""" - # Having // dummy comments makes the lines non-empty, so we will not get - # unnecessary blank line warnings later in the code. - for i in range(begin, end): - lines[i] = '/**/' - - -def RemoveMultiLineComments(filename, lines, error): - """Removes multiline (c-style) comments from lines.""" - lineix = 0 - while lineix < len(lines): - lineix_begin = FindNextMultiLineCommentStart(lines, lineix) - if lineix_begin >= len(lines): - return - lineix_end = FindNextMultiLineCommentEnd(lines, lineix_begin) - if lineix_end >= len(lines): - error(filename, lineix_begin + 1, 'readability/multiline_comment', 5, - 'Could not find end of multi-line comment') - return - RemoveMultiLineCommentsFromRange(lines, lineix_begin, lineix_end + 1) - lineix = lineix_end + 1 - - -def CleanseComments(line): - """Removes //-comments and single-line C-style /* */ comments. - - Args: - line: A line of C++ source. - - Returns: - The line with single-line comments removed. - """ - commentpos = line.find('//') - if commentpos != -1 and not IsCppString(line[:commentpos]): - line = line[:commentpos].rstrip() - # get rid of /* ... */ - return _RE_PATTERN_CLEANSE_LINE_C_COMMENTS.sub('', line) - - -class CleansedLines(object): - """Holds 4 copies of all lines with different preprocessing applied to them. - - 1) elided member contains lines without strings and comments. - 2) lines member contains lines without comments. - 3) raw_lines member contains all the lines without processing. - 4) lines_without_raw_strings member is same as raw_lines, but with C++11 raw - strings removed. - All these members are of , and of the same length. - """ - - def __init__(self, lines): - self.elided = [] - self.lines = [] - self.raw_lines = lines - self.num_lines = len(lines) - self.lines_without_raw_strings = CleanseRawStrings(lines) - for linenum in range(len(self.lines_without_raw_strings)): - self.lines.append(CleanseComments( - self.lines_without_raw_strings[linenum])) - elided = self._CollapseStrings(self.lines_without_raw_strings[linenum]) - self.elided.append(CleanseComments(elided)) - - def NumLines(self): - """Returns the number of lines represented.""" - return self.num_lines - - @staticmethod - def _CollapseStrings(elided): - """Collapses strings and chars on a line to simple "" or '' blocks. - - We nix strings first so we're not fooled by text like '"http://"' - - Args: - elided: The line being processed. - - Returns: - The line with collapsed strings. - """ - if _RE_PATTERN_INCLUDE.match(elided): - return elided - - # Remove escaped characters first to make quote/single quote collapsing - # basic. Things that look like escaped characters shouldn't occur - # outside of strings and chars. - elided = _RE_PATTERN_CLEANSE_LINE_ESCAPES.sub('', elided) - - # Replace quoted strings and digit separators. Both single quotes - # and double quotes are processed in the same loop, otherwise - # nested quotes wouldn't work. - collapsed = '' - while True: - # Find the first quote character - match = Match(r'^([^\'"]*)([\'"])(.*)$', elided) - if not match: - collapsed += elided - break - head, quote, tail = match.groups() - - if quote == '"': - # Collapse double quoted strings - second_quote = tail.find('"') - if second_quote >= 0: - collapsed += head + '""' - elided = tail[second_quote + 1:] - else: - # Unmatched double quote, don't bother processing the rest - # of the line since this is probably a multiline string. - collapsed += elided - break - else: - # Found single quote, check nearby text to eliminate digit separators. - # - # There is no special handling for floating point here, because - # the integer/fractional/exponent parts would all be parsed - # correctly as long as there are digits on both sides of the - # separator. So we are fine as long as we don't see something - # like "0.'3" (gcc 4.9.0 will not allow this literal). - if Search(r'\b(?:0[bBxX]?|[1-9])[0-9a-fA-F]*$', head): - match_literal = Match(r'^((?:\'?[0-9a-zA-Z_])*)(.*)$', "'" + tail) - collapsed += head + match_literal.group(1).replace("'", '') - elided = match_literal.group(2) - else: - second_quote = tail.find('\'') - if second_quote >= 0: - collapsed += head + "''" - elided = tail[second_quote + 1:] - else: - # Unmatched single quote - collapsed += elided - break - - return collapsed - - -def FindEndOfExpressionInLine(line, startpos, stack): - """Find the position just after the end of current parenthesized expression. - - Args: - line: a CleansedLines line. - startpos: start searching at this position. - stack: nesting stack at startpos. - - Returns: - On finding matching end: (index just after matching end, None) - On finding an unclosed expression: (-1, None) - Otherwise: (-1, new stack at end of this line) - """ - for i in xrange(startpos, len(line)): - char = line[i] - if char in '([{': - # Found start of parenthesized expression, push to expression stack - stack.append(char) - elif char == '<': - # Found potential start of template argument list - if i > 0 and line[i - 1] == '<': - # Left shift operator - if stack and stack[-1] == '<': - stack.pop() - if not stack: - return (-1, None) - elif i > 0 and Search(r'\boperator\s*$', line[0:i]): - # operator<, don't add to stack - continue - else: - # Tentative start of template argument list - stack.append('<') - elif char in ')]}': - # Found end of parenthesized expression. - # - # If we are currently expecting a matching '>', the pending '<' - # must have been an operator. Remove them from expression stack. - while stack and stack[-1] == '<': - stack.pop() - if not stack: - return (-1, None) - if ((stack[-1] == '(' and char == ')') or - (stack[-1] == '[' and char == ']') or - (stack[-1] == '{' and char == '}')): - stack.pop() - if not stack: - return (i + 1, None) - else: - # Mismatched parentheses - return (-1, None) - elif char == '>': - # Found potential end of template argument list. - - # Ignore "->" and operator functions - if (i > 0 and - (line[i - 1] == '-' or Search(r'\boperator\s*$', line[0:i - 1]))): - continue - - # Pop the stack if there is a matching '<'. Otherwise, ignore - # this '>' since it must be an operator. - if stack: - if stack[-1] == '<': - stack.pop() - if not stack: - return (i + 1, None) - elif char == ';': - # Found something that look like end of statements. If we are currently - # expecting a '>', the matching '<' must have been an operator, since - # template argument list should not contain statements. - while stack and stack[-1] == '<': - stack.pop() - if not stack: - return (-1, None) - - # Did not find end of expression or unbalanced parentheses on this line - return (-1, stack) - - -def CloseExpression(clean_lines, linenum, pos): - """If input points to ( or { or [ or <, finds the position that closes it. - - If lines[linenum][pos] points to a '(' or '{' or '[' or '<', finds the - linenum/pos that correspond to the closing of the expression. - - TODO(unknown): cpplint spends a fair bit of time matching parentheses. - Ideally we would want to index all opening and closing parentheses once - and have CloseExpression be just a simple lookup, but due to preprocessor - tricks, this is not so easy. - - Args: - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - pos: A position on the line. - - Returns: - A tuple (line, linenum, pos) pointer *past* the closing brace, or - (line, len(lines), -1) if we never find a close. Note we ignore - strings and comments when matching; and the line we return is the - 'cleansed' line at linenum. - """ - - line = clean_lines.elided[linenum] - if (line[pos] not in '({[<') or Match(r'<[<=]', line[pos:]): - return (line, clean_lines.NumLines(), -1) - - # Check first line - (end_pos, stack) = FindEndOfExpressionInLine(line, pos, []) - if end_pos > -1: - return (line, linenum, end_pos) - - # Continue scanning forward - while stack and linenum < clean_lines.NumLines() - 1: - linenum += 1 - line = clean_lines.elided[linenum] - (end_pos, stack) = FindEndOfExpressionInLine(line, 0, stack) - if end_pos > -1: - return (line, linenum, end_pos) - - # Did not find end of expression before end of file, give up - return (line, clean_lines.NumLines(), -1) - - -def FindStartOfExpressionInLine(line, endpos, stack): - """Find position at the matching start of current expression. - - This is almost the reverse of FindEndOfExpressionInLine, but note - that the input position and returned position differs by 1. - - Args: - line: a CleansedLines line. - endpos: start searching at this position. - stack: nesting stack at endpos. - - Returns: - On finding matching start: (index at matching start, None) - On finding an unclosed expression: (-1, None) - Otherwise: (-1, new stack at beginning of this line) - """ - i = endpos - while i >= 0: - char = line[i] - if char in ')]}': - # Found end of expression, push to expression stack - stack.append(char) - elif char == '>': - # Found potential end of template argument list. - # - # Ignore it if it's a "->" or ">=" or "operator>" - if (i > 0 and - (line[i - 1] == '-' or - Match(r'\s>=\s', line[i - 1:]) or - Search(r'\boperator\s*$', line[0:i]))): - i -= 1 - else: - stack.append('>') - elif char == '<': - # Found potential start of template argument list - if i > 0 and line[i - 1] == '<': - # Left shift operator - i -= 1 - else: - # If there is a matching '>', we can pop the expression stack. - # Otherwise, ignore this '<' since it must be an operator. - if stack and stack[-1] == '>': - stack.pop() - if not stack: - return (i, None) - elif char in '([{': - # Found start of expression. - # - # If there are any unmatched '>' on the stack, they must be - # operators. Remove those. - while stack and stack[-1] == '>': - stack.pop() - if not stack: - return (-1, None) - if ((char == '(' and stack[-1] == ')') or - (char == '[' and stack[-1] == ']') or - (char == '{' and stack[-1] == '}')): - stack.pop() - if not stack: - return (i, None) - else: - # Mismatched parentheses - return (-1, None) - elif char == ';': - # Found something that look like end of statements. If we are currently - # expecting a '<', the matching '>' must have been an operator, since - # template argument list should not contain statements. - while stack and stack[-1] == '>': - stack.pop() - if not stack: - return (-1, None) - - i -= 1 - - return (-1, stack) - - -def ReverseCloseExpression(clean_lines, linenum, pos): - """If input points to ) or } or ] or >, finds the position that opens it. - - If lines[linenum][pos] points to a ')' or '}' or ']' or '>', finds the - linenum/pos that correspond to the opening of the expression. - - Args: - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - pos: A position on the line. - - Returns: - A tuple (line, linenum, pos) pointer *at* the opening brace, or - (line, 0, -1) if we never find the matching opening brace. Note - we ignore strings and comments when matching; and the line we - return is the 'cleansed' line at linenum. - """ - line = clean_lines.elided[linenum] - if line[pos] not in ')}]>': - return (line, 0, -1) - - # Check last line - (start_pos, stack) = FindStartOfExpressionInLine(line, pos, []) - if start_pos > -1: - return (line, linenum, start_pos) - - # Continue scanning backward - while stack and linenum > 0: - linenum -= 1 - line = clean_lines.elided[linenum] - (start_pos, stack) = FindStartOfExpressionInLine(line, len(line) - 1, stack) - if start_pos > -1: - return (line, linenum, start_pos) - - # Did not find start of expression before beginning of file, give up - return (line, 0, -1) - - -def CheckForCopyright(filename, lines, error): - """Logs an error if no Copyright message appears at the top of the file.""" - - # We'll say it should occur by line 10. Don't forget there's a - # dummy line at the front. - for line in xrange(1, min(len(lines), 11)): - if re.search(r'Copyright', lines[line], re.I): break - else: # means no copyright line was found - error(filename, 0, 'legal/copyright', 5, - 'No copyright message found. ' - 'You should have a line: "Copyright [year] "') - - -def GetIndentLevel(line): - """Return the number of leading spaces in line. - - Args: - line: A string to check. - - Returns: - An integer count of leading spaces, possibly zero. - """ - indent = Match(r'^( *)\S', line) - if indent: - return len(indent.group(1)) - else: - return 0 - -def PathSplitToList(path): - """Returns the path split into a list by the separator. - - Args: - path: An absolute or relative path (e.g. '/a/b/c/' or '../a') - - Returns: - A list of path components (e.g. ['a', 'b', 'c]). - """ - lst = [] - while True: - (head, tail) = os.path.split(path) - if head == path: # absolute paths end - lst.append(head) - break - if tail == path: # relative paths end - lst.append(tail) - break - - path = head - lst.append(tail) - - lst.reverse() - return lst - -def GetHeaderGuardCPPVariable(filename): - """Returns the CPP variable that should be used as a header guard. - - Args: - filename: The name of a C++ header file. - - Returns: - The CPP variable that should be used as a header guard in the - named file. - - """ - - # Restores original filename in case that cpplint is invoked from Emacs's - # flymake. - filename = re.sub(r'_flymake\.h$', '.h', filename) - filename = re.sub(r'/\.flymake/([^/]*)$', r'/\1', filename) - # Replace 'c++' with 'cpp'. - filename = filename.replace('C++', 'cpp').replace('c++', 'cpp') - - fileinfo = FileInfo(filename) - file_path_from_root = fileinfo.RepositoryName() - - def FixupPathFromRoot(): - if _root_debug: - sys.stderr.write("\n_root fixup, _root = '%s', repository name = '%s'\n" - % (_root, fileinfo.RepositoryName())) - - # Process the file path with the --root flag if it was set. - if not _root: - if _root_debug: - sys.stderr.write("_root unspecified\n") - return file_path_from_root - - def StripListPrefix(lst, prefix): - # f(['x', 'y'], ['w, z']) -> None (not a valid prefix) - if lst[:len(prefix)] != prefix: - return None - # f(['a, 'b', 'c', 'd'], ['a', 'b']) -> ['c', 'd'] - return lst[(len(prefix)):] - - # root behavior: - # --root=subdir , lstrips subdir from the header guard - maybe_path = StripListPrefix(PathSplitToList(file_path_from_root), - PathSplitToList(_root)) - - if _root_debug: - sys.stderr.write(("_root lstrip (maybe_path=%s, file_path_from_root=%s," + - " _root=%s)\n") % (maybe_path, file_path_from_root, _root)) - - if maybe_path: - return os.path.join(*maybe_path) - - # --root=.. , will prepend the outer directory to the header guard - full_path = fileinfo.FullName() - root_abspath = os.path.abspath(_root) - - maybe_path = StripListPrefix(PathSplitToList(full_path), - PathSplitToList(root_abspath)) - - if _root_debug: - sys.stderr.write(("_root prepend (maybe_path=%s, full_path=%s, " + - "root_abspath=%s)\n") % (maybe_path, full_path, root_abspath)) - - if maybe_path: - return os.path.join(*maybe_path) - - if _root_debug: - sys.stderr.write("_root ignore, returning %s\n" % (file_path_from_root)) - - # --root=FAKE_DIR is ignored - return file_path_from_root - - file_path_from_root = FixupPathFromRoot() - return re.sub(r'[^a-zA-Z0-9]', '_', file_path_from_root).upper() + '_' - - -def CheckForHeaderGuard(filename, clean_lines, error): - """Checks that the file contains a header guard. - - Logs an error if no #ifndef header guard is present. For other - headers, checks that the full pathname is used. - - Args: - filename: The name of the C++ header file. - clean_lines: A CleansedLines instance containing the file. - error: The function to call with any errors found. - """ - - # Don't check for header guards if there are error suppression - # comments somewhere in this file. - # - # Because this is silencing a warning for a nonexistent line, we - # only support the very specific NOLINT(build/header_guard) syntax, - # and not the general NOLINT or NOLINT(*) syntax. - raw_lines = clean_lines.lines_without_raw_strings - for i in raw_lines: - if Search(r'//\s*NOLINT\(build/header_guard\)', i): - return - - # Allow pragma once instead of header guards - for i in raw_lines: - if Search(r'^\s*#pragma\s+once', i): - return - - cppvar = GetHeaderGuardCPPVariable(filename) - - ifndef = '' - ifndef_linenum = 0 - define = '' - endif = '' - endif_linenum = 0 - for linenum, line in enumerate(raw_lines): - linesplit = line.split() - if len(linesplit) >= 2: - # find the first occurrence of #ifndef and #define, save arg - if not ifndef and linesplit[0] == '#ifndef': - # set ifndef to the header guard presented on the #ifndef line. - ifndef = linesplit[1] - ifndef_linenum = linenum - if not define and linesplit[0] == '#define': - define = linesplit[1] - # find the last occurrence of #endif, save entire line - if line.startswith('#endif'): - endif = line - endif_linenum = linenum - - if not ifndef or not define or ifndef != define: - error(filename, 0, 'build/header_guard', 5, - 'No #ifndef header guard found, suggested CPP variable is: %s' % - cppvar) - return - - # The guard should be PATH_FILE_H_, but we also allow PATH_FILE_H__ - # for backward compatibility. - if ifndef != cppvar: - error_level = 0 - if ifndef != cppvar + '_': - error_level = 5 - - ParseNolintSuppressions(filename, raw_lines[ifndef_linenum], ifndef_linenum, - error) - error(filename, ifndef_linenum, 'build/header_guard', error_level, - '#ifndef header guard has wrong style, please use: %s' % cppvar) - - # Check for "//" comments on endif line. - ParseNolintSuppressions(filename, raw_lines[endif_linenum], endif_linenum, - error) - match = Match(r'#endif\s*//\s*' + cppvar + r'(_)?\b', endif) - if match: - if match.group(1) == '_': - # Issue low severity warning for deprecated double trailing underscore - error(filename, endif_linenum, 'build/header_guard', 0, - '#endif line should be "#endif // %s"' % cppvar) - return - - # Didn't find the corresponding "//" comment. If this file does not - # contain any "//" comments at all, it could be that the compiler - # only wants "/**/" comments, look for those instead. - no_single_line_comments = True - for i in xrange(1, len(raw_lines) - 1): - line = raw_lines[i] - if Match(r'^(?:(?:\'(?:\.|[^\'])*\')|(?:"(?:\.|[^"])*")|[^\'"])*//', line): - no_single_line_comments = False - break - - if no_single_line_comments: - match = Match(r'#endif\s*/\*\s*' + cppvar + r'(_)?\s*\*/', endif) - if match: - if match.group(1) == '_': - # Low severity warning for double trailing underscore - error(filename, endif_linenum, 'build/header_guard', 0, - '#endif line should be "#endif /* %s */"' % cppvar) - return - - # Didn't find anything - error(filename, endif_linenum, 'build/header_guard', 5, - '#endif line should be "#endif // %s"' % cppvar) - - -def CheckHeaderFileIncluded(filename, include_state, error): - """Logs an error if a source file does not include its header.""" - - # Do not check test files - fileinfo = FileInfo(filename) - if Search(_TEST_FILE_SUFFIX, fileinfo.BaseName()): - return - - for ext in GetHeaderExtensions(): - basefilename = filename[0:len(filename) - len(fileinfo.Extension())] - headerfile = basefilename + '.' + ext - if not os.path.exists(headerfile): - continue - headername = FileInfo(headerfile).RepositoryName() - first_include = None - include_uses_unix_dir_aliases = False - for section_list in include_state.include_list: - for f in section_list: - include_text = f[0] - if "./" in include_text: - include_uses_unix_dir_aliases = True - if headername in include_text or include_text in headername: - return - if not first_include: - first_include = f[1] - - message = '%s should include its header file %s' % (fileinfo.RepositoryName(), headername) - if include_uses_unix_dir_aliases: - message += ". Relative paths like . and .. are not allowed." - - error(filename, first_include, 'build/include', 5, message) - - -def CheckForBadCharacters(filename, lines, error): - """Logs an error for each line containing bad characters. - - Two kinds of bad characters: - - 1. Unicode replacement characters: These indicate that either the file - contained invalid UTF-8 (likely) or Unicode replacement characters (which - it shouldn't). Note that it's possible for this to throw off line - numbering if the invalid UTF-8 occurred adjacent to a newline. - - 2. NUL bytes. These are problematic for some tools. - - Args: - filename: The name of the current file. - lines: An array of strings, each representing a line of the file. - error: The function to call with any errors found. - """ - for linenum, line in enumerate(lines): - if unicode_escape_decode('\ufffd') in line: - error(filename, linenum, 'readability/utf8', 5, - 'Line contains invalid UTF-8 (or Unicode replacement character).') - if '\0' in line: - error(filename, linenum, 'readability/nul', 5, 'Line contains NUL byte.') - - -def CheckForNewlineAtEOF(filename, lines, error): - """Logs an error if there is no newline char at the end of the file. - - Args: - filename: The name of the current file. - lines: An array of strings, each representing a line of the file. - error: The function to call with any errors found. - """ - - # The array lines() was created by adding two newlines to the - # original file (go figure), then splitting on \n. - # To verify that the file ends in \n, we just have to make sure the - # last-but-two element of lines() exists and is empty. - if len(lines) < 3 or lines[-2]: - error(filename, len(lines) - 2, 'whitespace/ending_newline', 5, - 'Could not find a newline character at the end of the file.') - - -def CheckForMultilineCommentsAndStrings(filename, clean_lines, linenum, error): - """Logs an error if we see /* ... */ or "..." that extend past one line. - - /* ... */ comments are legit inside macros, for one line. - Otherwise, we prefer // comments, so it's ok to warn about the - other. Likewise, it's ok for strings to extend across multiple - lines, as long as a line continuation character (backslash) - terminates each line. Although not currently prohibited by the C++ - style guide, it's ugly and unnecessary. We don't do well with either - in this lint program, so we warn about both. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - # Remove all \\ (escaped backslashes) from the line. They are OK, and the - # second (escaped) slash may trigger later \" detection erroneously. - line = line.replace('\\\\', '') - - if line.count('/*') > line.count('*/'): - error(filename, linenum, 'readability/multiline_comment', 5, - 'Complex multi-line /*...*/-style comment found. ' - 'Lint may give bogus warnings. ' - 'Consider replacing these with //-style comments, ' - 'with #if 0...#endif, ' - 'or with more clearly structured multi-line comments.') - - if (line.count('"') - line.count('\\"')) % 2: - error(filename, linenum, 'readability/multiline_string', 5, - 'Multi-line string ("...") found. This lint script doesn\'t ' - 'do well with such strings, and may give bogus warnings. ' - 'Use C++11 raw strings or concatenation instead.') - - -# (non-threadsafe name, thread-safe alternative, validation pattern) -# -# The validation pattern is used to eliminate false positives such as: -# _rand(); // false positive due to substring match. -# ->rand(); // some member function rand(). -# ACMRandom rand(seed); // some variable named rand. -# ISAACRandom rand(); // another variable named rand. -# -# Basically we require the return value of these functions to be used -# in some expression context on the same line by matching on some -# operator before the function name. This eliminates constructors and -# member function calls. -_UNSAFE_FUNC_PREFIX = r'(?:[-+*/=%^&|(<]\s*|>\s+)' -_THREADING_LIST = ( - ('asctime(', 'asctime_r(', _UNSAFE_FUNC_PREFIX + r'asctime\([^)]+\)'), - ('ctime(', 'ctime_r(', _UNSAFE_FUNC_PREFIX + r'ctime\([^)]+\)'), - ('getgrgid(', 'getgrgid_r(', _UNSAFE_FUNC_PREFIX + r'getgrgid\([^)]+\)'), - ('getgrnam(', 'getgrnam_r(', _UNSAFE_FUNC_PREFIX + r'getgrnam\([^)]+\)'), - ('getlogin(', 'getlogin_r(', _UNSAFE_FUNC_PREFIX + r'getlogin\(\)'), - ('getpwnam(', 'getpwnam_r(', _UNSAFE_FUNC_PREFIX + r'getpwnam\([^)]+\)'), - ('getpwuid(', 'getpwuid_r(', _UNSAFE_FUNC_PREFIX + r'getpwuid\([^)]+\)'), - ('gmtime(', 'gmtime_r(', _UNSAFE_FUNC_PREFIX + r'gmtime\([^)]+\)'), - ('localtime(', 'localtime_r(', _UNSAFE_FUNC_PREFIX + r'localtime\([^)]+\)'), - ('rand(', 'rand_r(', _UNSAFE_FUNC_PREFIX + r'rand\(\)'), - ('strtok(', 'strtok_r(', - _UNSAFE_FUNC_PREFIX + r'strtok\([^)]+\)'), - ('ttyname(', 'ttyname_r(', _UNSAFE_FUNC_PREFIX + r'ttyname\([^)]+\)'), - ) - - -def CheckPosixThreading(filename, clean_lines, linenum, error): - """Checks for calls to thread-unsafe functions. - - Much code has been originally written without consideration of - multi-threading. Also, engineers are relying on their old experience; - they have learned posix before threading extensions were added. These - tests guide the engineers to use thread-safe functions (when using - posix directly). - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - for single_thread_func, multithread_safe_func, pattern in _THREADING_LIST: - # Additional pattern matching check to confirm that this is the - # function we are looking for - if Search(pattern, line): - error(filename, linenum, 'runtime/threadsafe_fn', 2, - 'Consider using ' + multithread_safe_func + - '...) instead of ' + single_thread_func + - '...) for improved thread safety.') - - -def CheckVlogArguments(filename, clean_lines, linenum, error): - """Checks that VLOG() is only used for defining a logging level. - - For example, VLOG(2) is correct. VLOG(INFO), VLOG(WARNING), VLOG(ERROR), and - VLOG(FATAL) are not. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - if Search(r'\bVLOG\((INFO|ERROR|WARNING|DFATAL|FATAL)\)', line): - error(filename, linenum, 'runtime/vlog', 5, - 'VLOG() should be used with numeric verbosity level. ' - 'Use LOG() if you want symbolic severity levels.') - -# Matches invalid increment: *count++, which moves pointer instead of -# incrementing a value. -_RE_PATTERN_INVALID_INCREMENT = re.compile( - r'^\s*\*\w+(\+\+|--);') - - -def CheckInvalidIncrement(filename, clean_lines, linenum, error): - """Checks for invalid increment *count++. - - For example following function: - void increment_counter(int* count) { - *count++; - } - is invalid, because it effectively does count++, moving pointer, and should - be replaced with ++*count, (*count)++ or *count += 1. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - if _RE_PATTERN_INVALID_INCREMENT.match(line): - error(filename, linenum, 'runtime/invalid_increment', 5, - 'Changing pointer instead of value (or unused value of operator*).') - - -def IsMacroDefinition(clean_lines, linenum): - if Search(r'^#define', clean_lines[linenum]): - return True - - if linenum > 0 and Search(r'\\$', clean_lines[linenum - 1]): - return True - - return False - - -def IsForwardClassDeclaration(clean_lines, linenum): - return Match(r'^\s*(\btemplate\b)*.*class\s+\w+;\s*$', clean_lines[linenum]) - - -class _BlockInfo(object): - """Stores information about a generic block of code.""" - - def __init__(self, linenum, seen_open_brace): - self.starting_linenum = linenum - self.seen_open_brace = seen_open_brace - self.open_parentheses = 0 - self.inline_asm = _NO_ASM - self.check_namespace_indentation = False - - def CheckBegin(self, filename, clean_lines, linenum, error): - """Run checks that applies to text up to the opening brace. - - This is mostly for checking the text after the class identifier - and the "{", usually where the base class is specified. For other - blocks, there isn't much to check, so we always pass. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - pass - - def CheckEnd(self, filename, clean_lines, linenum, error): - """Run checks that applies to text after the closing brace. - - This is mostly used for checking end of namespace comments. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - pass - - def IsBlockInfo(self): - """Returns true if this block is a _BlockInfo. - - This is convenient for verifying that an object is an instance of - a _BlockInfo, but not an instance of any of the derived classes. - - Returns: - True for this class, False for derived classes. - """ - return self.__class__ == _BlockInfo - - -class _ExternCInfo(_BlockInfo): - """Stores information about an 'extern "C"' block.""" - - def __init__(self, linenum): - _BlockInfo.__init__(self, linenum, True) - - -class _ClassInfo(_BlockInfo): - """Stores information about a class.""" - - def __init__(self, name, class_or_struct, clean_lines, linenum): - _BlockInfo.__init__(self, linenum, False) - self.name = name - self.is_derived = False - self.check_namespace_indentation = True - if class_or_struct == 'struct': - self.access = 'public' - self.is_struct = True - else: - self.access = 'private' - self.is_struct = False - - # Remember initial indentation level for this class. Using raw_lines here - # instead of elided to account for leading comments. - self.class_indent = GetIndentLevel(clean_lines.raw_lines[linenum]) - - # Try to find the end of the class. This will be confused by things like: - # class A { - # } *x = { ... - # - # But it's still good enough for CheckSectionSpacing. - self.last_line = 0 - depth = 0 - for i in range(linenum, clean_lines.NumLines()): - line = clean_lines.elided[i] - depth += line.count('{') - line.count('}') - if not depth: - self.last_line = i - break - - def CheckBegin(self, filename, clean_lines, linenum, error): - # Look for a bare ':' - if Search('(^|[^:]):($|[^:])', clean_lines.elided[linenum]): - self.is_derived = True - - def CheckEnd(self, filename, clean_lines, linenum, error): - # If there is a DISALLOW macro, it should appear near the end of - # the class. - seen_last_thing_in_class = False - for i in xrange(linenum - 1, self.starting_linenum, -1): - match = Search( - r'\b(DISALLOW_COPY_AND_ASSIGN|DISALLOW_IMPLICIT_CONSTRUCTORS)\(' + - self.name + r'\)', - clean_lines.elided[i]) - if match: - if seen_last_thing_in_class: - error(filename, i, 'readability/constructors', 3, - match.group(1) + ' should be the last thing in the class') - break - - if not Match(r'^\s*$', clean_lines.elided[i]): - seen_last_thing_in_class = True - - # Check that closing brace is aligned with beginning of the class. - # Only do this if the closing brace is indented by only whitespaces. - # This means we will not check single-line class definitions. - indent = Match(r'^( *)\}', clean_lines.elided[linenum]) - if indent and len(indent.group(1)) != self.class_indent: - if self.is_struct: - parent = 'struct ' + self.name - else: - parent = 'class ' + self.name - error(filename, linenum, 'whitespace/indent', 3, - 'Closing brace should be aligned with beginning of %s' % parent) - - -class _NamespaceInfo(_BlockInfo): - """Stores information about a namespace.""" - - def __init__(self, name, linenum): - _BlockInfo.__init__(self, linenum, False) - self.name = name or '' - self.check_namespace_indentation = True - - def CheckEnd(self, filename, clean_lines, linenum, error): - """Check end of namespace comments.""" - line = clean_lines.raw_lines[linenum] - - # Check how many lines is enclosed in this namespace. Don't issue - # warning for missing namespace comments if there aren't enough - # lines. However, do apply checks if there is already an end of - # namespace comment and it's incorrect. - # - # TODO(unknown): We always want to check end of namespace comments - # if a namespace is large, but sometimes we also want to apply the - # check if a short namespace contained nontrivial things (something - # other than forward declarations). There is currently no logic on - # deciding what these nontrivial things are, so this check is - # triggered by namespace size only, which works most of the time. - if (linenum - self.starting_linenum < 10 - and not Match(r'^\s*};*\s*(//|/\*).*\bnamespace\b', line)): - return - - # Look for matching comment at end of namespace. - # - # Note that we accept C style "/* */" comments for terminating - # namespaces, so that code that terminate namespaces inside - # preprocessor macros can be cpplint clean. - # - # We also accept stuff like "// end of namespace ." with the - # period at the end. - # - # Besides these, we don't accept anything else, otherwise we might - # get false negatives when existing comment is a substring of the - # expected namespace. - if self.name: - # Named namespace - if not Match((r'^\s*};*\s*(//|/\*).*\bnamespace\s+' + - re.escape(self.name) + r'[\*/\.\\\s]*$'), - line): - error(filename, linenum, 'readability/namespace', 5, - 'Namespace should be terminated with "// namespace %s"' % - self.name) - else: - # Anonymous namespace - if not Match(r'^\s*};*\s*(//|/\*).*\bnamespace[\*/\.\\\s]*$', line): - # If "// namespace anonymous" or "// anonymous namespace (more text)", - # mention "// anonymous namespace" as an acceptable form - if Match(r'^\s*}.*\b(namespace anonymous|anonymous namespace)\b', line): - error(filename, linenum, 'readability/namespace', 5, - 'Anonymous namespace should be terminated with "// namespace"' - ' or "// anonymous namespace"') - else: - error(filename, linenum, 'readability/namespace', 5, - 'Anonymous namespace should be terminated with "// namespace"') - - -class _PreprocessorInfo(object): - """Stores checkpoints of nesting stacks when #if/#else is seen.""" - - def __init__(self, stack_before_if): - # The entire nesting stack before #if - self.stack_before_if = stack_before_if - - # The entire nesting stack up to #else - self.stack_before_else = [] - - # Whether we have already seen #else or #elif - self.seen_else = False - - -class NestingState(object): - """Holds states related to parsing braces.""" - - def __init__(self): - # Stack for tracking all braces. An object is pushed whenever we - # see a "{", and popped when we see a "}". Only 3 types of - # objects are possible: - # - _ClassInfo: a class or struct. - # - _NamespaceInfo: a namespace. - # - _BlockInfo: some other type of block. - self.stack = [] - - # Top of the previous stack before each Update(). - # - # Because the nesting_stack is updated at the end of each line, we - # had to do some convoluted checks to find out what is the current - # scope at the beginning of the line. This check is simplified by - # saving the previous top of nesting stack. - # - # We could save the full stack, but we only need the top. Copying - # the full nesting stack would slow down cpplint by ~10%. - self.previous_stack_top = [] - - # Stack of _PreprocessorInfo objects. - self.pp_stack = [] - - def SeenOpenBrace(self): - """Check if we have seen the opening brace for the innermost block. - - Returns: - True if we have seen the opening brace, False if the innermost - block is still expecting an opening brace. - """ - return (not self.stack) or self.stack[-1].seen_open_brace - - def InNamespaceBody(self): - """Check if we are currently one level inside a namespace body. - - Returns: - True if top of the stack is a namespace block, False otherwise. - """ - return self.stack and isinstance(self.stack[-1], _NamespaceInfo) - - def InExternC(self): - """Check if we are currently one level inside an 'extern "C"' block. - - Returns: - True if top of the stack is an extern block, False otherwise. - """ - return self.stack and isinstance(self.stack[-1], _ExternCInfo) - - def InClassDeclaration(self): - """Check if we are currently one level inside a class or struct declaration. - - Returns: - True if top of the stack is a class/struct, False otherwise. - """ - return self.stack and isinstance(self.stack[-1], _ClassInfo) - - def InAsmBlock(self): - """Check if we are currently one level inside an inline ASM block. - - Returns: - True if the top of the stack is a block containing inline ASM. - """ - return self.stack and self.stack[-1].inline_asm != _NO_ASM - - def InTemplateArgumentList(self, clean_lines, linenum, pos): - """Check if current position is inside template argument list. - - Args: - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - pos: position just after the suspected template argument. - Returns: - True if (linenum, pos) is inside template arguments. - """ - while linenum < clean_lines.NumLines(): - # Find the earliest character that might indicate a template argument - line = clean_lines.elided[linenum] - match = Match(r'^[^{};=\[\]\.<>]*(.)', line[pos:]) - if not match: - linenum += 1 - pos = 0 - continue - token = match.group(1) - pos += len(match.group(0)) - - # These things do not look like template argument list: - # class Suspect { - # class Suspect x; } - if token in ('{', '}', ';'): return False - - # These things look like template argument list: - # template - # template - # template - # template - if token in ('>', '=', '[', ']', '.'): return True - - # Check if token is an unmatched '<'. - # If not, move on to the next character. - if token != '<': - pos += 1 - if pos >= len(line): - linenum += 1 - pos = 0 - continue - - # We can't be sure if we just find a single '<', and need to - # find the matching '>'. - (_, end_line, end_pos) = CloseExpression(clean_lines, linenum, pos - 1) - if end_pos < 0: - # Not sure if template argument list or syntax error in file - return False - linenum = end_line - pos = end_pos - return False - - def UpdatePreprocessor(self, line): - """Update preprocessor stack. - - We need to handle preprocessors due to classes like this: - #ifdef SWIG - struct ResultDetailsPageElementExtensionPoint { - #else - struct ResultDetailsPageElementExtensionPoint : public Extension { - #endif - - We make the following assumptions (good enough for most files): - - Preprocessor condition evaluates to true from #if up to first - #else/#elif/#endif. - - - Preprocessor condition evaluates to false from #else/#elif up - to #endif. We still perform lint checks on these lines, but - these do not affect nesting stack. - - Args: - line: current line to check. - """ - if Match(r'^\s*#\s*(if|ifdef|ifndef)\b', line): - # Beginning of #if block, save the nesting stack here. The saved - # stack will allow us to restore the parsing state in the #else case. - self.pp_stack.append(_PreprocessorInfo(copy.deepcopy(self.stack))) - elif Match(r'^\s*#\s*(else|elif)\b', line): - # Beginning of #else block - if self.pp_stack: - if not self.pp_stack[-1].seen_else: - # This is the first #else or #elif block. Remember the - # whole nesting stack up to this point. This is what we - # keep after the #endif. - self.pp_stack[-1].seen_else = True - self.pp_stack[-1].stack_before_else = copy.deepcopy(self.stack) - - # Restore the stack to how it was before the #if - self.stack = copy.deepcopy(self.pp_stack[-1].stack_before_if) - else: - # TODO(unknown): unexpected #else, issue warning? - pass - elif Match(r'^\s*#\s*endif\b', line): - # End of #if or #else blocks. - if self.pp_stack: - # If we saw an #else, we will need to restore the nesting - # stack to its former state before the #else, otherwise we - # will just continue from where we left off. - if self.pp_stack[-1].seen_else: - # Here we can just use a shallow copy since we are the last - # reference to it. - self.stack = self.pp_stack[-1].stack_before_else - # Drop the corresponding #if - self.pp_stack.pop() - else: - # TODO(unknown): unexpected #endif, issue warning? - pass - - # TODO(unknown): Update() is too long, but we will refactor later. - def Update(self, filename, clean_lines, linenum, error): - """Update nesting state with current line. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - # Remember top of the previous nesting stack. - # - # The stack is always pushed/popped and not modified in place, so - # we can just do a shallow copy instead of copy.deepcopy. Using - # deepcopy would slow down cpplint by ~28%. - if self.stack: - self.previous_stack_top = self.stack[-1] - else: - self.previous_stack_top = None - - # Update pp_stack - self.UpdatePreprocessor(line) - - # Count parentheses. This is to avoid adding struct arguments to - # the nesting stack. - if self.stack: - inner_block = self.stack[-1] - depth_change = line.count('(') - line.count(')') - inner_block.open_parentheses += depth_change - - # Also check if we are starting or ending an inline assembly block. - if inner_block.inline_asm in (_NO_ASM, _END_ASM): - if (depth_change != 0 and - inner_block.open_parentheses == 1 and - _MATCH_ASM.match(line)): - # Enter assembly block - inner_block.inline_asm = _INSIDE_ASM - else: - # Not entering assembly block. If previous line was _END_ASM, - # we will now shift to _NO_ASM state. - inner_block.inline_asm = _NO_ASM - elif (inner_block.inline_asm == _INSIDE_ASM and - inner_block.open_parentheses == 0): - # Exit assembly block - inner_block.inline_asm = _END_ASM - - # Consume namespace declaration at the beginning of the line. Do - # this in a loop so that we catch same line declarations like this: - # namespace proto2 { namespace bridge { class MessageSet; } } - while True: - # Match start of namespace. The "\b\s*" below catches namespace - # declarations even if it weren't followed by a whitespace, this - # is so that we don't confuse our namespace checker. The - # missing spaces will be flagged by CheckSpacing. - namespace_decl_match = Match(r'^\s*namespace\b\s*([:\w]+)?(.*)$', line) - if not namespace_decl_match: - break - - new_namespace = _NamespaceInfo(namespace_decl_match.group(1), linenum) - self.stack.append(new_namespace) - - line = namespace_decl_match.group(2) - if line.find('{') != -1: - new_namespace.seen_open_brace = True - line = line[line.find('{') + 1:] - - # Look for a class declaration in whatever is left of the line - # after parsing namespaces. The regexp accounts for decorated classes - # such as in: - # class LOCKABLE API Object { - # }; - class_decl_match = Match( - r'^(\s*(?:template\s*<[\w\s<>,:=]*>\s*)?' - r'(class|struct)\s+(?:[a-zA-Z0-9_]+\s+)*(\w+(?:::\w+)*))' - r'(.*)$', line) - if (class_decl_match and - (not self.stack or self.stack[-1].open_parentheses == 0)): - # We do not want to accept classes that are actually template arguments: - # template , - # template class Ignore3> - # void Function() {}; - # - # To avoid template argument cases, we scan forward and look for - # an unmatched '>'. If we see one, assume we are inside a - # template argument list. - end_declaration = len(class_decl_match.group(1)) - if not self.InTemplateArgumentList(clean_lines, linenum, end_declaration): - self.stack.append(_ClassInfo( - class_decl_match.group(3), class_decl_match.group(2), - clean_lines, linenum)) - line = class_decl_match.group(4) - - # If we have not yet seen the opening brace for the innermost block, - # run checks here. - if not self.SeenOpenBrace(): - self.stack[-1].CheckBegin(filename, clean_lines, linenum, error) - - # Update access control if we are inside a class/struct - if self.stack and isinstance(self.stack[-1], _ClassInfo): - classinfo = self.stack[-1] - access_match = Match( - r'^(.*)\b(public|private|protected|signals)(\s+(?:slots\s*)?)?' - r':(?:[^:]|$)', - line) - if access_match: - classinfo.access = access_match.group(2) - - # Check that access keywords are indented +1 space. Skip this - # check if the keywords are not preceded by whitespaces. - indent = access_match.group(1) - if (len(indent) != classinfo.class_indent + 1 and - Match(r'^\s*$', indent)): - if classinfo.is_struct: - parent = 'struct ' + classinfo.name - else: - parent = 'class ' + classinfo.name - slots = '' - if access_match.group(3): - slots = access_match.group(3) - error(filename, linenum, 'whitespace/indent', 3, - '%s%s: should be indented +1 space inside %s' % ( - access_match.group(2), slots, parent)) - - # Consume braces or semicolons from what's left of the line - while True: - # Match first brace, semicolon, or closed parenthesis. - matched = Match(r'^[^{;)}]*([{;)}])(.*)$', line) - if not matched: - break - - token = matched.group(1) - if token == '{': - # If namespace or class hasn't seen a opening brace yet, mark - # namespace/class head as complete. Push a new block onto the - # stack otherwise. - if not self.SeenOpenBrace(): - self.stack[-1].seen_open_brace = True - elif Match(r'^extern\s*"[^"]*"\s*\{', line): - self.stack.append(_ExternCInfo(linenum)) - else: - self.stack.append(_BlockInfo(linenum, True)) - if _MATCH_ASM.match(line): - self.stack[-1].inline_asm = _BLOCK_ASM - - elif token == ';' or token == ')': - # If we haven't seen an opening brace yet, but we already saw - # a semicolon, this is probably a forward declaration. Pop - # the stack for these. - # - # Similarly, if we haven't seen an opening brace yet, but we - # already saw a closing parenthesis, then these are probably - # function arguments with extra "class" or "struct" keywords. - # Also pop these stack for these. - if not self.SeenOpenBrace(): - self.stack.pop() - else: # token == '}' - # Perform end of block checks and pop the stack. - if self.stack: - self.stack[-1].CheckEnd(filename, clean_lines, linenum, error) - self.stack.pop() - line = matched.group(2) - - def InnermostClass(self): - """Get class info on the top of the stack. - - Returns: - A _ClassInfo object if we are inside a class, or None otherwise. - """ - for i in range(len(self.stack), 0, -1): - classinfo = self.stack[i - 1] - if isinstance(classinfo, _ClassInfo): - return classinfo - return None - - def CheckCompletedBlocks(self, filename, error): - """Checks that all classes and namespaces have been completely parsed. - - Call this when all lines in a file have been processed. - Args: - filename: The name of the current file. - error: The function to call with any errors found. - """ - # Note: This test can result in false positives if #ifdef constructs - # get in the way of brace matching. See the testBuildClass test in - # cpplint_unittest.py for an example of this. - for obj in self.stack: - if isinstance(obj, _ClassInfo): - error(filename, obj.starting_linenum, 'build/class', 5, - 'Failed to find complete declaration of class %s' % - obj.name) - elif isinstance(obj, _NamespaceInfo): - error(filename, obj.starting_linenum, 'build/namespaces', 5, - 'Failed to find complete declaration of namespace %s' % - obj.name) - - -def CheckForNonStandardConstructs(filename, clean_lines, linenum, - nesting_state, error): - r"""Logs an error if we see certain non-ANSI constructs ignored by gcc-2. - - Complain about several constructs which gcc-2 accepts, but which are - not standard C++. Warning about these in lint is one way to ease the - transition to new compilers. - - put storage class first (e.g. "static const" instead of "const static"). - - "%lld" instead of %qd" in printf-type functions. - - "%1$d" is non-standard in printf-type functions. - - "\%" is an undefined character escape sequence. - - text after #endif is not allowed. - - invalid inner-style forward declaration. - - >? and ?= and )\?=?\s*(\w+|[+-]?\d+)(\.\d*)?', - line): - error(filename, linenum, 'build/deprecated', 3, - '>? and ))?' - # r'\s*const\s*' + type_name + '\s*&\s*\w+\s*;' - error(filename, linenum, 'runtime/member_string_references', 2, - 'const string& members are dangerous. It is much better to use ' - 'alternatives, such as pointers or simple constants.') - - # Everything else in this function operates on class declarations. - # Return early if the top of the nesting stack is not a class, or if - # the class head is not completed yet. - classinfo = nesting_state.InnermostClass() - if not classinfo or not classinfo.seen_open_brace: - return - - # The class may have been declared with namespace or classname qualifiers. - # The constructor and destructor will not have those qualifiers. - base_classname = classinfo.name.split('::')[-1] - - # Look for single-argument constructors that aren't marked explicit. - # Technically a valid construct, but against style. - explicit_constructor_match = Match( - r'\s+(?:(?:inline|constexpr)\s+)*(explicit\s+)?' - r'(?:(?:inline|constexpr)\s+)*%s\s*' - r'\(((?:[^()]|\([^()]*\))*)\)' - % re.escape(base_classname), - line) - - if explicit_constructor_match: - is_marked_explicit = explicit_constructor_match.group(1) - - if not explicit_constructor_match.group(2): - constructor_args = [] - else: - constructor_args = explicit_constructor_match.group(2).split(',') - - # collapse arguments so that commas in template parameter lists and function - # argument parameter lists don't split arguments in two - i = 0 - while i < len(constructor_args): - constructor_arg = constructor_args[i] - while (constructor_arg.count('<') > constructor_arg.count('>') or - constructor_arg.count('(') > constructor_arg.count(')')): - constructor_arg += ',' + constructor_args[i + 1] - del constructor_args[i + 1] - constructor_args[i] = constructor_arg - i += 1 - - variadic_args = [arg for arg in constructor_args if '&&...' in arg] - defaulted_args = [arg for arg in constructor_args if '=' in arg] - noarg_constructor = (not constructor_args or # empty arg list - # 'void' arg specifier - (len(constructor_args) == 1 and - constructor_args[0].strip() == 'void')) - onearg_constructor = ((len(constructor_args) == 1 and # exactly one arg - not noarg_constructor) or - # all but at most one arg defaulted - (len(constructor_args) >= 1 and - not noarg_constructor and - len(defaulted_args) >= len(constructor_args) - 1) or - # variadic arguments with zero or one argument - (len(constructor_args) <= 2 and - len(variadic_args) >= 1)) - initializer_list_constructor = bool( - onearg_constructor and - Search(r'\bstd\s*::\s*initializer_list\b', constructor_args[0])) - copy_constructor = bool( - onearg_constructor and - Match(r'((const\s+(volatile\s+)?)?|(volatile\s+(const\s+)?))?' - r'%s(\s*<[^>]*>)?(\s+const)?\s*(?:<\w+>\s*)?&' - % re.escape(base_classname), constructor_args[0].strip())) - - if (not is_marked_explicit and - onearg_constructor and - not initializer_list_constructor and - not copy_constructor): - if defaulted_args or variadic_args: - error(filename, linenum, 'runtime/explicit', 5, - 'Constructors callable with one argument ' - 'should be marked explicit.') - else: - error(filename, linenum, 'runtime/explicit', 5, - 'Single-parameter constructors should be marked explicit.') - elif is_marked_explicit and not onearg_constructor: - if noarg_constructor: - error(filename, linenum, 'runtime/explicit', 5, - 'Zero-parameter constructors should not be marked explicit.') - - -def CheckSpacingForFunctionCall(filename, clean_lines, linenum, error): - """Checks for the correctness of various spacing around function calls. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - # Since function calls often occur inside if/for/while/switch - # expressions - which have their own, more liberal conventions - we - # first see if we should be looking inside such an expression for a - # function call, to which we can apply more strict standards. - fncall = line # if there's no control flow construct, look at whole line - for pattern in (r'\bif\s*\((.*)\)\s*{', - r'\bfor\s*\((.*)\)\s*{', - r'\bwhile\s*\((.*)\)\s*[{;]', - r'\bswitch\s*\((.*)\)\s*{'): - match = Search(pattern, line) - if match: - fncall = match.group(1) # look inside the parens for function calls - break - - # Except in if/for/while/switch, there should never be space - # immediately inside parens (eg "f( 3, 4 )"). We make an exception - # for nested parens ( (a+b) + c ). Likewise, there should never be - # a space before a ( when it's a function argument. I assume it's a - # function argument when the char before the whitespace is legal in - # a function name (alnum + _) and we're not starting a macro. Also ignore - # pointers and references to arrays and functions coz they're too tricky: - # we use a very simple way to recognize these: - # " (something)(maybe-something)" or - # " (something)(maybe-something," or - # " (something)[something]" - # Note that we assume the contents of [] to be short enough that - # they'll never need to wrap. - if ( # Ignore control structures. - not Search(r'\b(if|for|while|switch|return|new|delete|catch|sizeof)\b', - fncall) and - # Ignore pointers/references to functions. - not Search(r' \([^)]+\)\([^)]*(\)|,$)', fncall) and - # Ignore pointers/references to arrays. - not Search(r' \([^)]+\)\[[^\]]+\]', fncall)): - if Search(r'\w\s*\(\s(?!\s*\\$)', fncall): # a ( used for a fn call - error(filename, linenum, 'whitespace/parens', 4, - 'Extra space after ( in function call') - elif Search(r'\(\s+(?!(\s*\\)|\()', fncall): - error(filename, linenum, 'whitespace/parens', 2, - 'Extra space after (') - if (Search(r'\w\s+\(', fncall) and - not Search(r'_{0,2}asm_{0,2}\s+_{0,2}volatile_{0,2}\s+\(', fncall) and - not Search(r'#\s*define|typedef|using\s+\w+\s*=', fncall) and - not Search(r'\w\s+\((\w+::)*\*\w+\)\(', fncall) and - not Search(r'\bcase\s+\(', fncall)): - # TODO(unknown): Space after an operator function seem to be a common - # error, silence those for now by restricting them to highest verbosity. - if Search(r'\boperator_*\b', line): - error(filename, linenum, 'whitespace/parens', 0, - 'Extra space before ( in function call') - else: - error(filename, linenum, 'whitespace/parens', 4, - 'Extra space before ( in function call') - # If the ) is followed only by a newline or a { + newline, assume it's - # part of a control statement (if/while/etc), and don't complain - if Search(r'[^)]\s+\)\s*[^{\s]', fncall): - # If the closing parenthesis is preceded by only whitespaces, - # try to give a more descriptive error message. - if Search(r'^\s+\)', fncall): - error(filename, linenum, 'whitespace/parens', 2, - 'Closing ) should be moved to the previous line') - else: - error(filename, linenum, 'whitespace/parens', 2, - 'Extra space before )') - - -def IsBlankLine(line): - """Returns true if the given line is blank. - - We consider a line to be blank if the line is empty or consists of - only white spaces. - - Args: - line: A line of a string. - - Returns: - True, if the given line is blank. - """ - return not line or line.isspace() - - -def CheckForNamespaceIndentation(filename, nesting_state, clean_lines, line, - error): - is_namespace_indent_item = ( - len(nesting_state.stack) > 1 and - nesting_state.stack[-1].check_namespace_indentation and - isinstance(nesting_state.previous_stack_top, _NamespaceInfo) and - nesting_state.previous_stack_top == nesting_state.stack[-2]) - - if ShouldCheckNamespaceIndentation(nesting_state, is_namespace_indent_item, - clean_lines.elided, line): - CheckItemIndentationInNamespace(filename, clean_lines.elided, - line, error) - - -def CheckForFunctionLengths(filename, clean_lines, linenum, - function_state, error): - """Reports for long function bodies. - - For an overview why this is done, see: - https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Write_Short_Functions - - Uses a simplistic algorithm assuming other style guidelines - (especially spacing) are followed. - Only checks unindented functions, so class members are unchecked. - Trivial bodies are unchecked, so constructors with huge initializer lists - may be missed. - Blank/comment lines are not counted so as to avoid encouraging the removal - of vertical space and comments just to get through a lint check. - NOLINT *on the last line of a function* disables this check. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - function_state: Current function name and lines in body so far. - error: The function to call with any errors found. - """ - lines = clean_lines.lines - line = lines[linenum] - joined_line = '' - - starting_func = False - regexp = r'(\w(\w|::|\*|\&|\s)*)\(' # decls * & space::name( ... - match_result = Match(regexp, line) - if match_result: - # If the name is all caps and underscores, figure it's a macro and - # ignore it, unless it's TEST or TEST_F. - function_name = match_result.group(1).split()[-1] - if function_name == 'TEST' or function_name == 'TEST_F' or ( - not Match(r'[A-Z_]+$', function_name)): - starting_func = True - - if starting_func: - body_found = False - for start_linenum in xrange(linenum, clean_lines.NumLines()): - start_line = lines[start_linenum] - joined_line += ' ' + start_line.lstrip() - if Search(r'(;|})', start_line): # Declarations and trivial functions - body_found = True - break # ... ignore - if Search(r'{', start_line): - body_found = True - function = Search(r'((\w|:)*)\(', line).group(1) - if Match(r'TEST', function): # Handle TEST... macros - parameter_regexp = Search(r'(\(.*\))', joined_line) - if parameter_regexp: # Ignore bad syntax - function += parameter_regexp.group(1) - else: - function += '()' - function_state.Begin(function) - break - if not body_found: - # No body for the function (or evidence of a non-function) was found. - error(filename, linenum, 'readability/fn_size', 5, - 'Lint failed to find start of function body.') - elif Match(r'^\}\s*$', line): # function end - function_state.Check(error, filename, linenum) - function_state.End() - elif not Match(r'^\s*$', line): - function_state.Count() # Count non-blank/non-comment lines. - - -_RE_PATTERN_TODO = re.compile(r'^//(\s*)TODO(\(.+?\))?:?(\s|$)?') - - -def CheckComment(line, filename, linenum, next_line_start, error): - """Checks for common mistakes in comments. - - Args: - line: The line in question. - filename: The name of the current file. - linenum: The number of the line to check. - next_line_start: The first non-whitespace column of the next line. - error: The function to call with any errors found. - """ - commentpos = line.find('//') - if commentpos != -1: - # Check if the // may be in quotes. If so, ignore it - if re.sub(r'\\.', '', line[0:commentpos]).count('"') % 2 == 0: - # Allow one space for new scopes, two spaces otherwise: - if (not (Match(r'^.*{ *//', line) and next_line_start == commentpos) and - ((commentpos >= 1 and - line[commentpos-1] not in string.whitespace) or - (commentpos >= 2 and - line[commentpos-2] not in string.whitespace))): - error(filename, linenum, 'whitespace/comments', 2, - 'At least two spaces is best between code and comments') - - # Checks for common mistakes in TODO comments. - comment = line[commentpos:] - match = _RE_PATTERN_TODO.match(comment) - if match: - # One whitespace is correct; zero whitespace is handled elsewhere. - leading_whitespace = match.group(1) - if len(leading_whitespace) > 1: - error(filename, linenum, 'whitespace/todo', 2, - 'Too many spaces before TODO') - - username = match.group(2) - if not username: - error(filename, linenum, 'readability/todo', 2, - 'Missing username in TODO; it should look like ' - '"// TODO(my_username): Stuff."') - - middle_whitespace = match.group(3) - # Comparisons made explicit for correctness -- pylint: disable=g-explicit-bool-comparison - if middle_whitespace != ' ' and middle_whitespace != '': - error(filename, linenum, 'whitespace/todo', 2, - 'TODO(my_username) should be followed by a space') - - # If the comment contains an alphanumeric character, there - # should be a space somewhere between it and the // unless - # it's a /// or //! Doxygen comment. - if (Match(r'//[^ ]*\w', comment) and - not Match(r'(///|//\!)(\s+|$)', comment)): - error(filename, linenum, 'whitespace/comments', 4, - 'Should have a space between // and comment') - - -def CheckSpacing(filename, clean_lines, linenum, nesting_state, error): - """Checks for the correctness of various spacing issues in the code. - - Things we check for: spaces around operators, spaces after - if/for/while/switch, no spaces around parens in function calls, two - spaces between code and comment, don't start a block with a blank - line, don't end a function with a blank line, don't add a blank line - after public/protected/private, don't have too many blank lines in a row. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - nesting_state: A NestingState instance which maintains information about - the current stack of nested blocks being parsed. - error: The function to call with any errors found. - """ - - # Don't use "elided" lines here, otherwise we can't check commented lines. - # Don't want to use "raw" either, because we don't want to check inside C++11 - # raw strings, - raw = clean_lines.lines_without_raw_strings - line = raw[linenum] - - # Before nixing comments, check if the line is blank for no good - # reason. This includes the first line after a block is opened, and - # blank lines at the end of a function (ie, right before a line like '}' - # - # Skip all the blank line checks if we are immediately inside a - # namespace body. In other words, don't issue blank line warnings - # for this block: - # namespace { - # - # } - # - # A warning about missing end of namespace comments will be issued instead. - # - # Also skip blank line checks for 'extern "C"' blocks, which are formatted - # like namespaces. - if (IsBlankLine(line) and - not nesting_state.InNamespaceBody() and - not nesting_state.InExternC()): - elided = clean_lines.elided - prev_line = elided[linenum - 1] - prevbrace = prev_line.rfind('{') - # TODO(unknown): Don't complain if line before blank line, and line after, - # both start with alnums and are indented the same amount. - # This ignores whitespace at the start of a namespace block - # because those are not usually indented. - if prevbrace != -1 and prev_line[prevbrace:].find('}') == -1: - # OK, we have a blank line at the start of a code block. Before we - # complain, we check if it is an exception to the rule: The previous - # non-empty line has the parameters of a function header that are indented - # 4 spaces (because they did not fit in a 80 column line when placed on - # the same line as the function name). We also check for the case where - # the previous line is indented 6 spaces, which may happen when the - # initializers of a constructor do not fit into a 80 column line. - exception = False - if Match(r' {6}\w', prev_line): # Initializer list? - # We are looking for the opening column of initializer list, which - # should be indented 4 spaces to cause 6 space indentation afterwards. - search_position = linenum-2 - while (search_position >= 0 - and Match(r' {6}\w', elided[search_position])): - search_position -= 1 - exception = (search_position >= 0 - and elided[search_position][:5] == ' :') - else: - # Search for the function arguments or an initializer list. We use a - # simple heuristic here: If the line is indented 4 spaces; and we have a - # closing paren, without the opening paren, followed by an opening brace - # or colon (for initializer lists) we assume that it is the last line of - # a function header. If we have a colon indented 4 spaces, it is an - # initializer list. - exception = (Match(r' {4}\w[^\(]*\)\s*(const\s*)?(\{\s*$|:)', - prev_line) - or Match(r' {4}:', prev_line)) - - if not exception: - error(filename, linenum, 'whitespace/blank_line', 2, - 'Redundant blank line at the start of a code block ' - 'should be deleted.') - # Ignore blank lines at the end of a block in a long if-else - # chain, like this: - # if (condition1) { - # // Something followed by a blank line - # - # } else if (condition2) { - # // Something else - # } - if linenum + 1 < clean_lines.NumLines(): - next_line = raw[linenum + 1] - if (next_line - and Match(r'\s*}', next_line) - and next_line.find('} else ') == -1): - error(filename, linenum, 'whitespace/blank_line', 3, - 'Redundant blank line at the end of a code block ' - 'should be deleted.') - - matched = Match(r'\s*(public|protected|private):', prev_line) - if matched: - error(filename, linenum, 'whitespace/blank_line', 3, - 'Do not leave a blank line after "%s:"' % matched.group(1)) - - # Next, check comments - next_line_start = 0 - if linenum + 1 < clean_lines.NumLines(): - next_line = raw[linenum + 1] - next_line_start = len(next_line) - len(next_line.lstrip()) - CheckComment(line, filename, linenum, next_line_start, error) - - # get rid of comments and strings - line = clean_lines.elided[linenum] - - # You shouldn't have spaces before your brackets, except maybe after - # 'delete []', 'return []() {};', or 'auto [abc, ...] = ...;'. - if Search(r'\w\s+\[', line) and not Search(r'(?:auto&?|delete|return)\s+\[', line): - error(filename, linenum, 'whitespace/braces', 5, - 'Extra space before [') - - # In range-based for, we wanted spaces before and after the colon, but - # not around "::" tokens that might appear. - if (Search(r'for *\(.*[^:]:[^: ]', line) or - Search(r'for *\(.*[^: ]:[^:]', line)): - error(filename, linenum, 'whitespace/forcolon', 2, - 'Missing space around colon in range-based for loop') - - -def CheckOperatorSpacing(filename, clean_lines, linenum, error): - """Checks for horizontal spacing around operators. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - # Don't try to do spacing checks for operator methods. Do this by - # replacing the troublesome characters with something else, - # preserving column position for all other characters. - # - # The replacement is done repeatedly to avoid false positives from - # operators that call operators. - while True: - match = Match(r'^(.*\boperator\b)(\S+)(\s*\(.*)$', line) - if match: - line = match.group(1) + ('_' * len(match.group(2))) + match.group(3) - else: - break - - # We allow no-spaces around = within an if: "if ( (a=Foo()) == 0 )". - # Otherwise not. Note we only check for non-spaces on *both* sides; - # sometimes people put non-spaces on one side when aligning ='s among - # many lines (not that this is behavior that I approve of...) - if ((Search(r'[\w.]=', line) or - Search(r'=[\w.]', line)) - and not Search(r'\b(if|while|for) ', line) - # Operators taken from [lex.operators] in C++11 standard. - and not Search(r'(>=|<=|==|!=|&=|\^=|\|=|\+=|\*=|\/=|\%=)', line) - and not Search(r'operator=', line)): - error(filename, linenum, 'whitespace/operators', 4, - 'Missing spaces around =') - - # It's ok not to have spaces around binary operators like + - * /, but if - # there's too little whitespace, we get concerned. It's hard to tell, - # though, so we punt on this one for now. TODO. - - # You should always have whitespace around binary operators. - # - # Check <= and >= first to avoid false positives with < and >, then - # check non-include lines for spacing around < and >. - # - # If the operator is followed by a comma, assume it's be used in a - # macro context and don't do any checks. This avoids false - # positives. - # - # Note that && is not included here. This is because there are too - # many false positives due to RValue references. - match = Search(r'[^<>=!\s](==|!=|<=|>=|\|\|)[^<>=!\s,;\)]', line) - if match: - error(filename, linenum, 'whitespace/operators', 3, - 'Missing spaces around %s' % match.group(1)) - elif not Match(r'#.*include', line): - # Look for < that is not surrounded by spaces. This is only - # triggered if both sides are missing spaces, even though - # technically should should flag if at least one side is missing a - # space. This is done to avoid some false positives with shifts. - match = Match(r'^(.*[^\s<])<[^\s=<,]', line) - if match: - (_, _, end_pos) = CloseExpression( - clean_lines, linenum, len(match.group(1))) - if end_pos <= -1: - error(filename, linenum, 'whitespace/operators', 3, - 'Missing spaces around <') - - # Look for > that is not surrounded by spaces. Similar to the - # above, we only trigger if both sides are missing spaces to avoid - # false positives with shifts. - match = Match(r'^(.*[^-\s>])>[^\s=>,]', line) - if match: - (_, _, start_pos) = ReverseCloseExpression( - clean_lines, linenum, len(match.group(1))) - if start_pos <= -1: - error(filename, linenum, 'whitespace/operators', 3, - 'Missing spaces around >') - - # We allow no-spaces around << when used like this: 10<<20, but - # not otherwise (particularly, not when used as streams) - # - # We also allow operators following an opening parenthesis, since - # those tend to be macros that deal with operators. - match = Search(r'(operator|[^\s(<])(?:L|UL|LL|ULL|l|ul|ll|ull)?<<([^\s,=<])', line) - if (match and not (match.group(1).isdigit() and match.group(2).isdigit()) and - not (match.group(1) == 'operator' and match.group(2) == ';')): - error(filename, linenum, 'whitespace/operators', 3, - 'Missing spaces around <<') - - # We allow no-spaces around >> for almost anything. This is because - # C++11 allows ">>" to close nested templates, which accounts for - # most cases when ">>" is not followed by a space. - # - # We still warn on ">>" followed by alpha character, because that is - # likely due to ">>" being used for right shifts, e.g.: - # value >> alpha - # - # When ">>" is used to close templates, the alphanumeric letter that - # follows would be part of an identifier, and there should still be - # a space separating the template type and the identifier. - # type> alpha - match = Search(r'>>[a-zA-Z_]', line) - if match: - error(filename, linenum, 'whitespace/operators', 3, - 'Missing spaces around >>') - - # There shouldn't be space around unary operators - match = Search(r'(!\s|~\s|[\s]--[\s;]|[\s]\+\+[\s;])', line) - if match: - error(filename, linenum, 'whitespace/operators', 4, - 'Extra space for operator %s' % match.group(1)) - - -def CheckParenthesisSpacing(filename, clean_lines, linenum, error): - """Checks for horizontal spacing around parentheses. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - # No spaces after an if, while, switch, or for - match = Search(r' (if\(|for\(|while\(|switch\()', line) - if match: - error(filename, linenum, 'whitespace/parens', 5, - 'Missing space before ( in %s' % match.group(1)) - - # For if/for/while/switch, the left and right parens should be - # consistent about how many spaces are inside the parens, and - # there should either be zero or one spaces inside the parens. - # We don't want: "if ( foo)" or "if ( foo )". - # Exception: "for ( ; foo; bar)" and "for (foo; bar; )" are allowed. - match = Search(r'\b(if|for|while|switch)\s*' - r'\(([ ]*)(.).*[^ ]+([ ]*)\)\s*{\s*$', - line) - if match: - if len(match.group(2)) != len(match.group(4)): - if not (match.group(3) == ';' and - len(match.group(2)) == 1 + len(match.group(4)) or - not match.group(2) and Search(r'\bfor\s*\(.*; \)', line)): - error(filename, linenum, 'whitespace/parens', 5, - 'Mismatching spaces inside () in %s' % match.group(1)) - if len(match.group(2)) not in [0, 1]: - error(filename, linenum, 'whitespace/parens', 5, - 'Should have zero or one spaces inside ( and ) in %s' % - match.group(1)) - - -def CheckCommaSpacing(filename, clean_lines, linenum, error): - """Checks for horizontal spacing near commas and semicolons. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - raw = clean_lines.lines_without_raw_strings - line = clean_lines.elided[linenum] - - # You should always have a space after a comma (either as fn arg or operator) - # - # This does not apply when the non-space character following the - # comma is another comma, since the only time when that happens is - # for empty macro arguments. - # - # We run this check in two passes: first pass on elided lines to - # verify that lines contain missing whitespaces, second pass on raw - # lines to confirm that those missing whitespaces are not due to - # elided comments. - if (Search(r',[^,\s]', ReplaceAll(r'\boperator\s*,\s*\(', 'F(', line)) and - Search(r',[^,\s]', raw[linenum])): - error(filename, linenum, 'whitespace/comma', 3, - 'Missing space after ,') - - # You should always have a space after a semicolon - # except for few corner cases - # TODO(unknown): clarify if 'if (1) { return 1;}' is requires one more - # space after ; - if Search(r';[^\s};\\)/]', line): - error(filename, linenum, 'whitespace/semicolon', 3, - 'Missing space after ;') - - -def _IsType(clean_lines, nesting_state, expr): - """Check if expression looks like a type name, returns true if so. - - Args: - clean_lines: A CleansedLines instance containing the file. - nesting_state: A NestingState instance which maintains information about - the current stack of nested blocks being parsed. - expr: The expression to check. - Returns: - True, if token looks like a type. - """ - # Keep only the last token in the expression - last_word = Match(r'^.*(\b\S+)$', expr) - if last_word: - token = last_word.group(1) - else: - token = expr - - # Match native types and stdint types - if _TYPES.match(token): - return True - - # Try a bit harder to match templated types. Walk up the nesting - # stack until we find something that resembles a typename - # declaration for what we are looking for. - typename_pattern = (r'\b(?:typename|class|struct)\s+' + re.escape(token) + - r'\b') - block_index = len(nesting_state.stack) - 1 - while block_index >= 0: - if isinstance(nesting_state.stack[block_index], _NamespaceInfo): - return False - - # Found where the opening brace is. We want to scan from this - # line up to the beginning of the function, minus a few lines. - # template - # class C - # : public ... { // start scanning here - last_line = nesting_state.stack[block_index].starting_linenum - - next_block_start = 0 - if block_index > 0: - next_block_start = nesting_state.stack[block_index - 1].starting_linenum - first_line = last_line - while first_line >= next_block_start: - if clean_lines.elided[first_line].find('template') >= 0: - break - first_line -= 1 - if first_line < next_block_start: - # Didn't find any "template" keyword before reaching the next block, - # there are probably no template things to check for this block - block_index -= 1 - continue - - # Look for typename in the specified range - for i in xrange(first_line, last_line + 1, 1): - if Search(typename_pattern, clean_lines.elided[i]): - return True - block_index -= 1 - - return False - - -def CheckBracesSpacing(filename, clean_lines, linenum, nesting_state, error): - """Checks for horizontal spacing near commas. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - nesting_state: A NestingState instance which maintains information about - the current stack of nested blocks being parsed. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - # Except after an opening paren, or after another opening brace (in case of - # an initializer list, for instance), you should have spaces before your - # braces when they are delimiting blocks, classes, namespaces etc. - # And since you should never have braces at the beginning of a line, - # this is an easy test. Except that braces used for initialization don't - # follow the same rule; we often don't want spaces before those. - match = Match(r'^(.*[^ ({>]){', line) - - if match: - # Try a bit harder to check for brace initialization. This - # happens in one of the following forms: - # Constructor() : initializer_list_{} { ... } - # Constructor{}.MemberFunction() - # Type variable{}; - # FunctionCall(type{}, ...); - # LastArgument(..., type{}); - # LOG(INFO) << type{} << " ..."; - # map_of_type[{...}] = ...; - # ternary = expr ? new type{} : nullptr; - # OuterTemplate{}> - # - # We check for the character following the closing brace, and - # silence the warning if it's one of those listed above, i.e. - # "{.;,)<>]:". - # - # To account for nested initializer list, we allow any number of - # closing braces up to "{;,)<". We can't simply silence the - # warning on first sight of closing brace, because that would - # cause false negatives for things that are not initializer lists. - # Silence this: But not this: - # Outer{ if (...) { - # Inner{...} if (...){ // Missing space before { - # }; } - # - # There is a false negative with this approach if people inserted - # spurious semicolons, e.g. "if (cond){};", but we will catch the - # spurious semicolon with a separate check. - leading_text = match.group(1) - (endline, endlinenum, endpos) = CloseExpression( - clean_lines, linenum, len(match.group(1))) - trailing_text = '' - if endpos > -1: - trailing_text = endline[endpos:] - for offset in xrange(endlinenum + 1, - min(endlinenum + 3, clean_lines.NumLines() - 1)): - trailing_text += clean_lines.elided[offset] - # We also suppress warnings for `uint64_t{expression}` etc., as the style - # guide recommends brace initialization for integral types to avoid - # overflow/truncation. - if (not Match(r'^[\s}]*[{.;,)<>\]:]', trailing_text) - and not _IsType(clean_lines, nesting_state, leading_text)): - error(filename, linenum, 'whitespace/braces', 5, - 'Missing space before {') - - # Make sure '} else {' has spaces. - if Search(r'}else', line): - error(filename, linenum, 'whitespace/braces', 5, - 'Missing space before else') - - # You shouldn't have a space before a semicolon at the end of the line. - # There's a special case for "for" since the style guide allows space before - # the semicolon there. - if Search(r':\s*;\s*$', line): - error(filename, linenum, 'whitespace/semicolon', 5, - 'Semicolon defining empty statement. Use {} instead.') - elif Search(r'^\s*;\s*$', line): - error(filename, linenum, 'whitespace/semicolon', 5, - 'Line contains only semicolon. If this should be an empty statement, ' - 'use {} instead.') - elif (Search(r'\s+;\s*$', line) and - not Search(r'\bfor\b', line)): - error(filename, linenum, 'whitespace/semicolon', 5, - 'Extra space before last semicolon. If this should be an empty ' - 'statement, use {} instead.') - - -def IsDecltype(clean_lines, linenum, column): - """Check if the token ending on (linenum, column) is decltype(). - - Args: - clean_lines: A CleansedLines instance containing the file. - linenum: the number of the line to check. - column: end column of the token to check. - Returns: - True if this token is decltype() expression, False otherwise. - """ - (text, _, start_col) = ReverseCloseExpression(clean_lines, linenum, column) - if start_col < 0: - return False - if Search(r'\bdecltype\s*$', text[0:start_col]): - return True - return False - -def CheckSectionSpacing(filename, clean_lines, class_info, linenum, error): - """Checks for additional blank line issues related to sections. - - Currently the only thing checked here is blank line before protected/private. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - class_info: A _ClassInfo objects. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - # Skip checks if the class is small, where small means 25 lines or less. - # 25 lines seems like a good cutoff since that's the usual height of - # terminals, and any class that can't fit in one screen can't really - # be considered "small". - # - # Also skip checks if we are on the first line. This accounts for - # classes that look like - # class Foo { public: ... }; - # - # If we didn't find the end of the class, last_line would be zero, - # and the check will be skipped by the first condition. - if (class_info.last_line - class_info.starting_linenum <= 24 or - linenum <= class_info.starting_linenum): - return - - matched = Match(r'\s*(public|protected|private):', clean_lines.lines[linenum]) - if matched: - # Issue warning if the line before public/protected/private was - # not a blank line, but don't do this if the previous line contains - # "class" or "struct". This can happen two ways: - # - We are at the beginning of the class. - # - We are forward-declaring an inner class that is semantically - # private, but needed to be public for implementation reasons. - # Also ignores cases where the previous line ends with a backslash as can be - # common when defining classes in C macros. - prev_line = clean_lines.lines[linenum - 1] - if (not IsBlankLine(prev_line) and - not Search(r'\b(class|struct)\b', prev_line) and - not Search(r'\\$', prev_line)): - # Try a bit harder to find the beginning of the class. This is to - # account for multi-line base-specifier lists, e.g.: - # class Derived - # : public Base { - end_class_head = class_info.starting_linenum - for i in range(class_info.starting_linenum, linenum): - if Search(r'\{\s*$', clean_lines.lines[i]): - end_class_head = i - break - if end_class_head < linenum - 1: - error(filename, linenum, 'whitespace/blank_line', 3, - '"%s:" should be preceded by a blank line' % matched.group(1)) - - -def GetPreviousNonBlankLine(clean_lines, linenum): - """Return the most recent non-blank line and its line number. - - Args: - clean_lines: A CleansedLines instance containing the file contents. - linenum: The number of the line to check. - - Returns: - A tuple with two elements. The first element is the contents of the last - non-blank line before the current line, or the empty string if this is the - first non-blank line. The second is the line number of that line, or -1 - if this is the first non-blank line. - """ - - prevlinenum = linenum - 1 - while prevlinenum >= 0: - prevline = clean_lines.elided[prevlinenum] - if not IsBlankLine(prevline): # if not a blank line... - return (prevline, prevlinenum) - prevlinenum -= 1 - return ('', -1) - - -def CheckBraces(filename, clean_lines, linenum, error): - """Looks for misplaced braces (e.g. at the end of line). - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - - line = clean_lines.elided[linenum] # get rid of comments and strings - - if Match(r'\s*{\s*$', line): - # We allow an open brace to start a line in the case where someone is using - # braces in a block to explicitly create a new scope, which is commonly used - # to control the lifetime of stack-allocated variables. Braces are also - # used for brace initializers inside function calls. We don't detect this - # perfectly: we just don't complain if the last non-whitespace character on - # the previous non-blank line is ',', ';', ':', '(', '{', or '}', or if the - # previous line starts a preprocessor block. We also allow a brace on the - # following line if it is part of an array initialization and would not fit - # within the 80 character limit of the preceding line. - prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] - if (not Search(r'[,;:}{(]\s*$', prevline) and - not Match(r'\s*#', prevline) and - not (GetLineWidth(prevline) > _line_length - 2 and '[]' in prevline)): - error(filename, linenum, 'whitespace/braces', 4, - '{ should almost always be at the end of the previous line') - - # An else clause should be on the same line as the preceding closing brace. - if Match(r'\s*else\b\s*(?:if\b|\{|$)', line): - prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] - if Match(r'\s*}\s*$', prevline): - error(filename, linenum, 'whitespace/newline', 4, - 'An else should appear on the same line as the preceding }') - - # If braces come on one side of an else, they should be on both. - # However, we have to worry about "else if" that spans multiple lines! - if Search(r'else if\s*\(', line): # could be multi-line if - brace_on_left = bool(Search(r'}\s*else if\s*\(', line)) - # find the ( after the if - pos = line.find('else if') - pos = line.find('(', pos) - if pos > 0: - (endline, _, endpos) = CloseExpression(clean_lines, linenum, pos) - brace_on_right = endline[endpos:].find('{') != -1 - if brace_on_left != brace_on_right: # must be brace after if - error(filename, linenum, 'readability/braces', 5, - 'If an else has a brace on one side, it should have it on both') - elif Search(r'}\s*else[^{]*$', line) or Match(r'[^}]*else\s*{', line): - error(filename, linenum, 'readability/braces', 5, - 'If an else has a brace on one side, it should have it on both') - - # Likewise, an else should never have the else clause on the same line - if Search(r'\belse [^\s{]', line) and not Search(r'\belse if\b', line): - error(filename, linenum, 'whitespace/newline', 4, - 'Else clause should never be on same line as else (use 2 lines)') - - # In the same way, a do/while should never be on one line - if Match(r'\s*do [^\s{]', line): - error(filename, linenum, 'whitespace/newline', 4, - 'do/while clauses should not be on a single line') - - # Check single-line if/else bodies. The style guide says 'curly braces are not - # required for single-line statements'. We additionally allow multi-line, - # single statements, but we reject anything with more than one semicolon in - # it. This means that the first semicolon after the if should be at the end of - # its line, and the line after that should have an indent level equal to or - # lower than the if. We also check for ambiguous if/else nesting without - # braces. - if_else_match = Search(r'\b(if\s*(|constexpr)\s*\(|else\b)', line) - if if_else_match and not Match(r'\s*#', line): - if_indent = GetIndentLevel(line) - endline, endlinenum, endpos = line, linenum, if_else_match.end() - if_match = Search(r'\bif\s*(|constexpr)\s*\(', line) - if if_match: - # This could be a multiline if condition, so find the end first. - pos = if_match.end() - 1 - (endline, endlinenum, endpos) = CloseExpression(clean_lines, linenum, pos) - # Check for an opening brace, either directly after the if or on the next - # line. If found, this isn't a single-statement conditional. - if (not Match(r'\s*{', endline[endpos:]) - and not (Match(r'\s*$', endline[endpos:]) - and endlinenum < (len(clean_lines.elided) - 1) - and Match(r'\s*{', clean_lines.elided[endlinenum + 1]))): - while (endlinenum < len(clean_lines.elided) - and ';' not in clean_lines.elided[endlinenum][endpos:]): - endlinenum += 1 - endpos = 0 - if endlinenum < len(clean_lines.elided): - endline = clean_lines.elided[endlinenum] - # We allow a mix of whitespace and closing braces (e.g. for one-liner - # methods) and a single \ after the semicolon (for macros) - endpos = endline.find(';') - if not Match(r';[\s}]*(\\?)$', endline[endpos:]): - # Semicolon isn't the last character, there's something trailing. - # Output a warning if the semicolon is not contained inside - # a lambda expression. - if not Match(r'^[^{};]*\[[^\[\]]*\][^{}]*\{[^{}]*\}\s*\)*[;,]\s*$', - endline): - error(filename, linenum, 'readability/braces', 4, - 'If/else bodies with multiple statements require braces') - elif endlinenum < len(clean_lines.elided) - 1: - # Make sure the next line is dedented - next_line = clean_lines.elided[endlinenum + 1] - next_indent = GetIndentLevel(next_line) - # With ambiguous nested if statements, this will error out on the - # if that *doesn't* match the else, regardless of whether it's the - # inner one or outer one. - if (if_match and Match(r'\s*else\b', next_line) - and next_indent != if_indent): - error(filename, linenum, 'readability/braces', 4, - 'Else clause should be indented at the same level as if. ' - 'Ambiguous nested if/else chains require braces.') - elif next_indent > if_indent: - error(filename, linenum, 'readability/braces', 4, - 'If/else bodies with multiple statements require braces') - - -def CheckTrailingSemicolon(filename, clean_lines, linenum, error): - """Looks for redundant trailing semicolon. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - - line = clean_lines.elided[linenum] - - # Block bodies should not be followed by a semicolon. Due to C++11 - # brace initialization, there are more places where semicolons are - # required than not, so we explicitly list the allowed rules rather - # than listing the disallowed ones. These are the places where "};" - # should be replaced by just "}": - # 1. Some flavor of block following closing parenthesis: - # for (;;) {}; - # while (...) {}; - # switch (...) {}; - # Function(...) {}; - # if (...) {}; - # if (...) else if (...) {}; - # - # 2. else block: - # if (...) else {}; - # - # 3. const member function: - # Function(...) const {}; - # - # 4. Block following some statement: - # x = 42; - # {}; - # - # 5. Block at the beginning of a function: - # Function(...) { - # {}; - # } - # - # Note that naively checking for the preceding "{" will also match - # braces inside multi-dimensional arrays, but this is fine since - # that expression will not contain semicolons. - # - # 6. Block following another block: - # while (true) {} - # {}; - # - # 7. End of namespaces: - # namespace {}; - # - # These semicolons seems far more common than other kinds of - # redundant semicolons, possibly due to people converting classes - # to namespaces. For now we do not warn for this case. - # - # Try matching case 1 first. - match = Match(r'^(.*\)\s*)\{', line) - if match: - # Matched closing parenthesis (case 1). Check the token before the - # matching opening parenthesis, and don't warn if it looks like a - # macro. This avoids these false positives: - # - macro that defines a base class - # - multi-line macro that defines a base class - # - macro that defines the whole class-head - # - # But we still issue warnings for macros that we know are safe to - # warn, specifically: - # - TEST, TEST_F, TEST_P, MATCHER, MATCHER_P - # - TYPED_TEST - # - INTERFACE_DEF - # - EXCLUSIVE_LOCKS_REQUIRED, SHARED_LOCKS_REQUIRED, LOCKS_EXCLUDED: - # - # We implement a list of safe macros instead of a list of - # unsafe macros, even though the latter appears less frequently in - # google code and would have been easier to implement. This is because - # the downside for getting the allowed checks wrong means some extra - # semicolons, while the downside for getting disallowed checks wrong - # would result in compile errors. - # - # In addition to macros, we also don't want to warn on - # - Compound literals - # - Lambdas - # - alignas specifier with anonymous structs - # - decltype - closing_brace_pos = match.group(1).rfind(')') - opening_parenthesis = ReverseCloseExpression( - clean_lines, linenum, closing_brace_pos) - if opening_parenthesis[2] > -1: - line_prefix = opening_parenthesis[0][0:opening_parenthesis[2]] - macro = Search(r'\b([A-Z_][A-Z0-9_]*)\s*$', line_prefix) - func = Match(r'^(.*\])\s*$', line_prefix) - if ((macro and - macro.group(1) not in ( - 'TEST', 'TEST_F', 'MATCHER', 'MATCHER_P', 'TYPED_TEST', - 'EXCLUSIVE_LOCKS_REQUIRED', 'SHARED_LOCKS_REQUIRED', - 'LOCKS_EXCLUDED', 'INTERFACE_DEF')) or - (func and not Search(r'\boperator\s*\[\s*\]', func.group(1))) or - Search(r'\b(?:struct|union)\s+alignas\s*$', line_prefix) or - Search(r'\bdecltype$', line_prefix) or - Search(r'\s+=\s*$', line_prefix)): - match = None - if (match and - opening_parenthesis[1] > 1 and - Search(r'\]\s*$', clean_lines.elided[opening_parenthesis[1] - 1])): - # Multi-line lambda-expression - match = None - - else: - # Try matching cases 2-3. - match = Match(r'^(.*(?:else|\)\s*const)\s*)\{', line) - if not match: - # Try matching cases 4-6. These are always matched on separate lines. - # - # Note that we can't simply concatenate the previous line to the - # current line and do a single match, otherwise we may output - # duplicate warnings for the blank line case: - # if (cond) { - # // blank line - # } - prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] - if prevline and Search(r'[;{}]\s*$', prevline): - match = Match(r'^(\s*)\{', line) - - # Check matching closing brace - if match: - (endline, endlinenum, endpos) = CloseExpression( - clean_lines, linenum, len(match.group(1))) - if endpos > -1 and Match(r'^\s*;', endline[endpos:]): - # Current {} pair is eligible for semicolon check, and we have found - # the redundant semicolon, output warning here. - # - # Note: because we are scanning forward for opening braces, and - # outputting warnings for the matching closing brace, if there are - # nested blocks with trailing semicolons, we will get the error - # messages in reversed order. - - # We need to check the line forward for NOLINT - raw_lines = clean_lines.raw_lines - ParseNolintSuppressions(filename, raw_lines[endlinenum-1], endlinenum-1, - error) - ParseNolintSuppressions(filename, raw_lines[endlinenum], endlinenum, - error) - - error(filename, endlinenum, 'readability/braces', 4, - "You don't need a ; after a }") - - -def CheckEmptyBlockBody(filename, clean_lines, linenum, error): - """Look for empty loop/conditional body with only a single semicolon. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - - # Search for loop keywords at the beginning of the line. Because only - # whitespaces are allowed before the keywords, this will also ignore most - # do-while-loops, since those lines should start with closing brace. - # - # We also check "if" blocks here, since an empty conditional block - # is likely an error. - line = clean_lines.elided[linenum] - matched = Match(r'\s*(for|while|if)\s*\(', line) - if matched: - # Find the end of the conditional expression. - (end_line, end_linenum, end_pos) = CloseExpression( - clean_lines, linenum, line.find('(')) - - # Output warning if what follows the condition expression is a semicolon. - # No warning for all other cases, including whitespace or newline, since we - # have a separate check for semicolons preceded by whitespace. - if end_pos >= 0 and Match(r';', end_line[end_pos:]): - if matched.group(1) == 'if': - error(filename, end_linenum, 'whitespace/empty_conditional_body', 5, - 'Empty conditional bodies should use {}') - else: - error(filename, end_linenum, 'whitespace/empty_loop_body', 5, - 'Empty loop bodies should use {} or continue') - - # Check for if statements that have completely empty bodies (no comments) - # and no else clauses. - if end_pos >= 0 and matched.group(1) == 'if': - # Find the position of the opening { for the if statement. - # Return without logging an error if it has no brackets. - opening_linenum = end_linenum - opening_line_fragment = end_line[end_pos:] - # Loop until EOF or find anything that's not whitespace or opening {. - while not Search(r'^\s*\{', opening_line_fragment): - if Search(r'^(?!\s*$)', opening_line_fragment): - # Conditional has no brackets. - return - opening_linenum += 1 - if opening_linenum == len(clean_lines.elided): - # Couldn't find conditional's opening { or any code before EOF. - return - opening_line_fragment = clean_lines.elided[opening_linenum] - # Set opening_line (opening_line_fragment may not be entire opening line). - opening_line = clean_lines.elided[opening_linenum] - - # Find the position of the closing }. - opening_pos = opening_line_fragment.find('{') - if opening_linenum == end_linenum: - # We need to make opening_pos relative to the start of the entire line. - opening_pos += end_pos - (closing_line, closing_linenum, closing_pos) = CloseExpression( - clean_lines, opening_linenum, opening_pos) - if closing_pos < 0: - return - - # Now construct the body of the conditional. This consists of the portion - # of the opening line after the {, all lines until the closing line, - # and the portion of the closing line before the }. - if (clean_lines.raw_lines[opening_linenum] != - CleanseComments(clean_lines.raw_lines[opening_linenum])): - # Opening line ends with a comment, so conditional isn't empty. - return - if closing_linenum > opening_linenum: - # Opening line after the {. Ignore comments here since we checked above. - bodylist = list(opening_line[opening_pos+1:]) - # All lines until closing line, excluding closing line, with comments. - bodylist.extend(clean_lines.raw_lines[opening_linenum+1:closing_linenum]) - # Closing line before the }. Won't (and can't) have comments. - bodylist.append(clean_lines.elided[closing_linenum][:closing_pos-1]) - body = '\n'.join(bodylist) - else: - # If statement has brackets and fits on a single line. - body = opening_line[opening_pos+1:closing_pos-1] - - # Check if the body is empty - if not _EMPTY_CONDITIONAL_BODY_PATTERN.search(body): - return - # The body is empty. Now make sure there's not an else clause. - current_linenum = closing_linenum - current_line_fragment = closing_line[closing_pos:] - # Loop until EOF or find anything that's not whitespace or else clause. - while Search(r'^\s*$|^(?=\s*else)', current_line_fragment): - if Search(r'^(?=\s*else)', current_line_fragment): - # Found an else clause, so don't log an error. - return - current_linenum += 1 - if current_linenum == len(clean_lines.elided): - break - current_line_fragment = clean_lines.elided[current_linenum] - - # The body is empty and there's no else clause until EOF or other code. - error(filename, end_linenum, 'whitespace/empty_if_body', 4, - ('If statement had no body and no else clause')) - - -def FindCheckMacro(line): - """Find a replaceable CHECK-like macro. - - Args: - line: line to search on. - Returns: - (macro name, start position), or (None, -1) if no replaceable - macro is found. - """ - for macro in _CHECK_MACROS: - i = line.find(macro) - if i >= 0: - # Find opening parenthesis. Do a regular expression match here - # to make sure that we are matching the expected CHECK macro, as - # opposed to some other macro that happens to contain the CHECK - # substring. - matched = Match(r'^(.*\b' + macro + r'\s*)\(', line) - if not matched: - continue - return (macro, len(matched.group(1))) - return (None, -1) - - -def CheckCheck(filename, clean_lines, linenum, error): - """Checks the use of CHECK and EXPECT macros. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - - # Decide the set of replacement macros that should be suggested - lines = clean_lines.elided - (check_macro, start_pos) = FindCheckMacro(lines[linenum]) - if not check_macro: - return - - # Find end of the boolean expression by matching parentheses - (last_line, end_line, end_pos) = CloseExpression( - clean_lines, linenum, start_pos) - if end_pos < 0: - return - - # If the check macro is followed by something other than a - # semicolon, assume users will log their own custom error messages - # and don't suggest any replacements. - if not Match(r'\s*;', last_line[end_pos:]): - return - - if linenum == end_line: - expression = lines[linenum][start_pos + 1:end_pos - 1] - else: - expression = lines[linenum][start_pos + 1:] - for i in xrange(linenum + 1, end_line): - expression += lines[i] - expression += last_line[0:end_pos - 1] - - # Parse expression so that we can take parentheses into account. - # This avoids false positives for inputs like "CHECK((a < 4) == b)", - # which is not replaceable by CHECK_LE. - lhs = '' - rhs = '' - operator = None - while expression: - matched = Match(r'^\s*(<<|<<=|>>|>>=|->\*|->|&&|\|\||' - r'==|!=|>=|>|<=|<|\()(.*)$', expression) - if matched: - token = matched.group(1) - if token == '(': - # Parenthesized operand - expression = matched.group(2) - (end, _) = FindEndOfExpressionInLine(expression, 0, ['(']) - if end < 0: - return # Unmatched parenthesis - lhs += '(' + expression[0:end] - expression = expression[end:] - elif token in ('&&', '||'): - # Logical and/or operators. This means the expression - # contains more than one term, for example: - # CHECK(42 < a && a < b); - # - # These are not replaceable with CHECK_LE, so bail out early. - return - elif token in ('<<', '<<=', '>>', '>>=', '->*', '->'): - # Non-relational operator - lhs += token - expression = matched.group(2) - else: - # Relational operator - operator = token - rhs = matched.group(2) - break - else: - # Unparenthesized operand. Instead of appending to lhs one character - # at a time, we do another regular expression match to consume several - # characters at once if possible. Trivial benchmark shows that this - # is more efficient when the operands are longer than a single - # character, which is generally the case. - matched = Match(r'^([^-=!<>()&|]+)(.*)$', expression) - if not matched: - matched = Match(r'^(\s*\S)(.*)$', expression) - if not matched: - break - lhs += matched.group(1) - expression = matched.group(2) - - # Only apply checks if we got all parts of the boolean expression - if not (lhs and operator and rhs): - return - - # Check that rhs do not contain logical operators. We already know - # that lhs is fine since the loop above parses out && and ||. - if rhs.find('&&') > -1 or rhs.find('||') > -1: - return - - # At least one of the operands must be a constant literal. This is - # to avoid suggesting replacements for unprintable things like - # CHECK(variable != iterator) - # - # The following pattern matches decimal, hex integers, strings, and - # characters (in that order). - lhs = lhs.strip() - rhs = rhs.strip() - match_constant = r'^([-+]?(\d+|0[xX][0-9a-fA-F]+)[lLuU]{0,3}|".*"|\'.*\')$' - if Match(match_constant, lhs) or Match(match_constant, rhs): - # Note: since we know both lhs and rhs, we can provide a more - # descriptive error message like: - # Consider using CHECK_EQ(x, 42) instead of CHECK(x == 42) - # Instead of: - # Consider using CHECK_EQ instead of CHECK(a == b) - # - # We are still keeping the less descriptive message because if lhs - # or rhs gets long, the error message might become unreadable. - error(filename, linenum, 'readability/check', 2, - 'Consider using %s instead of %s(a %s b)' % ( - _CHECK_REPLACEMENT[check_macro][operator], - check_macro, operator)) - - -def CheckAltTokens(filename, clean_lines, linenum, error): - """Check alternative keywords being used in boolean expressions. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - # Avoid preprocessor lines - if Match(r'^\s*#', line): - return - - # Last ditch effort to avoid multi-line comments. This will not help - # if the comment started before the current line or ended after the - # current line, but it catches most of the false positives. At least, - # it provides a way to workaround this warning for people who use - # multi-line comments in preprocessor macros. - # - # TODO(unknown): remove this once cpplint has better support for - # multi-line comments. - if line.find('/*') >= 0 or line.find('*/') >= 0: - return - - for match in _ALT_TOKEN_REPLACEMENT_PATTERN.finditer(line): - error(filename, linenum, 'readability/alt_tokens', 2, - 'Use operator %s instead of %s' % ( - _ALT_TOKEN_REPLACEMENT[match.group(1)], match.group(1))) - - -def GetLineWidth(line): - """Determines the width of the line in column positions. - - Args: - line: A string, which may be a Unicode string. - - Returns: - The width of the line in column positions, accounting for Unicode - combining characters and wide characters. - """ - if isinstance(line, unicode): - width = 0 - for uc in unicodedata.normalize('NFC', line): - if unicodedata.east_asian_width(uc) in ('W', 'F'): - width += 2 - elif not unicodedata.combining(uc): - # Issue 337 - # https://mail.python.org/pipermail/python-list/2012-August/628809.html - if (sys.version_info.major, sys.version_info.minor) <= (3, 2): - # https://github.com/python/cpython/blob/2.7/Include/unicodeobject.h#L81 - is_wide_build = sysconfig.get_config_var("Py_UNICODE_SIZE") >= 4 - # https://github.com/python/cpython/blob/2.7/Objects/unicodeobject.c#L564 - is_low_surrogate = 0xDC00 <= ord(uc) <= 0xDFFF - if not is_wide_build and is_low_surrogate: - width -= 1 - - width += 1 - return width - else: - return len(line) - - -def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state, - error): - """Checks rules from the 'C++ style rules' section of cppguide.html. - - Most of these rules are hard to test (naming, comment style), but we - do what we can. In particular we check for 2-space indents, line lengths, - tab usage, spaces inside code, etc. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - file_extension: The extension (without the dot) of the filename. - nesting_state: A NestingState instance which maintains information about - the current stack of nested blocks being parsed. - error: The function to call with any errors found. - """ - - # Don't use "elided" lines here, otherwise we can't check commented lines. - # Don't want to use "raw" either, because we don't want to check inside C++11 - # raw strings, - raw_lines = clean_lines.lines_without_raw_strings - line = raw_lines[linenum] - prev = raw_lines[linenum - 1] if linenum > 0 else '' - - if line.find('\t') != -1: - error(filename, linenum, 'whitespace/tab', 1, - 'Tab found; better to use spaces') - - # One or three blank spaces at the beginning of the line is weird; it's - # hard to reconcile that with 2-space indents. - # NOTE: here are the conditions rob pike used for his tests. Mine aren't - # as sophisticated, but it may be worth becoming so: RLENGTH==initial_spaces - # if(RLENGTH > 20) complain = 0; - # if(match($0, " +(error|private|public|protected):")) complain = 0; - # if(match(prev, "&& *$")) complain = 0; - # if(match(prev, "\\|\\| *$")) complain = 0; - # if(match(prev, "[\",=><] *$")) complain = 0; - # if(match($0, " <<")) complain = 0; - # if(match(prev, " +for \\(")) complain = 0; - # if(prevodd && match(prevprev, " +for \\(")) complain = 0; - scope_or_label_pattern = r'\s*(?:public|private|protected|signals)(?:\s+(?:slots\s*)?)?:\s*\\?$' - classinfo = nesting_state.InnermostClass() - initial_spaces = 0 - cleansed_line = clean_lines.elided[linenum] - while initial_spaces < len(line) and line[initial_spaces] == ' ': - initial_spaces += 1 - # There are certain situations we allow one space, notably for - # section labels, and also lines containing multi-line raw strings. - # We also don't check for lines that look like continuation lines - # (of lines ending in double quotes, commas, equals, or angle brackets) - # because the rules for how to indent those are non-trivial. - if (not Search(r'[",=><] *$', prev) and - (initial_spaces == 1 or initial_spaces == 3) and - not Match(scope_or_label_pattern, cleansed_line) and - not (clean_lines.raw_lines[linenum] != line and - Match(r'^\s*""', line))): - error(filename, linenum, 'whitespace/indent', 3, - 'Weird number of spaces at line-start. ' - 'Are you using a 2-space indent?') - - if line and line[-1].isspace(): - error(filename, linenum, 'whitespace/end_of_line', 4, - 'Line ends in whitespace. Consider deleting these extra spaces.') - - # Check if the line is a header guard. - is_header_guard = False - if IsHeaderExtension(file_extension): - cppvar = GetHeaderGuardCPPVariable(filename) - if (line.startswith('#ifndef %s' % cppvar) or - line.startswith('#define %s' % cppvar) or - line.startswith('#endif // %s' % cppvar)): - is_header_guard = True - # #include lines and header guards can be long, since there's no clean way to - # split them. - # - # URLs can be long too. It's possible to split these, but it makes them - # harder to cut&paste. - # - # The "$Id:...$" comment may also get very long without it being the - # developers fault. - # - # Doxygen documentation copying can get pretty long when using an overloaded - # function declaration - if (not line.startswith('#include') and not is_header_guard and - not Match(r'^\s*//.*http(s?)://\S*$', line) and - not Match(r'^\s*//\s*[^\s]*$', line) and - not Match(r'^// \$Id:.*#[0-9]+ \$$', line) and - not Match(r'^\s*/// [@\\](copydoc|copydetails|copybrief) .*$', line)): - line_width = GetLineWidth(line) - if line_width > _line_length: - error(filename, linenum, 'whitespace/line_length', 2, - 'Lines should be <= %i characters long' % _line_length) - - if (cleansed_line.count(';') > 1 and - # allow simple single line lambdas - not Match(r'^[^{};]*\[[^\[\]]*\][^{}]*\{[^{}\n\r]*\}', - line) and - # for loops are allowed two ;'s (and may run over two lines). - cleansed_line.find('for') == -1 and - (GetPreviousNonBlankLine(clean_lines, linenum)[0].find('for') == -1 or - GetPreviousNonBlankLine(clean_lines, linenum)[0].find(';') != -1) and - # It's ok to have many commands in a switch case that fits in 1 line - not ((cleansed_line.find('case ') != -1 or - cleansed_line.find('default:') != -1) and - cleansed_line.find('break;') != -1)): - error(filename, linenum, 'whitespace/newline', 0, - 'More than one command on the same line') - - # Some more style checks - CheckBraces(filename, clean_lines, linenum, error) - CheckTrailingSemicolon(filename, clean_lines, linenum, error) - CheckEmptyBlockBody(filename, clean_lines, linenum, error) - CheckSpacing(filename, clean_lines, linenum, nesting_state, error) - CheckOperatorSpacing(filename, clean_lines, linenum, error) - CheckParenthesisSpacing(filename, clean_lines, linenum, error) - CheckCommaSpacing(filename, clean_lines, linenum, error) - CheckBracesSpacing(filename, clean_lines, linenum, nesting_state, error) - CheckSpacingForFunctionCall(filename, clean_lines, linenum, error) - CheckCheck(filename, clean_lines, linenum, error) - CheckAltTokens(filename, clean_lines, linenum, error) - classinfo = nesting_state.InnermostClass() - if classinfo: - CheckSectionSpacing(filename, clean_lines, classinfo, linenum, error) - - -_RE_PATTERN_INCLUDE = re.compile(r'^\s*#\s*(?:include|import)\s*([<"])([^>"]*)[>"].*$') -# Matches the first component of a filename delimited by -s and _s. That is: -# _RE_FIRST_COMPONENT.match('foo').group(0) == 'foo' -# _RE_FIRST_COMPONENT.match('foo.cc').group(0) == 'foo' -# _RE_FIRST_COMPONENT.match('foo-bar_baz.cc').group(0) == 'foo' -# _RE_FIRST_COMPONENT.match('foo_bar-baz.cc').group(0) == 'foo' -_RE_FIRST_COMPONENT = re.compile(r'^[^-_.]+') - - -def _DropCommonSuffixes(filename): - """Drops common suffixes like _test.cc or -inl.h from filename. - - For example: - >>> _DropCommonSuffixes('foo/foo-inl.h') - 'foo/foo' - >>> _DropCommonSuffixes('foo/bar/foo.cc') - 'foo/bar/foo' - >>> _DropCommonSuffixes('foo/foo_internal.h') - 'foo/foo' - >>> _DropCommonSuffixes('foo/foo_unusualinternal.h') - 'foo/foo_unusualinternal' - - Args: - filename: The input filename. - - Returns: - The filename with the common suffix removed. - """ - for suffix in itertools.chain( - ('%s.%s' % (test_suffix.lstrip('_'), ext) - for test_suffix, ext in itertools.product(_test_suffixes, GetNonHeaderExtensions())), - ('%s.%s' % (suffix, ext) - for suffix, ext in itertools.product(['inl', 'imp', 'internal'], GetHeaderExtensions()))): - if (filename.endswith(suffix) and len(filename) > len(suffix) and - filename[-len(suffix) - 1] in ('-', '_')): - return filename[:-len(suffix) - 1] - - for suffix in ['Tests.h', 'Test.m', 'Test.mm', 'Tests.m', 'Tests.mm']: - if (filename.endswith(suffix) and len(filename) > len(suffix)): - return filename[:-len(suffix)] - - return os.path.splitext(filename)[0] - - -def _ClassifyInclude(fileinfo, include, used_angle_brackets, include_order="default"): - """Figures out what kind of header 'include' is. - - Args: - fileinfo: The current file cpplint is running over. A FileInfo instance. - include: The path to a #included file. - used_angle_brackets: True if the #include used <> rather than "". - include_order: "default" or other value allowed in program arguments - - Returns: - One of the _XXX_HEADER constants. - - For example: - >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'stdio.h', True) - _C_SYS_HEADER - >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'string', True) - _CPP_SYS_HEADER - >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/foo.h', True, "standardcfirst") - _OTHER_SYS_HEADER - >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/foo.h', False) - _LIKELY_MY_HEADER - >>> _ClassifyInclude(FileInfo('foo/foo_unknown_extension.cc'), - ... 'bar/foo_other_ext.h', False) - _POSSIBLE_MY_HEADER - >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/bar.h', False) - _OTHER_HEADER - """ - # This is a list of all standard c++ header files, except - # those already checked for above. - is_cpp_header = include in _CPP_HEADERS - - # Mark include as C header if in list or in a known folder for standard-ish C headers. - is_std_c_header = (include_order == "default") or (include in _C_HEADERS - # additional linux glibc header folders - or Search(r'(?:%s)\/.*\.h' % "|".join(C_STANDARD_HEADER_FOLDERS), include)) - - # Headers with C++ extensions shouldn't be considered C system headers - is_system = used_angle_brackets and not os.path.splitext(include)[1] in ['.hpp', '.hxx', '.h++'] - - if is_system: - if is_cpp_header: - return _CPP_SYS_HEADER - if is_std_c_header: - return _C_SYS_HEADER - else: - return _OTHER_SYS_HEADER - - # If the target file and the include we're checking share a - # basename when we drop common extensions, and the include - # lives in . , then it's likely to be owned by the target file. - target_dir, target_base = ( - os.path.split(_DropCommonSuffixes(fileinfo.RepositoryName()))) - include_dir, include_base = os.path.split(_DropCommonSuffixes(include)) - target_dir_pub = os.path.normpath(target_dir + '/../public') - target_dir_pub = target_dir_pub.replace('\\', '/') - if target_base == include_base and ( - include_dir == target_dir or - include_dir == target_dir_pub): - return _LIKELY_MY_HEADER - - # If the target and include share some initial basename - # component, it's possible the target is implementing the - # include, so it's allowed to be first, but we'll never - # complain if it's not there. - target_first_component = _RE_FIRST_COMPONENT.match(target_base) - include_first_component = _RE_FIRST_COMPONENT.match(include_base) - if (target_first_component and include_first_component and - target_first_component.group(0) == - include_first_component.group(0)): - return _POSSIBLE_MY_HEADER - - return _OTHER_HEADER - - - -def CheckIncludeLine(filename, clean_lines, linenum, include_state, error): - """Check rules that are applicable to #include lines. - - Strings on #include lines are NOT removed from elided line, to make - certain tasks easier. However, to prevent false positives, checks - applicable to #include lines in CheckLanguage must be put here. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - include_state: An _IncludeState instance in which the headers are inserted. - error: The function to call with any errors found. - """ - fileinfo = FileInfo(filename) - line = clean_lines.lines[linenum] - - # system-style includes should not be used for project includes - match = Match(r'#include\s*<(([^/>]+)/[^>]+)', line) - if match: - if match.group(2) not in _C_SYSTEM_DIRECTORIES: - error(filename, linenum, 'build/include', 4, - '<%s> should be #include "%s" or #import <%s>' % - (match.group(1), match.group(1), match.group(1))) - - # framework-style imports should not be used for project imports - match = Match(r'#import\s*<(Firestore/Source/[^>]+)', line) - if match: - error(filename, linenum, 'build/include', 4, - 'Prefer #import "%s" for project import rather than #import <>' % - match.group(1)) - - # C++ system files should not be #imported - match = Match(r'#import\s*<([^/>.]+)>', line) - if match: - error(filename, linenum, 'build/include', 4, - 'C++ header <%s> was #imported. Should be #include <%s>' % - (match.group(1), match.group(1))) - - # Prefer C++ wrappers for C headers - match = Match(r'#include\s*<(([^>]+).h)>', line) - if match: - wrapper = 'c' + match.group(2) - if wrapper in _CPP_HEADERS: - error(filename, linenum, 'build/include', 4, - 'Prefer C++ header <%s> for C system header %s' % - (wrapper, match.group(1))) - - # "include" should use the new style "foo/bar.h" instead of just "bar.h" - # Only do this check if the included header follows google naming - # conventions. If not, assume that it's a 3rd party API that - # requires special include conventions. - # - # We also make an exception for Lua headers, which follow google - # naming convention but not the include convention. - match = Match(r'#include\s*"([^/]+\.h)"', line) - if match and not _THIRD_PARTY_HEADERS_PATTERN.match(match.group(1)): - error(filename, linenum, 'build/include_subdir', 4, - 'Include the directory when naming .h files') - - # we shouldn't include a file more than once. actually, there are a - # handful of instances where doing so is okay, but in general it's - # not. - match = _RE_PATTERN_INCLUDE.search(line) - if match: - include = match.group(2) - used_angle_brackets = (match.group(1) == '<') - duplicate_line = include_state.FindHeader(include) - if duplicate_line >= 0: - error(filename, linenum, 'build/include', 4, - '"%s" already included at %s:%s' % - (include, filename, duplicate_line)) - return - - for extension in GetNonHeaderExtensions(): - if (include.endswith('.' + extension) and - os.path.dirname(fileinfo.RepositoryName()) != os.path.dirname(include)): - error(filename, linenum, 'build/include', 4, - 'Do not include .' + extension + ' files from other packages') - return - - # We DO want to include a 3rd party looking header if it matches the - # filename. Otherwise we get an erroneous error "...should include its - # header" error later. - third_src_header = False - for ext in GetHeaderExtensions(): - basefilename = filename[0:len(filename) - len(fileinfo.Extension())] - headerfile = basefilename + '.' + ext - headername = FileInfo(headerfile).RepositoryName() - if headername in include or include in headername: - third_src_header = True - break - - if third_src_header or not _THIRD_PARTY_HEADERS_PATTERN.match(include): - include_state.include_list[-1].append((include, linenum)) - - # We want to ensure that headers appear in the right order: - # 1) for foo.cc, foo.h (preferred location) - # 2) c system files - # 3) cpp system files - # 4) for foo.cc, foo.h (deprecated location) - # 5) other google headers - # - # We classify each include statement as one of those 5 types - # using a number of techniques. The include_state object keeps - # track of the highest type seen, and complains if we see a - # lower type after that. - error_message = include_state.CheckNextIncludeOrder( - _ClassifyInclude(fileinfo, include, used_angle_brackets, _include_order)) - if error_message: - error(filename, linenum, 'build/include_order', 4, - '%s. Should be: %s.h, c system, c++ system, other.' % - (error_message, fileinfo.BaseName())) - canonical_include = include_state.CanonicalizeAlphabeticalOrder(include) - if not include_state.IsInAlphabeticalOrder( - clean_lines, linenum, canonical_include): - error(filename, linenum, 'build/include_alpha', 4, - 'Include "%s" not in alphabetical order' % include) - include_state.SetLastHeader(canonical_include) - - - -def _GetTextInside(text, start_pattern): - r"""Retrieves all the text between matching open and close parentheses. - - Given a string of lines and a regular expression string, retrieve all the text - following the expression and between opening punctuation symbols like - (, [, or {, and the matching close-punctuation symbol. This properly nested - occurrences of the punctuations, so for the text like - printf(a(), b(c())); - a call to _GetTextInside(text, r'printf\(') will return 'a(), b(c())'. - start_pattern must match string having an open punctuation symbol at the end. - - Args: - text: The lines to extract text. Its comments and strings must be elided. - It can be single line and can span multiple lines. - start_pattern: The regexp string indicating where to start extracting - the text. - Returns: - The extracted text. - None if either the opening string or ending punctuation could not be found. - """ - # TODO(unknown): Audit cpplint.py to see what places could be profitably - # rewritten to use _GetTextInside (and use inferior regexp matching today). - - # Give opening punctuations to get the matching close-punctuations. - matching_punctuation = {'(': ')', '{': '}', '[': ']'} - closing_punctuation = set(itervalues(matching_punctuation)) - - # Find the position to start extracting text. - match = re.search(start_pattern, text, re.M) - if not match: # start_pattern not found in text. - return None - start_position = match.end(0) - - assert start_position > 0, ( - 'start_pattern must ends with an opening punctuation.') - assert text[start_position - 1] in matching_punctuation, ( - 'start_pattern must ends with an opening punctuation.') - # Stack of closing punctuations we expect to have in text after position. - punctuation_stack = [matching_punctuation[text[start_position - 1]]] - position = start_position - while punctuation_stack and position < len(text): - if text[position] == punctuation_stack[-1]: - punctuation_stack.pop() - elif text[position] in closing_punctuation: - # A closing punctuation without matching opening punctuations. - return None - elif text[position] in matching_punctuation: - punctuation_stack.append(matching_punctuation[text[position]]) - position += 1 - if punctuation_stack: - # Opening punctuations left without matching close-punctuations. - return None - # punctuations match. - return text[start_position:position - 1] - - -# Patterns for matching call-by-reference parameters. -# -# Supports nested templates up to 2 levels deep using this messy pattern: -# < (?: < (?: < [^<>]* -# > -# | [^<>] )* -# > -# | [^<>] )* -# > -_RE_PATTERN_IDENT = r'[_a-zA-Z]\w*' # =~ [[:alpha:]][[:alnum:]]* -_RE_PATTERN_TYPE = ( - r'(?:const\s+)?(?:typename\s+|class\s+|struct\s+|union\s+|enum\s+)?' - r'(?:\w|' - r'\s*<(?:<(?:<[^<>]*>|[^<>])*>|[^<>])*>|' - r'::)+') -# A call-by-reference parameter ends with '& identifier'. -_RE_PATTERN_REF_PARAM = re.compile( - r'(' + _RE_PATTERN_TYPE + r'(?:\s*(?:\bconst\b|[*]))*\s*' - r'&\s*' + _RE_PATTERN_IDENT + r')\s*(?:=[^,()]+)?[,)]') -# A call-by-const-reference parameter either ends with 'const& identifier' -# or looks like 'const type& identifier' when 'type' is atomic. -_RE_PATTERN_CONST_REF_PARAM = ( - r'(?:.*\s*\bconst\s*&\s*' + _RE_PATTERN_IDENT + - r'|const\s+' + _RE_PATTERN_TYPE + r'\s*&\s*' + _RE_PATTERN_IDENT + r')') -# Stream types. -_RE_PATTERN_REF_STREAM_PARAM = ( - r'(?:.*stream\s*&\s*' + _RE_PATTERN_IDENT + r')') - - -def CheckLanguage(filename, clean_lines, linenum, file_extension, - include_state, nesting_state, error): - """Checks rules from the 'C++ language rules' section of cppguide.html. - - Some of these rules are hard to test (function overloading, using - uint32 inappropriately), but we do the best we can. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - file_extension: The extension (without the dot) of the filename. - include_state: An _IncludeState instance in which the headers are inserted. - nesting_state: A NestingState instance which maintains information about - the current stack of nested blocks being parsed. - error: The function to call with any errors found. - """ - # If the line is empty or consists of entirely a comment, no need to - # check it. - line = clean_lines.elided[linenum] - if not line: - return - - match = _RE_PATTERN_INCLUDE.search(line) - if match: - CheckIncludeLine(filename, clean_lines, linenum, include_state, error) - return - - # Reset include state across preprocessor directives. This is meant - # to silence warnings for conditional includes. - match = Match(r'^\s*#\s*(if|ifdef|ifndef|elif|else|endif)\b', line) - if match: - include_state.ResetSection(match.group(1)) - - - # Perform other checks now that we are sure that this is not an include line - CheckCasts(filename, clean_lines, linenum, error) - CheckGlobalStatic(filename, clean_lines, linenum, error) - CheckPrintf(filename, clean_lines, linenum, error) - - if IsHeaderExtension(file_extension): - # TODO(unknown): check that 1-arg constructors are explicit. - # How to tell it's a constructor? - # (handled in CheckForNonStandardConstructs for now) - # TODO(unknown): check that classes declare or disable copy/assign - # (level 1 error) - pass - - # Check if people are using the verboten C basic types. The only exception - # we regularly allow is "unsigned short port" for port. - if Search(r'\bshort port\b', line): - if not Search(r'\bunsigned short port\b', line): - error(filename, linenum, 'runtime/int', 4, - 'Use "unsigned short" for ports, not "short"') - else: - match = Search(r'\b(short|long(?! +double)|long long)\b', line) - if match: - error(filename, linenum, 'runtime/int', 4, - 'Use int16/int64/etc, rather than the C type %s' % match.group(1)) - - # Check if some verboten operator overloading is going on - # TODO(unknown): catch out-of-line unary operator&: - # class X {}; - # int operator&(const X& x) { return 42; } // unary operator& - # The trick is it's hard to tell apart from binary operator&: - # class Y { int operator&(const Y& x) { return 23; } }; // binary operator& - if Search(r'\boperator\s*&\s*\(\s*\)', line): - error(filename, linenum, 'runtime/operator', 4, - 'Unary operator& is dangerous. Do not use it.') - - # Check for suspicious usage of "if" like - # } if (a == b) { - if Search(r'\}\s*if\s*\(', line): - error(filename, linenum, 'readability/braces', 4, - 'Did you mean "else if"? If not, start a new line for "if".') - - # Check for potential format string bugs like printf(foo). - # We constrain the pattern not to pick things like DocidForPrintf(foo). - # Not perfect but it can catch printf(foo.c_str()) and printf(foo->c_str()) - # TODO(unknown): Catch the following case. Need to change the calling - # convention of the whole function to process multiple line to handle it. - # printf( - # boy_this_is_a_really_long_variable_that_cannot_fit_on_the_prev_line); - printf_args = _GetTextInside(line, r'(?i)\b(string)?printf\s*\(') - if printf_args: - match = Match(r'([\w.\->()]+)$', printf_args) - if match and match.group(1) != '__VA_ARGS__': - function_name = re.search(r'\b((?:string)?printf)\s*\(', - line, re.I).group(1) - error(filename, linenum, 'runtime/printf', 4, - 'Potential format string bug. Do %s("%%s", %s) instead.' - % (function_name, match.group(1))) - - # Check for potential memset bugs like memset(buf, sizeof(buf), 0). - match = Search(r'memset\s*\(([^,]*),\s*([^,]*),\s*0\s*\)', line) - if match and not Match(r"^''|-?[0-9]+|0x[0-9A-Fa-f]$", match.group(2)): - error(filename, linenum, 'runtime/memset', 4, - 'Did you mean "memset(%s, 0, %s)"?' - % (match.group(1), match.group(2))) - - if Search(r'\busing namespace\b', line): - if Search(r'\bliterals\b', line): - error(filename, linenum, 'build/namespaces_literals', 5, - 'Do not use namespace using-directives. ' - 'Use using-declarations instead.') - else: - error(filename, linenum, 'build/namespaces', 5, - 'Do not use namespace using-directives. ' - 'Use using-declarations instead.') - - # Detect variable-length arrays. - match = Match(r'\s*(.+::)?(\w+) [a-z]\w*\[(.+)];', line) - if (match and match.group(2) != 'return' and match.group(2) != 'delete' and - match.group(3).find(']') == -1): - # Split the size using space and arithmetic operators as delimiters. - # If any of the resulting tokens are not compile time constants then - # report the error. - tokens = re.split(r'\s|\+|\-|\*|\/|<<|>>]', match.group(3)) - is_const = True - skip_next = False - for tok in tokens: - if skip_next: - skip_next = False - continue - - if Search(r'sizeof\(.+\)', tok): continue - if Search(r'arraysize\(\w+\)', tok): continue - - tok = tok.lstrip('(') - tok = tok.rstrip(')') - if not tok: continue - if Match(r'\d+', tok): continue - if Match(r'0[xX][0-9a-fA-F]+', tok): continue - if Match(r'k[A-Z0-9]\w*', tok): continue - if Match(r'(.+::)?k[A-Z0-9]\w*', tok): continue - if Match(r'(.+::)?[A-Z][A-Z0-9_]*', tok): continue - # A catch all for tricky sizeof cases, including 'sizeof expression', - # 'sizeof(*type)', 'sizeof(const type)', 'sizeof(struct StructName)' - # requires skipping the next token because we split on ' ' and '*'. - if tok.startswith('sizeof'): - skip_next = True - continue - is_const = False - break - if not is_const: - error(filename, linenum, 'runtime/arrays', 1, - 'Do not use variable-length arrays. Use an appropriately named ' - "('k' followed by CamelCase) compile-time constant for the size.") - - # Check for use of unnamed namespaces in header files. Registration - # macros are typically OK, so we allow use of "namespace {" on lines - # that end with backslashes. - if (IsHeaderExtension(file_extension) - and Search(r'\bnamespace\s*{', line) - and line[-1] != '\\'): - error(filename, linenum, 'build/namespaces_headers', 4, - 'Do not use unnamed namespaces in header files. See ' - 'https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Namespaces' - ' for more information.') - - -def CheckGlobalStatic(filename, clean_lines, linenum, error): - """Check for unsafe global or static objects. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - # Match two lines at a time to support multiline declarations - if linenum + 1 < clean_lines.NumLines() and not Search(r'[;({]', line): - line += clean_lines.elided[linenum + 1].strip() - - # Check for people declaring static/global STL strings at the top level. - # This is dangerous because the C++ language does not guarantee that - # globals with constructors are initialized before the first access, and - # also because globals can be destroyed when some threads are still running. - # TODO(unknown): Generalize this to also find static unique_ptr instances. - # TODO(unknown): File bugs for clang-tidy to find these. - match = Match( - r'((?:|static +)(?:|const +))(?::*std::)?string( +const)? +' - r'([a-zA-Z0-9_:]+)\b(.*)', - line) - - # Remove false positives: - # - String pointers (as opposed to values). - # string *pointer - # const string *pointer - # string const *pointer - # string *const pointer - # - # - Functions and template specializations. - # string Function(... - # string Class::Method(... - # - # - Operators. These are matched separately because operator names - # cross non-word boundaries, and trying to match both operators - # and functions at the same time would decrease accuracy of - # matching identifiers. - # string Class::operator*() - if (match and - not Search(r'\bstring\b(\s+const)?\s*[\*\&]\s*(const\s+)?\w', line) and - not Search(r'\boperator\W', line) and - not Match(r'\s*(<.*>)?(::[a-zA-Z0-9_]+)*\s*\(([^"]|$)', match.group(4))): - if Search(r'\bconst\b', line): - error(filename, linenum, 'runtime/string', 4, - 'For a static/global string constant, use a C style string ' - 'instead: "%schar%s %s[]".' % - (match.group(1), match.group(2) or '', match.group(3))) - else: - error(filename, linenum, 'runtime/string', 4, - 'Static/global string variables are not permitted.') - - if (Search(r'\b([A-Za-z0-9_]*_)\(\1\)', line) or - Search(r'\b([A-Za-z0-9_]*_)\(CHECK_NOTNULL\(\1\)\)', line)): - error(filename, linenum, 'runtime/init', 4, - 'You seem to be initializing a member variable with itself.') - - -def CheckPrintf(filename, clean_lines, linenum, error): - """Check for printf related issues. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - # When snprintf is used, the second argument shouldn't be a literal. - match = Search(r'snprintf\s*\(([^,]*),\s*([0-9]*)\s*,', line) - if match and match.group(2) != '0': - # If 2nd arg is zero, snprintf is used to calculate size. - error(filename, linenum, 'runtime/printf', 3, - 'If you can, use sizeof(%s) instead of %s as the 2nd arg ' - 'to snprintf.' % (match.group(1), match.group(2))) - - # Check if some verboten C functions are being used. - if Search(r'\bsprintf\s*\(', line): - error(filename, linenum, 'runtime/printf', 5, - 'Never use sprintf. Use snprintf instead.') - match = Search(r'\b(strcpy|strcat)\s*\(', line) - if match: - error(filename, linenum, 'runtime/printf', 4, - 'Almost always, snprintf is better than %s' % match.group(1)) - - -def IsDerivedFunction(clean_lines, linenum): - """Check if current line contains an inherited function. - - Args: - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - Returns: - True if current line contains a function with "override" - virt-specifier. - """ - # Scan back a few lines for start of current function - for i in xrange(linenum, max(-1, linenum - 10), -1): - match = Match(r'^([^()]*\w+)\(', clean_lines.elided[i]) - if match: - # Look for "override" after the matching closing parenthesis - line, _, closing_paren = CloseExpression( - clean_lines, i, len(match.group(1))) - return (closing_paren >= 0 and - Search(r'\boverride\b', line[closing_paren:])) - return False - - -def IsOutOfLineMethodDefinition(clean_lines, linenum): - """Check if current line contains an out-of-line method definition. - - Args: - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - Returns: - True if current line contains an out-of-line method definition. - """ - # Scan back a few lines for start of current function - for i in xrange(linenum, max(-1, linenum - 10), -1): - if Match(r'^([^()]*\w+)\(', clean_lines.elided[i]): - return Match(r'^[^()]*\w+::\w+\(', clean_lines.elided[i]) is not None - return False - - -def IsInitializerList(clean_lines, linenum): - """Check if current line is inside constructor initializer list. - - Args: - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - Returns: - True if current line appears to be inside constructor initializer - list, False otherwise. - """ - for i in xrange(linenum, 1, -1): - line = clean_lines.elided[i] - if i == linenum: - remove_function_body = Match(r'^(.*)\{\s*$', line) - if remove_function_body: - line = remove_function_body.group(1) - - if Search(r'\s:\s*\w+[({]', line): - # A lone colon tend to indicate the start of a constructor - # initializer list. It could also be a ternary operator, which - # also tend to appear in constructor initializer lists as - # opposed to parameter lists. - return True - if Search(r'\}\s*,\s*$', line): - # A closing brace followed by a comma is probably the end of a - # brace-initialized member in constructor initializer list. - return True - if Search(r'[{};]\s*$', line): - # Found one of the following: - # - A closing brace or semicolon, probably the end of the previous - # function. - # - An opening brace, probably the start of current class or namespace. - # - # Current line is probably not inside an initializer list since - # we saw one of those things without seeing the starting colon. - return False - - # Got to the beginning of the file without seeing the start of - # constructor initializer list. - return False - - -def CheckForStringViewReferences(filename, clean_lines, linenum, error): - line = clean_lines.elided[linenum] - match = Search(r'const absl::string_view(?:\s*&)', line) - if match: - error(filename, linenum, 'runtime/references', 5, - 'Avoid const references to absl::string_view; just pass by value.') - - -def CheckCasts(filename, clean_lines, linenum, error): - """Various cast related checks. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - # Check to see if they're using an conversion function cast. - # I just try to capture the most common basic types, though there are more. - # Parameterless conversion functions, such as bool(), are allowed as they are - # probably a member operator declaration or default constructor. - match = Search( - r'(\bnew\s+(?:const\s+)?|\S<\s*(?:const\s+)?)?\b' - r'(int|float|double|bool|char|int32|uint32|int64|uint64)' - r'(\([^)].*)', line) - expecting_function = ExpectingFunctionArgs(clean_lines, linenum) - if match and not expecting_function: - matched_type = match.group(2) - - # matched_new_or_template is used to silence two false positives: - # - New operators - # - Template arguments with function types - # - # For template arguments, we match on types immediately following - # an opening bracket without any spaces. This is a fast way to - # silence the common case where the function type is the first - # template argument. False negative with less-than comparison is - # avoided because those operators are usually followed by a space. - # - # function // bracket + no space = false positive - # value < double(42) // bracket + space = true positive - matched_new_or_template = match.group(1) - - # Avoid arrays by looking for brackets that come after the closing - # parenthesis. - if Match(r'\([^()]+\)\s*\[', match.group(3)): - return - - # Other things to ignore: - # - Function pointers - # - Casts to pointer types - # - Placement new - # - Alias declarations - matched_funcptr = match.group(3) - if (matched_new_or_template is None and - not (matched_funcptr and - (Match(r'\((?:[^() ]+::\s*\*\s*)?[^() ]+\)\s*\(', - matched_funcptr) or - matched_funcptr.startswith('(*)'))) and - not Match(r'\s*using\s+\S+\s*=\s*' + matched_type, line) and - not Search(r'new\(\S+\)\s*' + matched_type, line)): - error(filename, linenum, 'readability/casting', 4, - 'Using deprecated casting style. ' - 'Use static_cast<%s>(...) instead' % - matched_type) - - if not expecting_function: - CheckCStyleCast(filename, clean_lines, linenum, 'static_cast', - r'\((int|float|double|bool|char|u?int(16|32|64))\)', error) - - # This doesn't catch all cases. Consider (const char * const)"hello". - # - # (char *) "foo" should always be a const_cast (reinterpret_cast won't - # compile). - if CheckCStyleCast(filename, clean_lines, linenum, 'const_cast', - r'\((char\s?\*+\s?)\)\s*"', error): - pass - else: - # Check pointer casts for other than string constants - CheckCStyleCast(filename, clean_lines, linenum, 'reinterpret_cast', - r'\((\w+\s?\*+\s?)\)', error) - - # In addition, we look for people taking the address of a cast. This - # is dangerous -- casts can assign to temporaries, so the pointer doesn't - # point where you think. - # - # Some non-identifier character is required before the '&' for the - # expression to be recognized as a cast. These are casts: - # expression = &static_cast(temporary()); - # function(&(int*)(temporary())); - # - # This is not a cast: - # reference_type&(int* function_param); - match = Search( - r'(?:[^\w]&\(([^)*][^)]*)\)[\w(])|' - r'(?:[^\w]&(static|dynamic|down|reinterpret)_cast\b)', line) - if match: - # Try a better error message when the & is bound to something - # dereferenced by the casted pointer, as opposed to the casted - # pointer itself. - parenthesis_error = False - match = Match(r'^(.*&(?:static|dynamic|down|reinterpret)_cast\b)<', line) - if match: - _, y1, x1 = CloseExpression(clean_lines, linenum, len(match.group(1))) - if x1 >= 0 and clean_lines.elided[y1][x1] == '(': - _, y2, x2 = CloseExpression(clean_lines, y1, x1) - if x2 >= 0: - extended_line = clean_lines.elided[y2][x2:] - if y2 < clean_lines.NumLines() - 1: - extended_line += clean_lines.elided[y2 + 1] - if Match(r'\s*(?:->|\[)', extended_line): - parenthesis_error = True - - if parenthesis_error: - error(filename, linenum, 'readability/casting', 4, - ('Are you taking an address of something dereferenced ' - 'from a cast? Wrapping the dereferenced expression in ' - 'parentheses will make the binding more obvious')) - else: - error(filename, linenum, 'runtime/casting', 4, - ('Are you taking an address of a cast? ' - 'This is dangerous: could be a temp var. ' - 'Take the address before doing the cast, rather than after')) - - -def CheckCStyleCast(filename, clean_lines, linenum, cast_type, pattern, error): - """Checks for a C-style cast by looking for the pattern. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - cast_type: The string for the C++ cast to recommend. This is either - reinterpret_cast, static_cast, or const_cast, depending. - pattern: The regular expression used to find C-style casts. - error: The function to call with any errors found. - - Returns: - True if an error was emitted. - False otherwise. - """ - line = clean_lines.elided[linenum] - match = Search(pattern, line) - if not match: - return False - - # Exclude lines with keywords that tend to look like casts - context = line[0:match.start(1) - 1] - if Match(r'.*\b(?:sizeof|alignof|alignas|[_A-Z][_A-Z0-9]*)\s*$', context): - return False - - # Try expanding current context to see if we one level of - # parentheses inside a macro. - if linenum > 0: - for i in xrange(linenum - 1, max(0, linenum - 5), -1): - context = clean_lines.elided[i] + context - if Match(r'.*\b[_A-Z][_A-Z0-9]*\s*\((?:\([^()]*\)|[^()])*$', context): - return False - - # operator++(int) and operator--(int) - if context.endswith(' operator++') or context.endswith(' operator--'): - return False - - # A single unnamed argument for a function tends to look like old style cast. - # If we see those, don't issue warnings for deprecated casts. - remainder = line[match.end(0):] - if Match(r'^\s*(?:;|const\b|throw\b|final\b|override\b|[=>{),]|->)', - remainder): - return False - - # At this point, all that should be left is actual casts. - error(filename, linenum, 'readability/casting', 4, - 'Using C-style cast. Use %s<%s>(...) instead' % - (cast_type, match.group(1))) - - return True - - -def ExpectingFunctionArgs(clean_lines, linenum): - """Checks whether where function type arguments are expected. - - Args: - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - - Returns: - True if the line at 'linenum' is inside something that expects arguments - of function types. - """ - line = clean_lines.elided[linenum] - return (Match(r'^\s*MOCK_(CONST_)?METHOD\d+(_T)?\(', line) or - (linenum >= 2 and - (Match(r'^\s*MOCK_(?:CONST_)?METHOD\d+(?:_T)?\((?:\S+,)?\s*$', - clean_lines.elided[linenum - 1]) or - Match(r'^\s*MOCK_(?:CONST_)?METHOD\d+(?:_T)?\(\s*$', - clean_lines.elided[linenum - 2]) or - Search(r'\bstd::m?function\s*\<\s*$', - clean_lines.elided[linenum - 1])))) - - -_HEADERS_CONTAINING_TEMPLATES = ( - ('', ('deque',)), - ('', ('unary_function', 'binary_function', - 'plus', 'minus', 'multiplies', 'divides', 'modulus', - 'negate', - 'equal_to', 'not_equal_to', 'greater', 'less', - 'greater_equal', 'less_equal', - 'logical_and', 'logical_or', 'logical_not', - 'unary_negate', 'not1', 'binary_negate', 'not2', - 'bind1st', 'bind2nd', - 'pointer_to_unary_function', - 'pointer_to_binary_function', - 'ptr_fun', - 'mem_fun_t', 'mem_fun', 'mem_fun1_t', 'mem_fun1_ref_t', - 'mem_fun_ref_t', - 'const_mem_fun_t', 'const_mem_fun1_t', - 'const_mem_fun_ref_t', 'const_mem_fun1_ref_t', - 'mem_fun_ref', - )), - ('', ('numeric_limits',)), - ('', ('list',)), - ('', ('multimap',)), - ('', ('allocator', 'make_shared', 'make_unique', 'shared_ptr', - 'unique_ptr', 'weak_ptr')), - ('', ('ostream',)), - ('', ('queue', 'priority_queue',)), - ('', ('multiset',)), - ('', ('stack',)), - ('', ('char_traits', 'basic_string',)), - ('', ('tuple',)), - ('', ('unordered_map', 'unordered_multimap')), - ('', ('unordered_set', 'unordered_multiset')), - ('', ('pair',)), - ('', ('vector',)), - - # gcc extensions. - # Note: std::hash is their hash, ::hash is our hash - ('', ('hash_map', 'hash_multimap',)), - ('', ('hash_set', 'hash_multiset',)), - ('', ('slist',)), - ) - -_HEADERS_MAYBE_TEMPLATES = ( - ('', ('copy', 'max', 'min', 'min_element', 'sort', - 'transform', - )), - ('', ('forward', 'make_pair', 'move', 'swap')), - ) - -_RE_PATTERN_STRING = re.compile(r'\bstring\b') -_RE_PATTERN_OSTREAM = re.compile(r'\bostream\b') - -_re_pattern_headers_maybe_templates = [] -for _header, _templates in _HEADERS_MAYBE_TEMPLATES: - for _template in _templates: - # Match max(..., ...), max(..., ...), but not foo->max, foo.max or - # 'type::max()'. - _re_pattern_headers_maybe_templates.append( - (re.compile(r'[^>.]\b' + _template + r'(<.*?>)?\([^\)]'), - _template, - _header)) -# Match set, but not foo->set, foo.set -_re_pattern_headers_maybe_templates.append( - (re.compile(r'[^>.]\bset\s*\<'), - 'set<>', - '')) -# Match 'map var' and 'std::map(...)', but not 'map(...)'' -_re_pattern_headers_maybe_templates.append( - (re.compile(r'(std\b::\bmap\s*\<)|(^(std\b::\b)map\b\(\s*\<)'), - 'map<>', - '')) - -# Other scripts may reach in and modify this pattern. -_re_pattern_templates = [] -for _header, _templates in _HEADERS_CONTAINING_TEMPLATES: - for _template in _templates: - _re_pattern_templates.append( - (re.compile(r'(\<|\b)' + _template + r'\s*\<'), - _template + '<>', - _header)) - - -def FilesBelongToSameModule(filename_cc, filename_h): - """Check if these two filenames belong to the same module. - - The concept of a 'module' here is a as follows: - foo.h, foo-inl.h, foo.cc, foo_test.cc and foo_unittest.cc belong to the - same 'module' if they are in the same directory. - some/path/public/xyzzy and some/path/internal/xyzzy are also considered - to belong to the same module here. - - If the filename_cc contains a longer path than the filename_h, for example, - '/absolute/path/to/base/sysinfo.cc', and this file would include - 'base/sysinfo.h', this function also produces the prefix needed to open the - header. This is used by the caller of this function to more robustly open the - header file. We don't have access to the real include paths in this context, - so we need this guesswork here. - - Known bugs: tools/base/bar.cc and base/bar.h belong to the same module - according to this implementation. Because of this, this function gives - some false positives. This should be sufficiently rare in practice. - - Args: - filename_cc: is the path for the source (e.g. .cc) file - filename_h: is the path for the header path - - Returns: - Tuple with a bool and a string: - bool: True if filename_cc and filename_h belong to the same module. - string: the additional prefix needed to open the header file. - """ - fileinfo_cc = FileInfo(filename_cc) - if not fileinfo_cc.Extension().lstrip('.') in GetNonHeaderExtensions(): - return (False, '') - - fileinfo_h = FileInfo(filename_h) - if not IsHeaderExtension(fileinfo_h.Extension().lstrip('.')): - return (False, '') - - filename_cc = filename_cc[:-(len(fileinfo_cc.Extension()))] - matched_test_suffix = Search(_TEST_FILE_SUFFIX, fileinfo_cc.BaseName()) - if matched_test_suffix: - filename_cc = filename_cc[:-len(matched_test_suffix.group(1))] - - filename_cc = filename_cc.replace('/public/', '/') - filename_cc = filename_cc.replace('/internal/', '/') - - filename_h = filename_h[:-(len(fileinfo_h.Extension()))] - if filename_h.endswith('-inl'): - filename_h = filename_h[:-len('-inl')] - filename_h = filename_h.replace('/public/', '/') - filename_h = filename_h.replace('/internal/', '/') - - files_belong_to_same_module = filename_cc.endswith(filename_h) - common_path = '' - if files_belong_to_same_module: - common_path = filename_cc[:-len(filename_h)] - return files_belong_to_same_module, common_path - - -def UpdateIncludeState(filename, include_dict, io=codecs): - """Fill up the include_dict with new includes found from the file. - - Args: - filename: the name of the header to read. - include_dict: a dictionary in which the headers are inserted. - io: The io factory to use to read the file. Provided for testability. - - Returns: - True if a header was successfully added. False otherwise. - """ - headerfile = None - try: - with io.open(filename, 'r', 'utf8', 'replace') as headerfile: - linenum = 0 - for line in headerfile: - linenum += 1 - clean_line = CleanseComments(line) - match = _RE_PATTERN_INCLUDE.search(clean_line) - if match: - include = match.group(2) - include_dict.setdefault(include, linenum) - return True - except IOError: - return False - - - -def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, - io=codecs): - """Reports for missing stl includes. - - This function will output warnings to make sure you are including the headers - necessary for the stl containers and functions that you use. We only give one - reason to include a header. For example, if you use both equal_to<> and - less<> in a .h file, only one (the latter in the file) of these will be - reported as a reason to include the . - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - include_state: An _IncludeState instance. - error: The function to call with any errors found. - io: The IO factory to use to read the header file. Provided for unittest - injection. - """ - # A map of entity to a tuple of line number and tuple of headers. - # Example: { 'less<>': (1219, ('',)) } - # Example: { 'ostream': (1234, ('', '', '')) } - required = {} - - def Require(entity, linenum, *headers): - """Adds an entity at the given line, along with a list of possible headers - in which to find it. The first header is treated as the preferred header. - """ - required[entity] = (linenum, headers) - - for linenum in xrange(clean_lines.NumLines()): - line = clean_lines.elided[linenum] - if not line or line[0] == '#': - continue - - # String is special -- it is a non-templatized type in STL. - matched = _RE_PATTERN_STRING.search(line) - if matched: - # Don't warn about strings in non-STL namespaces: - # (We check only the first match per line; good enough.) - prefix = line[:matched.start()] - if prefix.endswith('std::') or not prefix.endswith('::'): - Require('string', linenum, '') - - # Ostream is special too -- also non-templatized - matched = _RE_PATTERN_OSTREAM.search(line) - if matched: - if _IsSourceFilename(filename): - Require('ostream', linenum, '', '') - else: - Require('ostream', linenum, '', '', '') - - for pattern, template, header in _re_pattern_headers_maybe_templates: - if pattern.search(line): - Require(template, linenum, header) - - # The following function is just a speed up, no semantics are changed. - if not '<' in line: # Reduces the cpu time usage by skipping lines. - continue - - for pattern, template, header in _re_pattern_templates: - matched = pattern.search(line) - if matched: - # Don't warn about IWYU in non-STL namespaces: - # (We check only the first match per line; good enough.) - prefix = line[:matched.start()] - if prefix.endswith('std::') or not prefix.endswith('::'): - Require(template, linenum, header) - - # The policy is that if you #include something in foo.h you don't need to - # include it again in foo.cc. Here, we will look at possible includes. - # Let's flatten the include_state include_list and copy it into a dictionary. - include_dict = dict([item for sublist in include_state.include_list - for item in sublist]) - - # Did we find the header for this file (if any) and successfully load it? - header_found = False - - # Use the absolute path so that matching works properly. - abs_filename = FileInfo(filename).FullName() - - # For Emacs's flymake. - # If cpplint is invoked from Emacs's flymake, a temporary file is generated - # by flymake and that file name might end with '_flymake.cc'. In that case, - # restore original file name here so that the corresponding header file can be - # found. - # e.g. If the file name is 'foo_flymake.cc', we should search for 'foo.h' - # instead of 'foo_flymake.h' - abs_filename = re.sub(r'_flymake\.cc$', '.cc', abs_filename) - - # include_dict is modified during iteration, so we iterate over a copy of - # the keys. - header_keys = list(include_dict.keys()) - for header in header_keys: - (same_module, common_path) = FilesBelongToSameModule(abs_filename, header) - fullpath = common_path + header - if same_module and UpdateIncludeState(fullpath, include_dict, io): - header_found = True - - # If we can't find the header file for a .cc, assume it's because we don't - # know where to look. In that case we'll give up as we're not sure they - # didn't include it in the .h file. - # TODO(unknown): Do a better job of finding .h files so we are confident that - # not having the .h file means there isn't one. - if not header_found: - for extension in GetNonHeaderExtensions(): - if filename.endswith('.' + extension): - return - - # Keep track of which headers have been reported already - reported = set() - - # All the lines have been processed, report the errors found. - for template in sorted(required, key=required.__getitem__): - (line, headers) = required[template] - found = False - for required_header_unstripped in headers: - if required_header_unstripped in reported: - found = True - break - - if required_header_unstripped.strip('<>"') in include_dict: - found = True - break - - if not found: - preferred_header = headers[0] - reported.add(preferred_header) - if len(headers) < 2: - alternatives = '' - else: - alternatives = ' (or ' + ', '.join(headers[1:]) + ')' - error(filename, line, - 'build/include_what_you_use', 4, - 'Add #include ' + preferred_header + ' for ' + template + - alternatives) - - -_RE_PATTERN_EXPLICIT_MAKEPAIR = re.compile(r'\bmake_pair\s*<') - - -def CheckMakePairUsesDeduction(filename, clean_lines, linenum, error): - """Check that make_pair's template arguments are deduced. - - G++ 4.6 in C++11 mode fails badly if make_pair's template arguments are - specified explicitly, and such use isn't intended in any case. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - match = _RE_PATTERN_EXPLICIT_MAKEPAIR.search(line) - if match: - error(filename, linenum, 'build/explicit_make_pair', - 4, # 4 = high confidence - 'For C++11-compatibility, omit template arguments from make_pair' - ' OR use pair directly OR if appropriate, construct a pair directly') - - -def CheckRedundantVirtual(filename, clean_lines, linenum, error): - """Check if line contains a redundant "virtual" function-specifier. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - # Look for "virtual" on current line. - line = clean_lines.elided[linenum] - virtual = Match(r'^(.*)(\bvirtual\b)(.*)$', line) - if not virtual: return - - # Ignore "virtual" keywords that are near access-specifiers. These - # are only used in class base-specifier and do not apply to member - # functions. - if (Search(r'\b(public|protected|private)\s+$', virtual.group(1)) or - Match(r'^\s+(public|protected|private)\b', virtual.group(3))): - return - - # Ignore the "virtual" keyword from virtual base classes. Usually - # there is a column on the same line in these cases (virtual base - # classes are rare in google3 because multiple inheritance is rare). - if Match(r'^.*[^:]:[^:].*$', line): return - - # Look for the next opening parenthesis. This is the start of the - # parameter list (possibly on the next line shortly after virtual). - # TODO(unknown): doesn't work if there are virtual functions with - # decltype() or other things that use parentheses, but csearch suggests - # that this is rare. - end_col = -1 - end_line = -1 - start_col = len(virtual.group(2)) - for start_line in xrange(linenum, min(linenum + 3, clean_lines.NumLines())): - line = clean_lines.elided[start_line][start_col:] - parameter_list = Match(r'^([^(]*)\(', line) - if parameter_list: - # Match parentheses to find the end of the parameter list - (_, end_line, end_col) = CloseExpression( - clean_lines, start_line, start_col + len(parameter_list.group(1))) - break - start_col = 0 - - if end_col < 0: - return # Couldn't find end of parameter list, give up - - # Look for "override" or "final" after the parameter list - # (possibly on the next few lines). - for i in xrange(end_line, min(end_line + 3, clean_lines.NumLines())): - line = clean_lines.elided[i][end_col:] - match = Search(r'\b(override|final)\b', line) - if match: - error(filename, linenum, 'readability/inheritance', 4, - ('"virtual" is redundant since function is ' - 'already declared as "%s"' % match.group(1))) - - # Set end_col to check whole lines after we are done with the - # first line. - end_col = 0 - if Search(r'[^\w]\s*$', line): - break - - -def CheckRedundantOverrideOrFinal(filename, clean_lines, linenum, error): - """Check if line contains a redundant "override" or "final" virt-specifier. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - # Look for closing parenthesis nearby. We need one to confirm where - # the declarator ends and where the virt-specifier starts to avoid - # false positives. - line = clean_lines.elided[linenum] - declarator_end = line.rfind(')') - if declarator_end >= 0: - fragment = line[declarator_end:] - else: - if linenum > 1 and clean_lines.elided[linenum - 1].rfind(')') >= 0: - fragment = line - else: - return - - # Check that at most one of "override" or "final" is present, not both - if Search(r'\boverride\b', fragment) and Search(r'\bfinal\b', fragment): - error(filename, linenum, 'readability/inheritance', 4, - ('"override" is redundant since function is ' - 'already declared as "final"')) - - - - -# Returns true if we are at a new block, and it is directly -# inside of a namespace. -def IsBlockInNameSpace(nesting_state, is_forward_declaration): - """Checks that the new block is directly in a namespace. - - Args: - nesting_state: The _NestingState object that contains info about our state. - is_forward_declaration: If the class is a forward declared class. - Returns: - Whether or not the new block is directly in a namespace. - """ - if is_forward_declaration: - return len(nesting_state.stack) >= 1 and ( - isinstance(nesting_state.stack[-1], _NamespaceInfo)) - - - return (len(nesting_state.stack) > 1 and - nesting_state.stack[-1].check_namespace_indentation and - isinstance(nesting_state.stack[-2], _NamespaceInfo)) - - -def ShouldCheckNamespaceIndentation(nesting_state, is_namespace_indent_item, - raw_lines_no_comments, linenum): - """This method determines if we should apply our namespace indentation check. - - Args: - nesting_state: The current nesting state. - is_namespace_indent_item: If we just put a new class on the stack, True. - If the top of the stack is not a class, or we did not recently - add the class, False. - raw_lines_no_comments: The lines without the comments. - linenum: The current line number we are processing. - - Returns: - True if we should apply our namespace indentation check. Currently, it - only works for classes and namespaces inside of a namespace. - """ - - is_forward_declaration = IsForwardClassDeclaration(raw_lines_no_comments, - linenum) - - if not (is_namespace_indent_item or is_forward_declaration): - return False - - # If we are in a macro, we do not want to check the namespace indentation. - if IsMacroDefinition(raw_lines_no_comments, linenum): - return False - - return IsBlockInNameSpace(nesting_state, is_forward_declaration) - - -# Call this method if the line is directly inside of a namespace. -# If the line above is blank (excluding comments) or the start of -# an inner namespace, it cannot be indented. -def CheckItemIndentationInNamespace(filename, raw_lines_no_comments, linenum, - error): - line = raw_lines_no_comments[linenum] - if Match(r'^\s+', line): - error(filename, linenum, 'runtime/indentation_namespace', 4, - 'Do not indent within a namespace') - - -def ProcessLine(filename, file_extension, clean_lines, line, - include_state, function_state, nesting_state, error, - extra_check_functions=None): - """Processes a single line in the file. - - Args: - filename: Filename of the file that is being processed. - file_extension: The extension (dot not included) of the file. - clean_lines: An array of strings, each representing a line of the file, - with comments stripped. - line: Number of line being processed. - include_state: An _IncludeState instance in which the headers are inserted. - function_state: A _FunctionState instance which counts function lines, etc. - nesting_state: A NestingState instance which maintains information about - the current stack of nested blocks being parsed. - error: A callable to which errors are reported, which takes 4 arguments: - filename, line number, error level, and message - extra_check_functions: An array of additional check functions that will be - run on each source line. Each function takes 4 - arguments: filename, clean_lines, line, error - """ - raw_lines = clean_lines.raw_lines - ParseNolintSuppressions(filename, raw_lines[line], line, error) - nesting_state.Update(filename, clean_lines, line, error) - CheckForNamespaceIndentation(filename, nesting_state, clean_lines, line, - error) - if nesting_state.InAsmBlock(): return - CheckForFunctionLengths(filename, clean_lines, line, function_state, error) - CheckForMultilineCommentsAndStrings(filename, clean_lines, line, error) - CheckStyle(filename, clean_lines, line, file_extension, nesting_state, error) - CheckLanguage(filename, clean_lines, line, file_extension, include_state, - nesting_state, error) - CheckForStringViewReferences(filename, clean_lines, line, error) - CheckForNonStandardConstructs(filename, clean_lines, line, - nesting_state, error) - CheckVlogArguments(filename, clean_lines, line, error) - CheckPosixThreading(filename, clean_lines, line, error) - CheckInvalidIncrement(filename, clean_lines, line, error) - CheckMakePairUsesDeduction(filename, clean_lines, line, error) - CheckRedundantVirtual(filename, clean_lines, line, error) - CheckRedundantOverrideOrFinal(filename, clean_lines, line, error) - if extra_check_functions: - for check_fn in extra_check_functions: - check_fn(filename, clean_lines, line, error) - -def FlagCxx11Features(filename, clean_lines, linenum, error): - """Flag those c++11 features that we only allow in certain places. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - include = Match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line) - - # Flag unapproved C++ TR1 headers. - if include and include.group(1).startswith('tr1/'): - error(filename, linenum, 'build/c++tr1', 5, - ('C++ TR1 headers such as <%s> are unapproved.') % include.group(1)) - - # Flag unapproved C++11 headers. - if include and include.group(1) in ('cfenv', - 'condition_variable', - 'fenv.h', - 'future', - 'mutex', - 'thread', - 'chrono', - 'ratio', - 'regex', - 'system_error', - ): - error(filename, linenum, 'build/c++11', 5, - ('<%s> is an unapproved C++11 header.') % include.group(1)) - - # The only place where we need to worry about C++11 keywords and library - # features in preprocessor directives is in macro definitions. - if Match(r'\s*#', line) and not Match(r'\s*#\s*define\b', line): return - - # These are classes and free functions. The classes are always - # mentioned as std::*, but we only catch the free functions if - # they're not found by ADL. They're alphabetical by header. - for top_name in ( - # type_traits - 'alignment_of', - 'aligned_union', - ): - if Search(r'\bstd::%s\b' % top_name, line): - error(filename, linenum, 'build/c++11', 5, - ('std::%s is an unapproved C++11 class or function. Send c-style ' - 'an example of where it would make your code more readable, and ' - 'they may let you use it.') % top_name) - - -def FlagCxx14Features(filename, clean_lines, linenum, error): - """Flag those C++14 features that we restrict. - - Args: - filename: The name of the current file. - clean_lines: A CleansedLines instance containing the file. - linenum: The number of the line to check. - error: The function to call with any errors found. - """ - line = clean_lines.elided[linenum] - - include = Match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line) - - # Flag unapproved C++14 headers. - if include and include.group(1) in ('scoped_allocator', 'shared_mutex'): - error(filename, linenum, 'build/c++14', 5, - ('<%s> is an unapproved C++14 header.') % include.group(1)) - - # These are classes and free functions with abseil equivalents. - for top_name in ( - # memory - 'make_unique', - ): - if Search(r'\bstd::%s\b' % top_name, line): - error(filename, linenum, 'build/c++14', 5, - 'std::%s does not exist in C++11. Use absl::%s instead.' % - (top_name, top_name)) - - -def ProcessFileData(filename, file_extension, lines, error, - extra_check_functions=None): - """Performs lint checks and reports any errors to the given error function. - - Args: - filename: Filename of the file that is being processed. - file_extension: The extension (dot not included) of the file. - lines: An array of strings, each representing a line of the file, with the - last element being empty if the file is terminated with a newline. - error: A callable to which errors are reported, which takes 4 arguments: - filename, line number, error level, and message - extra_check_functions: An array of additional check functions that will be - run on each source line. Each function takes 4 - arguments: filename, clean_lines, line, error - """ - lines = (['// marker so line numbers and indices both start at 1'] + lines + - ['// marker so line numbers end in a known way']) - - include_state = _IncludeState() - function_state = _FunctionState() - nesting_state = NestingState() - - ResetNolintSuppressions() - - CheckForCopyright(filename, lines, error) - ProcessGlobalSuppresions(lines) - RemoveMultiLineComments(filename, lines, error) - clean_lines = CleansedLines(lines) - - if IsHeaderExtension(file_extension): - CheckForHeaderGuard(filename, clean_lines, error) - - for line in xrange(clean_lines.NumLines()): - ProcessLine(filename, file_extension, clean_lines, line, - include_state, function_state, nesting_state, error, - extra_check_functions) - FlagCxx11Features(filename, clean_lines, line, error) - FlagCxx14Features(filename, clean_lines, line, error) - nesting_state.CheckCompletedBlocks(filename, error) - - CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error) - - # Check that the .cc file has included its header if it exists. - if _IsSourceExtension(file_extension): - CheckHeaderFileIncluded(filename, include_state, error) - - # We check here rather than inside ProcessLine so that we see raw - # lines rather than "cleaned" lines. - CheckForBadCharacters(filename, lines, error) - - CheckForNewlineAtEOF(filename, lines, error) - -def ProcessConfigOverrides(filename): - """ Loads the configuration files and processes the config overrides. - - Args: - filename: The name of the file being processed by the linter. - - Returns: - False if the current |filename| should not be processed further. - """ - - abs_filename = os.path.abspath(filename) - cfg_filters = [] - keep_looking = True - while keep_looking: - abs_path, base_name = os.path.split(abs_filename) - if not base_name: - break # Reached the root directory. - - cfg_file = os.path.join(abs_path, "CPPLINT.cfg") - abs_filename = abs_path - if not os.path.isfile(cfg_file): - continue - - try: - with open(cfg_file) as file_handle: - for line in file_handle: - line, _, _ = line.partition('#') # Remove comments. - if not line.strip(): - continue - - name, _, val = line.partition('=') - name = name.strip() - val = val.strip() - if name == 'set noparent': - keep_looking = False - elif name == 'filter': - cfg_filters.append(val) - elif name == 'exclude_files': - # When matching exclude_files pattern, use the base_name of - # the current file name or the directory name we are processing. - # For example, if we are checking for lint errors in /foo/bar/baz.cc - # and we found the .cfg file at /foo/CPPLINT.cfg, then the config - # file's "exclude_files" filter is meant to be checked against "bar" - # and not "baz" nor "bar/baz.cc". - if base_name: - pattern = re.compile(val) - if pattern.match(base_name): - if _cpplint_state.quiet: - # Suppress "Ignoring file" warning when using --quiet. - return False - _cpplint_state.PrintInfo('Ignoring "%s": file excluded by "%s". ' - 'File path component "%s" matches ' - 'pattern "%s"\n' % - (filename, cfg_file, base_name, val)) - return False - elif name == 'linelength': - global _line_length - try: - _line_length = int(val) - except ValueError: - _cpplint_state.PrintError('Line length must be numeric.') - elif name == 'extensions': - ProcessExtensionsOption(val) - elif name == 'root': - global _root - # root directories are specified relative to CPPLINT.cfg dir. - _root = os.path.join(os.path.dirname(cfg_file), val) - elif name == 'headers': - ProcessHppHeadersOption(val) - elif name == 'includeorder': - ProcessIncludeOrderOption(val) - else: - _cpplint_state.PrintError( - 'Invalid configuration option (%s) in file %s\n' % - (name, cfg_file)) - - except IOError: - _cpplint_state.PrintError( - "Skipping config file '%s': Can't open for reading\n" % cfg_file) - keep_looking = False - - # Apply all the accumulated filters in reverse order (top-level directory - # config options having the least priority). - for cfg_filter in reversed(cfg_filters): - _AddFilters(cfg_filter) - - return True - - -def ProcessFile(filename, vlevel, extra_check_functions=None): - """Does google-lint on a single file. - - Args: - filename: The name of the file to parse. - - vlevel: The level of errors to report. Every error of confidence - >= verbose_level will be reported. 0 is a good default. - - extra_check_functions: An array of additional check functions that will be - run on each source line. Each function takes 4 - arguments: filename, clean_lines, line, error - """ - - _SetVerboseLevel(vlevel) - _BackupFilters() - old_errors = _cpplint_state.error_count - - if not ProcessConfigOverrides(filename): - _RestoreFilters() - return - - lf_lines = [] - crlf_lines = [] - try: - # Support the UNIX convention of using "-" for stdin. Note that - # we are not opening the file with universal newline support - # (which codecs doesn't support anyway), so the resulting lines do - # contain trailing '\r' characters if we are reading a file that - # has CRLF endings. - # If after the split a trailing '\r' is present, it is removed - # below. - if filename == '-': - lines = codecs.StreamReaderWriter(sys.stdin, - codecs.getreader('utf8'), - codecs.getwriter('utf8'), - 'replace').read().split('\n') - else: - with codecs.open(filename, 'r', 'utf8', 'replace') as target_file: - lines = target_file.read().split('\n') - - # Remove trailing '\r'. - # The -1 accounts for the extra trailing blank line we get from split() - for linenum in range(len(lines) - 1): - if lines[linenum].endswith('\r'): - lines[linenum] = lines[linenum].rstrip('\r') - crlf_lines.append(linenum + 1) - else: - lf_lines.append(linenum + 1) - - except IOError: - _cpplint_state.PrintError( - "Skipping input '%s': Can't open for reading\n" % filename) - _RestoreFilters() - return - - # Note, if no dot is found, this will give the entire filename as the ext. - file_extension = filename[filename.rfind('.') + 1:] - - # When reading from stdin, the extension is unknown, so no cpplint tests - # should rely on the extension. - if filename != '-' and file_extension not in GetAllExtensions(): - _cpplint_state.PrintError('Ignoring %s; not a valid file name ' - '(%s)\n' % (filename, ', '.join(GetAllExtensions()))) - else: - ProcessFileData(filename, file_extension, lines, Error, - extra_check_functions) - - # If end-of-line sequences are a mix of LF and CR-LF, issue - # warnings on the lines with CR. - # - # Don't issue any warnings if all lines are uniformly LF or CR-LF, - # since critique can handle these just fine, and the style guide - # doesn't dictate a particular end of line sequence. - # - # We can't depend on os.linesep to determine what the desired - # end-of-line sequence should be, since that will return the - # server-side end-of-line sequence. - if lf_lines and crlf_lines: - # Warn on every line with CR. An alternative approach might be to - # check whether the file is mostly CRLF or just LF, and warn on the - # minority, we bias toward LF here since most tools prefer LF. - for linenum in crlf_lines: - Error(filename, linenum, 'whitespace/newline', 1, - 'Unexpected \\r (^M) found; better to use only \\n') - - # Suppress printing anything if --quiet was passed unless the error - # count has increased after processing this file. - if not _cpplint_state.quiet or old_errors != _cpplint_state.error_count: - _cpplint_state.PrintInfo('Done processing %s\n' % filename) - _RestoreFilters() - - -def PrintUsage(message): - """Prints a brief usage string and exits, optionally with an error message. - - Args: - message: The optional error message. - """ - sys.stderr.write(_USAGE % (list(GetAllExtensions()), - ','.join(list(GetAllExtensions())), - GetHeaderExtensions(), - ','.join(GetHeaderExtensions()))) - - if message: - sys.exit('\nFATAL ERROR: ' + message) - else: - sys.exit(0) - -def PrintVersion(): - sys.stdout.write('Cpplint fork (https://github.com/cpplint/cpplint)\n') - sys.stdout.write('cpplint ' + __VERSION__ + '\n') - sys.stdout.write('Python ' + sys.version + '\n') - sys.exit(0) - -def PrintCategories(): - """Prints a list of all the error-categories used by error messages. - - These are the categories used to filter messages via --filter. - """ - sys.stderr.write(''.join(' %s\n' % cat for cat in _ERROR_CATEGORIES)) - sys.exit(0) - - -def ParseArguments(args): - """Parses the command line arguments. - - This may set the output format and verbosity level as side-effects. - - Args: - args: The command line arguments: - - Returns: - The list of filenames to lint. - """ - try: - (opts, filenames) = getopt.getopt(args, '', ['help', 'output=', 'verbose=', - 'v=', - 'version', - 'counting=', - 'filter=', - 'root=', - 'repository=', - 'linelength=', - 'extensions=', - 'exclude=', - 'recursive', - 'headers=', - 'includeorder=', - 'quiet']) - except getopt.GetoptError: - PrintUsage('Invalid arguments.') - - verbosity = _VerboseLevel() - output_format = _OutputFormat() - filters = '' - quiet = _Quiet() - counting_style = '' - recursive = False - - for (opt, val) in opts: - if opt == '--help': - PrintUsage(None) - if opt == '--version': - PrintVersion() - elif opt == '--output': - if val not in ('emacs', 'vs7', 'eclipse', 'junit', 'sed', 'gsed'): - PrintUsage('The only allowed output formats are emacs, vs7, eclipse ' - 'sed, gsed and junit.') - output_format = val - elif opt == '--quiet': - quiet = True - elif opt == '--verbose' or opt == '--v': - verbosity = int(val) - elif opt == '--filter': - filters = val - if not filters: - PrintCategories() - elif opt == '--counting': - if val not in ('total', 'toplevel', 'detailed'): - PrintUsage('Valid counting options are total, toplevel, and detailed') - counting_style = val - elif opt == '--root': - global _root - _root = val - elif opt == '--repository': - global _repository - _repository = val - elif opt == '--linelength': - global _line_length - try: - _line_length = int(val) - except ValueError: - PrintUsage('Line length must be digits.') - elif opt == '--exclude': - global _excludes - if not _excludes: - _excludes = set() - _excludes.update(glob.glob(val)) - elif opt == '--extensions': - ProcessExtensionsOption(val) - elif opt == '--headers': - ProcessHppHeadersOption(val) - elif opt == '--recursive': - recursive = True - elif opt == '--includeorder': - ProcessIncludeOrderOption(val) - - if not filenames: - PrintUsage('No files were specified.') - - if recursive: - filenames = _ExpandDirectories(filenames) - - if _excludes: - filenames = _FilterExcludedFiles(filenames) - - _SetOutputFormat(output_format) - _SetQuiet(quiet) - _SetVerboseLevel(verbosity) - _SetFilters(filters) - _SetCountingStyle(counting_style) - - filenames.sort() - return filenames - -def _ExpandDirectories(filenames): - """Searches a list of filenames and replaces directories in the list with - all files descending from those directories. Files with extensions not in - the valid extensions list are excluded. - - Args: - filenames: A list of files or directories - - Returns: - A list of all files that are members of filenames or descended from a - directory in filenames - """ - expanded = set() - for filename in filenames: - if not os.path.isdir(filename): - expanded.add(filename) - continue - - for root, _, files in os.walk(filename): - for loopfile in files: - fullname = os.path.join(root, loopfile) - if fullname.startswith('.' + os.path.sep): - fullname = fullname[len('.' + os.path.sep):] - expanded.add(fullname) - - filtered = [] - for filename in expanded: - if os.path.splitext(filename)[1][1:] in GetAllExtensions(): - filtered.append(filename) - return filtered - -def _FilterExcludedFiles(fnames): - """Filters out files listed in the --exclude command line switch. File paths - in the switch are evaluated relative to the current working directory - """ - exclude_paths = [os.path.abspath(f) for f in _excludes] - # because globbing does not work recursively, exclude all subpath of all excluded entries - return [f for f in fnames - if not any(e for e in exclude_paths - if _IsParentOrSame(e, os.path.abspath(f)))] - -def _IsParentOrSame(parent, child): - """Return true if child is subdirectory of parent. - Assumes both paths are absolute and don't contain symlinks. - """ - parent = os.path.normpath(parent) - child = os.path.normpath(child) - if parent == child: - return True - - prefix = os.path.commonprefix([parent, child]) - if prefix != parent: - return False - # Note: os.path.commonprefix operates on character basis, so - # take extra care of situations like '/foo/ba' and '/foo/bar/baz' - child_suffix = child[len(prefix):] - child_suffix = child_suffix.lstrip(os.sep) - return child == os.path.join(prefix, child_suffix) - -def main(): - filenames = ParseArguments(sys.argv[1:]) - backup_err = sys.stderr - try: - # Change stderr to write with replacement characters so we don't die - # if we try to print something containing non-ASCII characters. - sys.stderr = codecs.StreamReader(sys.stderr, 'replace') - - _cpplint_state.ResetErrorCounts() - for filename in filenames: - ProcessFile(filename, _cpplint_state.verbose_level) - # If --quiet is passed, suppress printing error count unless there are errors. - if not _cpplint_state.quiet or _cpplint_state.error_count > 0: - _cpplint_state.PrintErrorCounts() - - if _cpplint_state.output_format == 'junit': - sys.stderr.write(_cpplint_state.FormatJUnitXML()) - - finally: - sys.stderr = backup_err - - sys.exit(_cpplint_state.error_count > 0) - - -if __name__ == '__main__': - main() diff --git a/scripts/create_pull_request.rb b/scripts/create_pull_request.rb deleted file mode 100644 index bb24943..0000000 --- a/scripts/create_pull_request.rb +++ /dev/null @@ -1,69 +0,0 @@ -# frozen_string_literal: true - -# Copyright 2022 Google LLC -# -# 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. - -require 'octokit' -require 'optparse' - -@options = { - repo_root: "./", - pr_title: "Autogenerated PR", - pr_body: "", - base_branch: "generated-branch-for-pr-"+Time.new.strftime("%Y%m%d%H%M%S"), - commit_comment: "Auto generated commit." -} -begin - OptionParser.new do |opts| - opts.banner = "Usage: create_pull_request.rb [options]" - opts.on('-r', '--repo-root REPO_ROOT', 'Root path of the repo dir.') { |v| @options[:repo_root] = v } - opts.on('-t', '--repo-token REPO_TOKEN', 'Token with write access') { |v| @options[:repo_token] = v } - opts.on('-n', '--target-path PATH', 'Path of targeted file or dir') { |v| @options[:target_path] = v } - opts.on('--pr-title PR_TITLE', 'Title of a PR') { |v| @options[:pr_title] = v } - opts.on('--pr-body PR_BODY', 'Body of a PR') { |v| @options[:pr_body] = v } - opts.on('--base-branch BASE_BRANCH', 'A new branch will be generated if not specified or the branch does not exist.') { |v| @options[:base_branch] = v } - opts.on('--commit-comment COMMIT_COMMENT', 'Commit comment') { |v| @options[:commit_comment] = v } - end.parse! - - raise OptionParser::MissingArgument if @options[:repo_token].nil? || @options[:target_path].nil? -rescue OptionParser::MissingArgument - puts "target path, `--target-path`, should be specified. " if @options[:target_path].nil? - puts "A token ,`--repo-token`, should be provided for creating a pull request." if @options[:repo_token].nil? - raise -end - -REPO_ROOT=@options[:repo_root] -ACCESS_TOKEN=@options[:repo_token] -TARGET_PATH=@options[:target_path] -PR_TITLE=@options[:pr_title] -PR_BODY=@options[:pr_body] -BASE_BRANCH=@options[:base_branch] -COMMIT_COMMENT=@options[:commit_comment] - -def generate_pr_for_target_changes(repo_root:, target_path:) - if `git diff #{TARGET_PATH}`=="" - puts "The file, #{TARGET_PATH}, has no changes." - return - end - system("cd #{REPO_ROOT}\ngit checkout -b #{BASE_BRANCH}\ngit add #{TARGET_PATH}\ngit commit -m \"#{COMMIT_COMMENT}\"\ngit push -u origin #{BASE_BRANCH}") - client = Octokit::Client.new(access_token: ACCESS_TOKEN) - client.create_pull_request("firebase/firebase-ios-sdk", "main", BASE_BRANCH, PR_TITLE, PR_BODY) -end - - -def main() - generate_pr_for_target_changes(repo_root: REPO_ROOT, target_path: TARGET_PATH) -end - -main() diff --git a/scripts/create_spec_repo/Package.swift b/scripts/create_spec_repo/Package.swift deleted file mode 100644 index e333872..0000000 --- a/scripts/create_spec_repo/Package.swift +++ /dev/null @@ -1,38 +0,0 @@ -// swift-tools-version:5.0 -// The swift-tools-version declares the minimum version of Swift required to build this package. -/* - * Copyright 2020 Google LLC - * - * 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. - */ - -import PackageDescription - -let package = Package( - name: "PodSpecBuilder", - platforms: [ - .macOS(.v10_13), - ], - products: [ - .executable(name: "spec-repo-builder", targets: ["SpecRepoBuilder"]), - ], - dependencies: [ - .package(url: "https://github.com/apple/swift-argument-parser", .upToNextMinor(from: "0.3.0")), - ], - targets: [ - .target( - name: "SpecRepoBuilder", - dependencies: [.product(name: "ArgumentParser", package: "swift-argument-parser")] - ), - ] -) diff --git a/scripts/create_spec_repo/RC_firebase_sdk.textproto b/scripts/create_spec_repo/RC_firebase_sdk.textproto deleted file mode 100644 index 543d413..0000000 --- a/scripts/create_spec_repo/RC_firebase_sdk.textproto +++ /dev/null @@ -1,9 +0,0 @@ -# Config file to update versions of Pods which will be pushed to SpecsStaging -# repo by add_podspec tool. -# e.g. -# sdk { -# sdk_name: "FirebaseAnalytics" -# sdk_version: "6.8.2" -# } -# Podspec versions will be the same as firebase-ios-sdk podspecs if they are not -# specified in this file. diff --git a/scripts/create_spec_repo/README.md b/scripts/create_spec_repo/README.md deleted file mode 100644 index c95a7b4..0000000 --- a/scripts/create_spec_repo/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# Podspec presubmit test setup - -Podspec presubmit test is to help ensure podspec is releasable. 'pod spec lint' will run for SDKs -with sources of - -- https://github.com/firebase/SpecsTesting -- https://github.com/firebase/SpecsDev.git -- https://github.com/firebase/SpecsStaging.git -- https://cdn.cocoapods.org/ - -where [SpecsTesting](https://github.com/firebase/SpecsTesting) is generated from the head of the -main branch of [firebase-ios-sdk repo](https://github.com/firebase/firebase-ios-sdk). - -The [prerelease workflow](https://github.com/firebase/firebase-ios-sdk/blob/main/.github/workflows/prerelease.yml#L11-L46) -will update the [SpecsTesting repo](https://github.com/firebase/SpecsTesting) nightly from the -head of the main branch. -In order to let presubmit tests run on the latest podspec repo, [SpecsTesting repo](https://github.com/firebase/SpecsTesting) -will be updated when a PR with changed podspecs is merged. -When this PR is merged, changed podspecs will be `pod repo push`ed to the podspec repo in -[postsubmit tests](https://github.com/firebase/firebase-ios-sdk/blob/main/.github/workflows/prerelease.yml#L48-L94). - -Since `pod spec lint` will test podspecs with remote sources. One PR with changes on multiple -podspecs are not encouraged. Changes with multiple podspecs, including their dependencies, might -fail presubmit tests. - -## Set up presubmit tests - -To set up presubmit tests, we can add a new job in SDK workflows. An example of `FirebaseDatabase` -is shown below. -`github.event.pull_request.merged != true && github.event.action != 'closed'` is to trigger this -job in presubmit. -``` - podspec-presubmit: - # Don't run on private repo unless it is a PR. - if: github.repository == 'Firebase/firebase-ios-sdk' && github.event.pull_request.merged != true && github.event.action != 'closed' - runs-on: macOS-latest - steps: - - uses: actions/checkout@v3 - - uses: ruby/setup-ruby@359bebbc29cbe6c87da6bc9ea3bc930432750108 - with: - ruby-version: '2.7' - - name: Setup Bundler - run: scripts/setup_bundler.sh - - name: Build and test - run: scripts/third_party/travis/retry.sh pod spec lint FirebaseDatabase.podspec --skip-tests --sources='https://github.com/firebase/SpecsTesting','https://github.com/firebase/SpecsDev.git','https://github.com/firebase/SpecsStaging.git','https://cdn.cocoapods.org/' - -``` - -Once a PR is merged, [`update_SpecsTesting_repo` job](https://github.com/firebase/firebase-ios-sdk/blob/main/.github/workflows/prerelease.yml#L48) -in the [prerelease workflow](https://github.com/firebase/firebase-ios-sdk/blob/main/.github/workflows/prerelease.yml) -will automatically `pod repo push` changed podspecs in postsubmits, diff --git a/scripts/create_spec_repo/Sources/SpecRepoBuilder/main.swift b/scripts/create_spec_repo/Sources/SpecRepoBuilder/main.swift deleted file mode 100644 index 4134ca5..0000000 --- a/scripts/create_spec_repo/Sources/SpecRepoBuilder/main.swift +++ /dev/null @@ -1,470 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * 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. - */ - -import ArgumentParser -import Foundation - -private enum Constants {} - -extension Constants { - static let specDependencyLabel = "dependency" - static let skipLinesWithWords = ["unit_tests", "test_spec"] - static let dependencyLineSeparators = CharacterSet(charactersIn: " ,/") - static let podSources = [ - "https://${BOT_TOKEN}@github.com/Firebase/SpecsTesting", - "https://github.com/firebase/SpecsStaging.git", - // https://cdn.cocoapods.org is not used here since `--update-sources` - // will update spec repos before a spec is pushed, but cdn is not a spec - // repo. - "https://github.com/CocoaPods/Specs.git", - ] -} - -// flags for 'pod push' -extension Constants { - static let flags = [ - "--skip-tests", - "--skip-import-validation", - "--update-sources", - ] - static let umbrellaPodFlags = Constants.flags + ["--use-json"] -} - -public extension Date { - func dateTimeString() -> String { - let formatter = DateFormatter() - formatter.dateStyle = .short - formatter.timeStyle = .medium - return formatter.string(from: self) - } - - func formattedDurationSince(_ date: Date) -> String { - let formatter = DateComponentsFormatter() - formatter.unitsStyle = .abbreviated - formatter.allowedUnits = [.hour, .minute, .second] - let secondsSinceDate = date.timeIntervalSince(self) - return formatter.string(from: secondsSinceDate) ?? "\(round(secondsSinceDate)) sec" - } -} - -// SpecFiles is a wrapper of dict mapping from required pods to their path. This -// will also contain a sequence of installing podspecs. -class SpecFiles { - private var specFilesDict: [String: URL] - var depInstallOrder: [String] - var specSource: String - init(_ specDict: [String: URL], from specSourcePath: String) { - specFilesDict = specDict - depInstallOrder = [] - specSource = specSourcePath - } - - func removePod(_ key: String) { - specFilesDict.removeValue(forKey: key) - } - - subscript(key: String) -> URL? { - return specFilesDict[key] - } - - func contains(_ key: String) -> Bool { - return specFilesDict[key] != nil - } - - func isEmpty() -> Bool { - return specFilesDict.isEmpty - } -} - -struct Shell { - static let shared = Shell() - @discardableResult - func run(_ command: String, displayCommand: Bool = true, - displayFailureResult: Bool = true) throws -> Int32 { - let task = Process() - let pipe = Pipe() - task.standardOutput = pipe - task.executableURL = URL(fileURLWithPath: "/bin/zsh") - task.arguments = ["-c", command] - - try task.run() - if displayCommand { - print("[SpecRepoBuilder] Command:\(command)\n") - } - task.waitUntilExit() - let data = pipe.fileHandleForReading.readDataToEndOfFile() - let log = String(data: data, encoding: .utf8)! - if displayFailureResult, task.terminationStatus != 0 { - print("-----Exit code: \(task.terminationStatus)") - print("-----Log:\n \(log)") - } - return task.terminationStatus - } -} - -// Error types -enum SpecRepoBuilderError: Error { - // Error occurs when circular dependencies are detected and deps will be - // displayed. - case circularDependencies(pods: Set) - // Error occurs when there exist specs that failed to push to a spec repo. All - // specs failed to push should be displayed. - case failedToPush(pods: [String]) - // Error occurs when a podspec is not found in the repo. - case podspecNotFound(_ podspec: String, from: String) - // Error occurs when a directory path cannot be determined. - case pathNotFound(_ path: String) -} - -struct SpecRepoBuilder: ParsableCommand { - @Option(help: "The root of the firebase-ios-sdk checked out git repo.") - var sdkRepo: String = FileManager().currentDirectoryPath - - @Option(parsing: .upToNextOption, help: "A list of podspec sources in Podfiles.") - var podSources: [String] = Constants.podSources - - @Option(parsing: .upToNextOption, help: "Podspecs that will not be pushed to repo.") - var excludePods: [String] = [] - - @Option(help: "GitHub Account Name.") - var githubAccount: String = "FirebasePrivate" - - @Option(help: "GitHub Repo Name.") - var sdkRepoName: String = "SpecsTesting" - - @Option(help: "Local Podspec Repo Name.") - var localSpecRepoName: String - - @Option(parsing: .upToNextOption, help: "Push selected podspecs.") - var includePods: [String] = [] - - @Flag(help: "Keep or erase a repo before push.") - var keepRepo: Bool = false - - @Flag(help: "Raise error while circular dependency detected.") - var raiseCircularDepError: Bool = false - - @Flag(help: "Allow warnings when push a spec.") - var allowWarnings: Bool = false - - // This will track down dependencies of pods and keep the sequence of - // dependency installation in specFiles.depInstallOrder. - func generateOrderOfInstallation(pods: [String], specFiles: SpecFiles, - parentDeps: inout Set) { - // pods are dependencies will be tracked down. - // specFiles includes required pods and their URLs. - // parentDeps will record the path of tracking down dependencies to avoid - // duplications and circular dependencies. - // Stop tracking down when the parent pod does not have any required deps. - if pods.isEmpty { - return - } - - for pod in pods { - guard specFiles.contains(pod) else { continue } - let deps = getTargetedDeps(of: pod, from: specFiles) - // parentDeps will have all dependencies the current pod supports. If the - // current pod were in the parent dependencies, that means it was tracked - // before and it is circular dependency. - if parentDeps.contains(pod) { - print("Circular dependency is detected in \(pod) and \(parentDeps)") - if raiseCircularDepError { - Self - .exit(withError: SpecRepoBuilderError - .circularDependencies(pods: parentDeps)) - } - continue - } - // Record the pod as a parent and use depth-first-search to track down - // dependencies of this pod. - parentDeps.insert(pod) - generateOrderOfInstallation( - pods: deps, - specFiles: specFiles, - parentDeps: &parentDeps - ) - // When pod does not have required dep or its required deps are recorded, - // the pod itself will be recorded into the depInstallOrder. - if !specFiles.depInstallOrder.contains(pod) { - print("\(pod) depends on \(deps).") - specFiles.depInstallOrder.append(pod) - } - // When track back from a lower level, parentDep should track back by - // removing one pod. - parentDeps.remove(pod) - } - } - - // Scan a podspec file and find and return all dependencies in this podspec. - func searchDeps(ofPod podName: String, from podSpecFiles: SpecFiles) -> [String] { - var deps: [String] = [] - var fileContents = "" - guard let podSpecURL = podSpecFiles[podName] else { - Self - .exit(withError: SpecRepoBuilderError - .podspecNotFound(podName, from: podSpecFiles.specSource)) - } - do { - fileContents = try String(contentsOfFile: podSpecURL.path, encoding: .utf8) - } catch { - fatalError("Could not read \(podName) podspec from \(podSpecURL.path).") - } - // Get all the lines containing `dependency` but don't contain words we - // want to ignore. - let depLines: [String] = fileContents - .components(separatedBy: .newlines) - .filter { $0.contains("dependency") } - // Skip lines with words in Constants.skipLinesWithWords - .filter { !Constants.skipLinesWithWords.contains(where: $0.contains) - } - for line in depLines { - let newLine = line.trimmingCharacters(in: .whitespacesAndNewlines) - // This is to avoid pushing umbrellapods like Firebase/Core. - let tokens = newLine.components(separatedBy: Constants.dependencyLineSeparators) - if let depPrefix = tokens.first { - if depPrefix.hasSuffix(Constants.specDependencyLabel) { - // e.g. In Firebase.podspec, Firebase/Core will not be considered a - // dependency. - // "ss.dependency 'Firebase/Core'" will be splitted in - // ["ss.dependency", "'Firebase", "Core'"] - let podNameRaw = String(tokens[1]).replacingOccurrences(of: "'", with: "") - // In the example above, deps here will not include Firebase since - // it is the same as the pod name. - if podNameRaw != podName { deps.append(podNameRaw) } - } - } - } - return deps - } - - // Filter and get a list of required dependencies found in the repo. - func filterTargetDeps(_ deps: [String], with targets: SpecFiles) -> [String] { - var targetedDeps: [String] = [] - for dep in deps { - // Only get unique and required dep in the output list. - if targets.contains(dep), !targetedDeps.contains(dep) { - targetedDeps.append(dep) - } - } - return targetedDeps - } - - func getTargetedDeps(of pod: String, from specFiles: SpecFiles) -> [String] { - let deps = searchDeps(ofPod: pod, from: specFiles) - return filterTargetDeps(deps, with: specFiles) - } - - func pushPodspec(forPod pod: URL, sdkRepo: String, sources: [String], - flags: [String], shell: Shell = Shell.shared) throws -> Int32 { - let sourcesArg = sources.joined(separator: ",") - let flagsArgArr = allowWarnings ?flags + ["--allow-warnings"] : flags - let flagsArg = flagsArgArr.joined(separator: " ") - - do { - // Update the repo - try shell.run("pod repo update") - var isDir: ObjCBool = true - let podName = pod.deletingPathExtension().lastPathComponent - let homeDirURL = FileManager.default.homeDirectoryForCurrentUser - let theProjectPath = "\(homeDirURL.path)/.cocoapods/repos/\(localSpecRepoName)/\(podName)" - print("check project path \(theProjectPath)") - if !FileManager.default.fileExists(atPath: theProjectPath, isDirectory: &isDir) { - let outcome = - try shell - .run( - "pod repo push \(localSpecRepoName) \(pod.path) --sources=\(sourcesArg) \(flagsArg)" - ) - try shell.run("pod repo update") - print("Outcome is \(outcome)") - return outcome - } - print("`pod repo push` \(podName) will not run since the repo was uploaded already.") - return 0 - - } catch { - throw error - } - } - - // This will commit and push to erase the entire remote spec repo. - func eraseRemoteRepo(repoPath: String, from githubAccount: String, _ sdkRepoName: String, - shell: Shell = Shell.shared) throws { - do { - try shell - .run( - "git clone --quiet https://${BOT_TOKEN}@github.com/\(githubAccount)/\(sdkRepoName).git" - ) - } catch { - throw error - } - let fileManager = FileManager.default - do { - let sdk_repo_path = "\(repoPath)/\(sdkRepoName)" - print("The repo path is \(sdk_repo_path)") - guard let repo_url = URL(string: sdk_repo_path) else { - print("Error: cannot find \(sdk_repo_path).") - Self - .exit(withError: SpecRepoBuilderError - .pathNotFound(sdk_repo_path)) - } - // Skip hidden files, e.g. /.git - let dirs = try fileManager.contentsOfDirectory( - at: repo_url, - includingPropertiesForKeys: nil, - options: [.skipsHiddenFiles] - ) - print("Found following unhidden dirs: \(dirs)") - for dir in dirs { - guard let isDir = try (dir.resourceValues(forKeys: [.isDirectoryKey])).isDirectory else { - print("Error: cannot determine if \(dir.path) is a directory or not.") - Self - .exit(withError: SpecRepoBuilderError - .pathNotFound(dir.path)) - } - if isDir { - print("Removing \(dir.path)") - try shell.run("cd \(sdkRepoName); git rm -r \(dir.path)") - } - } - do { - try shell.run("cd \(sdkRepoName); git commit -m 'Empty repo'; git push") - } catch { - throw error - } - } catch { - print("Error while enumerating files \(repoPath): \(error.localizedDescription)") - } - do { - try fileManager.removeItem(at: URL(fileURLWithPath: "\(repoPath)/\(sdkRepoName)")) - } catch { - print("Error occurred while removing \(repoPath)/\(sdkRepoName): \(error)") - } - } - - mutating func run() throws { - let fileManager = FileManager.default - let curDir = FileManager().currentDirectoryPath - var podSpecFiles: [String: URL] = [:] - - let documentsURL = URL(fileURLWithPath: sdkRepo) - do { - let fileURLs = try fileManager.contentsOfDirectory( - at: documentsURL, - includingPropertiesForKeys: nil - ) - let podspecURLs = fileURLs - .filter { $0.pathExtension == "podspec" || $0.pathExtension == "json" } - for podspecURL in podspecURLs { - print(podspecURL) - let podName = podspecURL.lastPathComponent.components(separatedBy: ".")[0] - print("Podspec, \(podName), is detected.") - if excludePods.contains(podName) { - continue - } - podSpecFiles[podName] = podspecURL - } - } catch { - print( - "Error while enumerating files \(documentsURL.path): \(error.localizedDescription)" - ) - throw error - } - - // This set is used to keep parent dependencies and help detect circular - // dependencies. - var tmpSet: Set = [] - print("Detect podspecs: \(podSpecFiles.keys)") - let specFileDict = SpecFiles(podSpecFiles, from: sdkRepo) - generateOrderOfInstallation( - pods: includePods.isEmpty ? Array(podSpecFiles.keys) : includePods, - specFiles: specFileDict, - parentDeps: &tmpSet - ) - print("Podspec push order:\n", specFileDict.depInstallOrder.joined(separator: "->\t")) - - if !keepRepo { - do { - if fileManager.fileExists(atPath: "\(curDir)/\(sdkRepoName)") { - print("remove \(sdkRepoName) dir.") - try fileManager.removeItem(at: URL(fileURLWithPath: "\(curDir)/\(sdkRepoName)")) - } - try eraseRemoteRepo(repoPath: "\(curDir)", from: githubAccount, sdkRepoName) - - } catch { - print("error occurred. \(error)") - throw error - } - } - - var exitCode: Int32 = 0 - var failedPods: [String] = [] - let startDate = Date() - var minutes = 0 - for pod in specFileDict.depInstallOrder { - print("----------\(pod)-----------") - let timer: DispatchSourceTimer = { - let t = DispatchSource.makeTimerSource() - t.schedule(deadline: .now(), repeating: 60) - t.setEventHandler(handler: { - print("Tests have run \(minutes) min(s).") - minutes += 1 - }) - return t - }() - timer.resume() - var podExitCode: Int32 = 0 - do { - guard let podURL = specFileDict[pod] else { - Self - .exit(withError: SpecRepoBuilderError - .podspecNotFound(pod, from: sdkRepo)) - } - switch pod { - case "Firebase": - podExitCode = try pushPodspec( - forPod: podURL, - sdkRepo: sdkRepo, - sources: podSources, - flags: Constants.umbrellaPodFlags - ) - default: - podExitCode = try pushPodspec( - forPod: podURL, - sdkRepo: sdkRepo, - sources: podSources, - flags: Constants.flags - ) - } - if podExitCode != 0 { - exitCode = 1 - failedPods.append(pod) - print("Failed pod - \(pod)") - } - } catch { - throw error - } - timer.cancel() - let finishDate = Date() - print("\(pod) is finished at: \(finishDate.dateTimeString()). " + - "Duration: \(startDate.formattedDurationSince(finishDate))") - } - if exitCode != 0 { - Self.exit(withError: SpecRepoBuilderError.failedToPush(pods: failedPods)) - } - } -} - -SpecRepoBuilder.main() diff --git a/scripts/create_spec_repo/firebase_sdk.textproto b/scripts/create_spec_repo/firebase_sdk.textproto deleted file mode 100644 index 543d413..0000000 --- a/scripts/create_spec_repo/firebase_sdk.textproto +++ /dev/null @@ -1,9 +0,0 @@ -# Config file to update versions of Pods which will be pushed to SpecsStaging -# repo by add_podspec tool. -# e.g. -# sdk { -# sdk_name: "FirebaseAnalytics" -# sdk_version: "6.8.2" -# } -# Podspec versions will be the same as firebase-ios-sdk podspecs if they are not -# specified in this file. diff --git a/scripts/decrypt_gha_secret.sh b/scripts/decrypt_gha_secret.sh deleted file mode 100755 index 2b1e0fb..0000000 --- a/scripts/decrypt_gha_secret.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -# Copyright 2020 Google LLC -# -# 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. - -# $1 is the file to decrypt -# $2 is the output file -# $3 is the passphrase - -# Decrypt the file -# --batch to prevent interactive command --yes to assume "yes" for questions - -file="$1" -output="$2" -passphrase="$3" -[ -z "$passphrase" ] || \ - gpg --quiet --batch --yes --decrypt --passphrase="$passphrase" --output $output "$file" diff --git a/scripts/encrypt_gha_secret.sh b/scripts/encrypt_gha_secret.sh deleted file mode 100755 index 6c4e21c..0000000 --- a/scripts/encrypt_gha_secret.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash - -# Copyright 2021 Google LLC -# -# 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. - -# $1 is the file to encrypt -# $2 is the passphrase - -# Encrypt the file -# See https://docs.github.com/en/actions/reference/encrypted-secrets for more details. -# --batch to prevent interactive command - -file="$1" -passphrase="$2" -[ -z "$passphrase" ] || \ - gpg --batch --passphrase="$passphrase" --symmetric --cipher-algo AES256 $file diff --git a/scripts/fuzzing_ci.sh b/scripts/fuzzing_ci.sh deleted file mode 100755 index e19d84b..0000000 --- a/scripts/fuzzing_ci.sh +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/env bash -## -# Copyright 2018 Google -# -# 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. - -# Run fuzz testing using xcodebuild. Given a total fuzzing duration, select -# a fuzzing target to run for half of the fuzzing duration. The remaining -# duration is split equally over the rest of the fuzzing targets. Must be run -# from the project root directory. The script is intended to execute on travis -# cron jobs, but can run locally (on a macOS) from the project root. - -# Total time allowed for fuzzing in seconds (25 minutes for now). -readonly ALLOWED_TIME=1500 - -# An invalid target that is used to retrieve the list of available targets in -# the returned error message. -readonly INVALID_TARGET="INVALID_TARGET" -# The NONE target that does not perform fuzzing. It is valid but useless. -readonly NONE_TARGET="NONE" - -# Script exit codes. -readonly EXIT_SUCCESS=0 -readonly EXIT_FAILURE=1 - -# Get day of the year to use it when selecting a main target. -readonly DOY="$(date +%j)" - -# How many last lines of the log to output for a failing job. This is to avoid -# exceeding Travis log size limit (4 Mb). -readonly LINES_TO_OUTPUT=500 - -# Run fuzz testing only if executing on xcode >= 9 because fuzzing requires -# Clang that supports -fsanitize-coverage=trace-pc-guard. -xcode_version="$(xcodebuild -version | head -n 1)" -xcode_version="${xcode_version/Xcode /}" -xcode_major="${xcode_version/.*/}" - -if [[ "${xcode_major}" -lt 9 ]]; then - echo "Unsupported version of xcode." - exit ${EXIT_SUCCESS} -fi - -# Helper to run fuzz tests using xcodebuild. Takes the fuzzing target as the -# first argument and the fuzzing duration as the second argument. -function run_xcode_fuzzing() { - xcodebuild \ - -workspace 'Firestore/Example/Firestore.xcworkspace' \ - -scheme 'Firestore_FuzzTests_iOS' \ - -sdk 'iphonesimulator' \ - -destination 'platform=iOS Simulator,name=iPhone 7' \ - FUZZING_TARGET="$1" \ - FUZZING_DURATION="$2" \ - test -} - -# Run fuzz testing with an INVALID_TARGET and grep the response message line -# that contains the string 'Available targets: {'. -fuzzing_targets_line="$(run_xcode_fuzzing ${INVALID_TARGET} "0" \ - | grep "Available targets: {")" - -# The fuzzing_targets_line string contains { TARGET1 TARGET2 ... TARGETN }. -# The following command extracts the targets and splits them into an array -# in the variable all_fuzzing_targets. -read -r -a all_fuzzing_targets <<< "$(echo ${fuzzing_targets_line} \ - | cut -d "{" -f2 | cut -d "}" -f1)" - -# Remove the NONE target, if it exists. -for i in "${!all_fuzzing_targets[@]}"; do - if [[ "${all_fuzzing_targets[${i}]}" == ${NONE_TARGET} ]]; then - unset all_fuzzing_targets[${i}] - fi -done - -# Count all targets. -readonly fuzzing_targets_count="${#all_fuzzing_targets[@]}" - -# Select a primary target to fuzz test for longer. Changes daily. The variable -# todays_primary_target contains the index of the target selected as primary. -todays_primary_target="$(( ${DOY} % ${fuzzing_targets_count} ))" - -# Spend half of allowed time on the selected primary target and half on the -# remaining targets. -primary_target_time="$(( ${ALLOWED_TIME} / 2 ))" -other_targets_time="$(( ${ALLOWED_TIME} / 2 / $(( ${fuzzing_targets_count} - 1)) ))" - -script_return=${EXIT_SUCCESS} -for i in "${!all_fuzzing_targets[@]}"; do - fuzzing_duration="${other_targets_time}" - if [[ "${i}" -eq "${todays_primary_target}" ]]; then - fuzzing_duration="${primary_target_time}" - fi - fuzzing_target="${all_fuzzing_targets[${i}]}" - - echo "Running fuzzing target ${fuzzing_target} for ${fuzzing_duration} seconds..." - fuzzing_results="$(run_xcode_fuzzing "${fuzzing_target}" "${fuzzing_duration}")" - # Get the last line of the fuzzing results. - fuzzing_results_last_line="$(echo "${fuzzing_results}" | tail -n1)" - # If fuzzing succeeded, the last line will start with "Done"; otherwise, - # print all fuzzing results. - if [[ "${fuzzing_results_last_line}" == Done* ]]; then - echo "Fuzz testing for target ${fuzzing_target} succeeded. Ignore the ** TEST FAILED ** message." - else - echo "Error: Fuzz testing for target ${fuzzing_target} failed." - script_return=${EXIT_FAILURE} - echo "Fuzzing logs:" - echo "${fuzzing_results}" | tail -n${LINES_TO_OUTPUT} - echo "End fuzzing logs" - fi -done - -exit ${script_return} diff --git a/scripts/generate_access_token.sh b/scripts/generate_access_token.sh deleted file mode 100755 index 3943b53..0000000 --- a/scripts/generate_access_token.sh +++ /dev/null @@ -1,93 +0,0 @@ -#!/bin/bash - -# Copyright 2020 Google LLC -# -# 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. - -# This script generates access tokens that are needed to make admin API -# calls to the Firebase Console. -# -# The script takes three arguments: -# - GHA_SECRET: The password for decrypting GitHub secrets. -# - SERVICE_ACCOUNT: The path to the encrypted service account secret. -# - OUTPUT: The path to file where generated access token will be stored. -# -# This script uses Google's Swift Auth Client Library. -# - https://github.com/googleapis/google-auth-library-swift -# -# Generated tokens are `JSON` in the form: -# { -# "token_type":"Bearer", -# "expires_in":3599, -# "access_token":"1234567890ABCDEFG" -# } - -GHA_SECRET="$1" # Pass in `local_dev` if the SERVICE_ACCOUNT does not to be decrypted. -SERVICE_ACCOUNT="$2" -OUTPUT="$3" - -echo "GHA_SECRET: ***" -echo "SERVICE_ACCOUNT: ${SERVICE_ACCOUNT}" -echo "OUTPUT: ${OUTPUT}" - -if [[ ! -f $SERVICE_ACCOUNT ]]; then - echo ERROR: Cannot find encrypted secret at $SERVICE_ACCOUNT, aborting. - exit 1 -fi - -if [[ ! -f $OUTPUT ]]; then - echo ERROR: Cannot find $OUTPUT, aborting. - exit 1 -fi - -# The access token is generated using a downloaded Service Account JSON file from a -# Firebase Project. This can be downloaded from Firebase console under 'Project Settings'. -# -# The following stores the decrypted service account JSON file in `$HOME/.credentials/` and points -# the GOOGLE_APPLICATION_CREDENTIALS env var to it. -SERVICE_ACCOUNT_FILE=$(basename $SERVICE_ACCOUNT .gpg) - -echo "Creating ~/.credentials/ directory" -mkdir -p ~/.credentials/ - -if [[ $GHA_SECRET == "local_dev" ]]; then - echo "Local Development Mode" - echo "Copying ${SERVICE_ACCOUNT_FILE} to ~/.credentials/" - cp $SERVICE_ACCOUNT ~/.credentials/ -else - echo "Decrypting ${SERVICE_ACCOUNT_FILE}.gpg" - scripts/decrypt_gha_secret.sh $SERVICE_ACCOUNT ~/.credentials/$SERVICE_ACCOUNT_FILE "$GHA_SECRET" -fi - -echo "GOOGLE_APPLICATION_CREDENTIALS=${HOME}/.credentials/${SERVICE_ACCOUNT_FILE}" >> $GITHUB_ENV -export GOOGLE_APPLICATION_CREDENTIALS="${HOME}/.credentials/${SERVICE_ACCOUNT_FILE}" - -# Clone Google's Swift Auth Client Library and use it to generate a token. -# The generated token is piped to the specified OUTPUT file. -git clone https://github.com/googleapis/google-auth-library-swift.git -cd google-auth-library-swift -make -f Makefile - -# Prepend OUTPUT path with ../ since we cd'd into `google-auth-library-swift`. -swift run TokenSource > ../$OUTPUT - -# Remove cloned Swift Auth Client Library. -cd .. -rm -rf google-auth-library-swift - -if grep -q "access_token" $OUTPUT; then - echo "Token successfully generated and placed at ${OUTPUT}" -else - echo "ERROR: $(cat $OUTPUT)" - exit 1 -fi diff --git a/scripts/gha-encrypted/AuthCredentials.h.gpg b/scripts/gha-encrypted/AuthCredentials.h.gpg deleted file mode 100644 index a6c6601..0000000 Binary files a/scripts/gha-encrypted/AuthCredentials.h.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/AuthSample/Application.plist.gpg b/scripts/gha-encrypted/AuthSample/Application.plist.gpg deleted file mode 100644 index 17f2b77..0000000 Binary files a/scripts/gha-encrypted/AuthSample/Application.plist.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/AuthSample/AuthCredentials.h.gpg b/scripts/gha-encrypted/AuthSample/AuthCredentials.h.gpg deleted file mode 100644 index eab6b42..0000000 Binary files a/scripts/gha-encrypted/AuthSample/AuthCredentials.h.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/AuthSample/Credentials.swift.gpg b/scripts/gha-encrypted/AuthSample/Credentials.swift.gpg deleted file mode 100644 index af09f7c..0000000 Binary files a/scripts/gha-encrypted/AuthSample/Credentials.swift.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/AuthSample/GoogleService-Info.plist.gpg b/scripts/gha-encrypted/AuthSample/GoogleService-Info.plist.gpg deleted file mode 100644 index 2d59f0a..0000000 Binary files a/scripts/gha-encrypted/AuthSample/GoogleService-Info.plist.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/AuthSample/GoogleService-Info_multi.plist.gpg b/scripts/gha-encrypted/AuthSample/GoogleService-Info_multi.plist.gpg deleted file mode 100644 index 9849f0c..0000000 Binary files a/scripts/gha-encrypted/AuthSample/GoogleService-Info_multi.plist.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/AuthSample/Sample.entitlements.gpg b/scripts/gha-encrypted/AuthSample/Sample.entitlements.gpg deleted file mode 100644 index deecafd..0000000 Binary files a/scripts/gha-encrypted/AuthSample/Sample.entitlements.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/FIREGSignInInfo.h.gpg b/scripts/gha-encrypted/FIREGSignInInfo.h.gpg deleted file mode 100644 index 17e6526..0000000 Binary files a/scripts/gha-encrypted/FIREGSignInInfo.h.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/Installations/GoogleService-Info.plist.gpg b/scripts/gha-encrypted/Installations/GoogleService-Info.plist.gpg deleted file mode 100644 index 8075963..0000000 Binary files a/scripts/gha-encrypted/Installations/GoogleService-Info.plist.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/MLModelDownloader/GoogleService-Info.plist.gpg b/scripts/gha-encrypted/MLModelDownloader/GoogleService-Info.plist.gpg deleted file mode 100644 index 7f8d5b4..0000000 Binary files a/scripts/gha-encrypted/MLModelDownloader/GoogleService-Info.plist.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/Performance/GoogleService-Info_e2e_autopush.plist.gpg b/scripts/gha-encrypted/Performance/GoogleService-Info_e2e_autopush.plist.gpg deleted file mode 100644 index ad7dcb4..0000000 Binary files a/scripts/gha-encrypted/Performance/GoogleService-Info_e2e_autopush.plist.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/Performance/GoogleService-Info_e2e_prod.plist.gpg b/scripts/gha-encrypted/Performance/GoogleService-Info_e2e_prod.plist.gpg deleted file mode 100644 index 84194ef..0000000 Binary files a/scripts/gha-encrypted/Performance/GoogleService-Info_e2e_prod.plist.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/README b/scripts/gha-encrypted/README deleted file mode 100644 index e85c40a..0000000 --- a/scripts/gha-encrypted/README +++ /dev/null @@ -1,3 +0,0 @@ -See https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets - -Googlers: code search firebase/ios/Secrets to find the sources. diff --git a/scripts/gha-encrypted/RemoteConfigSwiftAPI/GoogleService-Info.plist.gpg b/scripts/gha-encrypted/RemoteConfigSwiftAPI/GoogleService-Info.plist.gpg deleted file mode 100644 index daa0944..0000000 Binary files a/scripts/gha-encrypted/RemoteConfigSwiftAPI/GoogleService-Info.plist.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/RemoteConfigSwiftAPI/ServiceAccount.json.gpg b/scripts/gha-encrypted/RemoteConfigSwiftAPI/ServiceAccount.json.gpg deleted file mode 100644 index f316d3e..0000000 Binary files a/scripts/gha-encrypted/RemoteConfigSwiftAPI/ServiceAccount.json.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/Sessions/GoogleService-Info_TestApp.plist.gpg b/scripts/gha-encrypted/Sessions/GoogleService-Info_TestApp.plist.gpg deleted file mode 100644 index e2a8238..0000000 --- a/scripts/gha-encrypted/Sessions/GoogleService-Info_TestApp.plist.gpg +++ /dev/null @@ -1,6 +0,0 @@ -  q:42G6M_VA"7]p m6]0ʽcÐ7Ɔ7 -1BULc#W<7s? žDMif#{އ482($!3|ظ@jt2zXhd+@N y -eDa4,MnWcӛ˙fJ+p+Cx2 Wg 'd^^JO:hWF'iׄw\dƩmXJ)@7*Z, D/NgPr -OV ` @'[t_J= PGaTLN(dM(A5R aRj k"1nhb@6{8Sn2\v&} ,qsgGQ -&'̗km^ -9 Ab};4qM3x$_x>QB8҄R*-hz'RMO;x$ \ No newline at end of file diff --git a/scripts/gha-encrypted/Storage/Credentials.h.gpg b/scripts/gha-encrypted/Storage/Credentials.h.gpg deleted file mode 100644 index 07c9d01..0000000 Binary files a/scripts/gha-encrypted/Storage/Credentials.h.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/Storage/Credentials.swift.gpg b/scripts/gha-encrypted/Storage/Credentials.swift.gpg deleted file mode 100644 index 72c302f..0000000 --- a/scripts/gha-encrypted/Storage/Credentials.swift.gpg +++ /dev/null @@ -1,3 +0,0 @@ -  wU7}pHl`@1aˢӖ`m/_8 g#% ->1X 'She K)Ȳլ$L<ϡwgbCVKlsPz`rp]}G(c?$paƛm{"'# 5*]rD9\&󟦉[oAi1@N>`ESCY[$Gw_2 :x0]+2k_鬱g^Xs #TKEIi= 4ԿL&xKЅDFbHA`5>ê3ЬoX02Fvͺ!NL. Sŵ5DAHǺ˦ -E.KThM;Hd Dj-(cO8fg3Mk]dv"$J(Tq:hfalRLt>?EkXtoI~Ĩ9BGr#刽~Ox|5ׯyAɀG \ No newline at end of file diff --git a/scripts/gha-encrypted/bot-access.txt.gpg b/scripts/gha-encrypted/bot-access.txt.gpg deleted file mode 100644 index c1a3c87..0000000 --- a/scripts/gha-encrypted/bot-access.txt.gpg +++ /dev/null @@ -1,2 +0,0 @@ -  x)gl^Q؋']4' Sz %DwV.no'Rg -ʪNy!`LºѸb}lA_,5NDa-%0AV4 Wvy \ No newline at end of file diff --git a/scripts/gha-encrypted/firebase-ios-testing.json.gpg b/scripts/gha-encrypted/firebase-ios-testing.json.gpg deleted file mode 100644 index 650aea2..0000000 Binary files a/scripts/gha-encrypted/firebase-ios-testing.json.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/firestore-integration.json.gpg b/scripts/gha-encrypted/firestore-integration.json.gpg deleted file mode 100644 index 41a66e1..0000000 Binary files a/scripts/gha-encrypted/firestore-integration.json.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/firestore-nightly.plist.gpg b/scripts/gha-encrypted/firestore-nightly.plist.gpg deleted file mode 100644 index 7c86456..0000000 Binary files a/scripts/gha-encrypted/firestore-nightly.plist.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/firestore.plist.gpg b/scripts/gha-encrypted/firestore.plist.gpg deleted file mode 100644 index db5ff61..0000000 Binary files a/scripts/gha-encrypted/firestore.plist.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/messaging-sample-plist.gpg b/scripts/gha-encrypted/messaging-sample-plist.gpg deleted file mode 100644 index c5259f6..0000000 Binary files a/scripts/gha-encrypted/messaging-sample-plist.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/metrics_service_access.json.gpg b/scripts/gha-encrypted/metrics_service_access.json.gpg deleted file mode 100644 index d7f7a88..0000000 Binary files a/scripts/gha-encrypted/metrics_service_access.json.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/oss-bot-access.txt.gpg b/scripts/gha-encrypted/oss-bot-access.txt.gpg deleted file mode 100644 index f2b3154..0000000 --- a/scripts/gha-encrypted/oss-bot-access.txt.gpg +++ /dev/null @@ -1,2 +0,0 @@ -  nphcXҖ1tP܎@jvd ;$# -av[Xb們I N%t\1 ]j^`f‡< \ No newline at end of file diff --git a/scripts/gha-encrypted/qs-abtesting.plist.gpg b/scripts/gha-encrypted/qs-abtesting.plist.gpg deleted file mode 100644 index 8273a32..0000000 Binary files a/scripts/gha-encrypted/qs-abtesting.plist.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/qs-auth.plist.gpg b/scripts/gha-encrypted/qs-auth.plist.gpg deleted file mode 100644 index 2af1325..0000000 Binary files a/scripts/gha-encrypted/qs-auth.plist.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/qs-config.plist.gpg b/scripts/gha-encrypted/qs-config.plist.gpg deleted file mode 100644 index 977b696..0000000 Binary files a/scripts/gha-encrypted/qs-config.plist.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/qs-crashlytics.plist.gpg b/scripts/gha-encrypted/qs-crashlytics.plist.gpg deleted file mode 100644 index a1e538e..0000000 Binary files a/scripts/gha-encrypted/qs-crashlytics.plist.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/qs-database.plist.gpg b/scripts/gha-encrypted/qs-database.plist.gpg deleted file mode 100644 index 424f186..0000000 Binary files a/scripts/gha-encrypted/qs-database.plist.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/qs-dynamiclinks.plist.gpg b/scripts/gha-encrypted/qs-dynamiclinks.plist.gpg deleted file mode 100644 index bcabfa3..0000000 Binary files a/scripts/gha-encrypted/qs-dynamiclinks.plist.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/qs-firestore.plist.gpg b/scripts/gha-encrypted/qs-firestore.plist.gpg deleted file mode 100644 index df0c81f..0000000 --- a/scripts/gha-encrypted/qs-firestore.plist.gpg +++ /dev/null @@ -1,5 +0,0 @@ -  $Uh&xE;ث顜4ެ6 nnX9>0sWg> W5n^U ,= DTl"%( .$E7Წf9PWpf -Pڨ[6kC96wQ{Ν -V^-Iȑ-rعg '8۔FOf;:&RcWػ>)\i9= åxhrn cៈ>Rjw[@׍IN۳<.mS#*J -N`9Kt:WY#,ҍlh/7rS/[sɶo1~+.1(G<}Ul:;zܔ \ No newline at end of file diff --git a/scripts/gha-encrypted/qs-functions.plist.gpg b/scripts/gha-encrypted/qs-functions.plist.gpg deleted file mode 100644 index e3bdb8c..0000000 Binary files a/scripts/gha-encrypted/qs-functions.plist.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/qs-inappmessaging.plist.gpg b/scripts/gha-encrypted/qs-inappmessaging.plist.gpg deleted file mode 100644 index bd5ecad..0000000 Binary files a/scripts/gha-encrypted/qs-inappmessaging.plist.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/qs-messaging.plist.gpg b/scripts/gha-encrypted/qs-messaging.plist.gpg deleted file mode 100644 index f6208d1..0000000 --- a/scripts/gha-encrypted/qs-messaging.plist.gpg +++ /dev/null @@ -1,3 +0,0 @@ -   ónlWr I|nXklgLgP)x].;_[ubs&~2l7T>Džf1Q]C IIai!ɡh) ("c C3=*)P!ZrALv@GޭE\:kQ,گnMd~B"/N9<gi9q!j`to^Vu#<,ϕ z7vO@m -b)3')n?`\TG.]RRPLjٓwHZ0ړQP|fB""Dze1jƒ79r*a/5zW#xgk{%QiT I#W%@֪Ws9[JjJf~0L/SoGKu~AR{Ьr}9ɂ% s˵9'RB(|yN -r-u>s_ǁX2S) X/(j8Od/E h l-*Z3W4wN p|L[/y2F`>kDOTz0//#=Nl؛N{Lgz#d1BGOE:uq+k/XZͯq( -}uM|\NDV$d \ No newline at end of file diff --git a/scripts/gha-encrypted/qs-performance.plist.gpg b/scripts/gha-encrypted/qs-performance.plist.gpg deleted file mode 100644 index c889ad9..0000000 --- a/scripts/gha-encrypted/qs-performance.plist.gpg +++ /dev/null @@ -1,2 +0,0 @@ -  e/%D%ߧL#BIϟz)̮?UۏwR45=P7ImM t;T2_|Cn\PQϳ 3&1L\,AZZZrc΃ѹy$)&Q2 r7949OD7VpG5 4,8J"En$=_c;)X)ATS#фm)hÃ%\Gf~.be.ϊiZ+))#Y1ַ`b Y*Iɓ.)%BJ m1q ;x^yZ Ok*7^u{ŷpΊJkPJJ`mWJ}UdbJ0iFL -If`5ڱ&N_K3&UN7DrTqܼn^@;%5ݍ,"Y+]P9?6(BNsi'g/ \ No newline at end of file diff --git a/scripts/gha-encrypted/qs-storage.plist.gpg b/scripts/gha-encrypted/qs-storage.plist.gpg deleted file mode 100644 index acdbae8..0000000 Binary files a/scripts/gha-encrypted/qs-storage.plist.gpg and /dev/null differ diff --git a/scripts/gha-encrypted/storage-db-plist.gpg b/scripts/gha-encrypted/storage-db-plist.gpg deleted file mode 100644 index 30a5163..0000000 Binary files a/scripts/gha-encrypted/storage-db-plist.gpg and /dev/null differ diff --git a/scripts/health_metrics/Gemfile b/scripts/health_metrics/Gemfile deleted file mode 100644 index c1bc864..0000000 --- a/scripts/health_metrics/Gemfile +++ /dev/null @@ -1,3 +0,0 @@ -source "https://rubygems.org" - -gem "octokit", "~> 4.19" diff --git a/scripts/health_metrics/README.md b/scripts/health_metrics/README.md deleted file mode 100644 index 829cfea..0000000 --- a/scripts/health_metrics/README.md +++ /dev/null @@ -1,77 +0,0 @@ -# SDK Health Metrics Report Generation -This directory includes code coverage report generation and binary size report generation. - -## Code Coverage Report Generation - -This tool is to help generate coverage reports for pull requests. It's defined by the [test_coverage workflow](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/health_metrics/file_patterns.json). - -Coverage reports of SDK frameworks will be displayed in a pull request if the change is under corresponding SDK file patterns. - -[UpdatedFilesCollector](https://github.com/firebase/firebase-ios-sdk/tree/main/scripts/health_metrics/generate_code_coverage_report/Sources/UpdatedFilesCollector) will detect file changes and compare file paths to file patterns in [file_patterns.json](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/health_metrics/file_patterns.json). If updated file paths fit any patterns, corresponding SDK coverage job will be triggered. - -### Add a new coverage workflow - -To create a code coverage workflow for a new SDK, -1. Add `newsdk` and its patterns in [file_patterns.json](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/health_metrics/file_patterns.json). -2. Add a new output flag, e.g. `newsdk_run_job`, in the [coverage workflow](https://github.com/firebase/firebase-ios-sdk/blob/64d50a7f7b3af104a88f9c9203285ae20ea309d4/.github/workflows/test_coverage.yml#L17). `newsdk_run_job` should be aligned with the name of SDK `newsdk` in file_patterns.json. -3. Add a newsdk coverage job in the [workflow](https://github.com/firebase/firebase-ios-sdk/blob/main/.github/workflows/test_coverage.yml): -``` -pod-lib-lint-newsdk: - needs: check - # Don't run on private repo unless it is a PR. - if: always() && github.repository == 'Firebase/firebase-ios-sdk' && (needs.check.outputs.newsdk_run_job == 'true'|| github.event.pull_request.merged) - runs-on: macOS-latest - strategy: - matrix: - target: [iOS] - steps: - - uses: actions/checkout@v3 - - uses: ruby/setup-ruby@359bebbc29cbe6c87da6bc9ea3bc930432750108 - with: - ruby-version: '2.7' - - name: Setup Bundler - run: scripts/setup_bundler.sh - - name: Build and test - run: ./scripts/health_metrics/pod_test_code_coverage_report.sh FirebaseNewSDK "${{ matrix.target }}" - - uses: actions/upload-artifact@v3 - with: - name: codecoverage - path: /Users/runner/*.xcresult -``` -Add the job name to the [`needs` of `create_report` job](https://github.com/firebase/firebase-ios-sdk/blob/64d50a7f7b3af104a88f9c9203285ae20ea309d4/.github/workflows/test_coverage.yml#L277). - -4. If this newsdk podspec has unit test setup, e.g. [database](https://github.com/firebase/firebase-ios-sdk/blob/64d50a7f7b3af104a88f9c9203285ae20ea309d4/FirebaseDatabase.podspec#L44-L57), with `unit_tests.scheme = { :code_coverage => true }`, the code coverage workflow should run unit tests through podspecs and utilize those coverage data and nothing is needed to update here. Otherwise, add [another way of running tests](https://github.com/firebase/firebase-ios-sdk/blob/64d50a7f7b3af104a88f9c9203285ae20ea309d4/scripts/health_metrics/pod_test_code_coverage_report.sh#L26) and generating xcresult bundles with code coverage data in pod_test_code_coverage_report.sh. - -## Binary Size Report Generation - -This tool is to generate a report for SDK binary sizes. -[create_binary_size_report.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/health_metrics/create_binary_size_report.sh) -will trigger [BinarySizeReportGenerator](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/health_metrics/generate_code_coverage_report/Sources/BinarySizeReportGenerator/BinarySizeReportGeneration.swift) -which will run [cocoapods-size](https://github.com/google/cocoapods-size) -to create a binary size report and either post it in PRs in presubmits or -upload it to the Metrics Service. - -The cocoapods-size tool measures an iOS SDK size by comparing the [64 bit ARM](https://github.com/google/cocoapods-size/blob/master/size_build_configuration.json#L5) slice -before and after installing the pod to an [iOS skeleton app](https://github.com/google/cocoapods-size/tree/master/sizetestproject). -More about CocoaPods Size measurement could be found [here](https://github.com/google/cocoapods-size#cocoapods-size-measurement). - -### Replicate a binary size measurement for a released SDK - -To replicate an SDK binary size measurement locally, we can run the following -script to get the binary size of the latest released FirebaseABTesting SDK. -``` -git clone https://github.com/google/cocoapods-size -cd cocoapods-size -python3 measure_cocoapod_size.py --cocoapods FirebaseABTesting -``` - -### Environment Setup - -The `binary_size_metrics` on GitHub Actions [workflow](https://github.com/firebase/firebase-ios-sdk/blob/main/.github/workflows/health-metrics-presubmit.yml) -is running binary size measurement presubmits and postsubmits. The environment for this job -could be found in `runs-on`. More details about the envionment could be found [here](https://github.com/actions/virtual-environments#available-environments). - -Factors below might also affect the binary size: -- Each testapp is measured by a default version Xcode from the GitHub Actions environment above. -- Architecture is set to `arm64` and the destination is `generic/platform=iOS Simulator` when a testapp is built. -- The source of dependencies is the [SpecStaging repo](https://github.com/firebase/SpecsStaging), which is for prerelease, and [CocoaPods CDN](https://cdn.cocoapods.org). diff --git a/scripts/health_metrics/file_patterns.json b/scripts/health_metrics/file_patterns.json deleted file mode 100644 index cb56eb0..0000000 --- a/scripts/health_metrics/file_patterns.json +++ /dev/null @@ -1,187 +0,0 @@ -[ - { - "sdk": "abtesting", - "podspecs": ["FirebaseABTesting.podspec"], - "filePatterns": [ - "^FirebaseABTesting.*", - "Interop/Analytics/Public/[^/]+\\.h", - "\\.github/workflows/abtesting\\.yml" - ] - }, - { - "sdk": "analytics", - "podspecs": ["FirebaseAnalytics.podspec", "FirebaseAnalyticsSwift.podspec", "GoogleAppMeasurement.podspec"], - "filePatterns": [ - "^FirebaseAnalytics.*", - "^GoogleAppMeasurement.*" - ] - }, - { - "sdk": "appcheck", - "podspecs": ["FirebaseAppCheck.podspec"], - "filePatterns": [ - "^FirebaseAppCheck.*", - "\\.github/workflows/app_check\\.yml" - ] - }, - { - "sdk": "appdistribution", - "podspecs": ["FirebaseAppDistribution.podspec"], - "filePatterns": [ - "^FirebaseAppDistribution.*" - ] - }, - { - "sdk": "auth", - "podspecs": ["FirebaseAuth.podspec"], - "filePatterns": [ - "^FirebaseAuth.*", - "FirebaseAuth/Interop/[^/]+\\.h", - "\\.github/workflows/auth\\.yml" - ] - }, - { - "sdk": "core", - "podspecs": ["FirebaseCore.podspec"], - "filePatterns": [ - "^FirebaseCore.*", - "FirebaseCore.podspec" - ] - }, - { - "sdk": "crashlytics", - "podspecs": ["FirebaseCrashlytics.podspec"], - "filePatterns": [ - "^Crashlytics.*", - "FirebaseCrashlytics.podspec" - ] - }, - { - "sdk": "database", - "podspecs": ["FirebaseDatabase.podspec", "FirebaseDatabaseSwift.podspec"], - "filePatterns": [ - "^FirebaseDatabase.*", - "\\.github/workflows/database\\.yml", - "Example/Database/", - "FirebaseAuth/Interop/[^/]+\\.h" - ] - }, - { - "sdk": "dynamiclinks", - "podspecs": ["FirebaseDynamicLinks.podspec"], - "filePatterns": [ - "^FirebaseDynamicLinks.*", - "\\.github/workflows/dynamiclinks\\.yml", - "Interop/Analytics/Public/[^/]+\\.h" - ] - }, - { - "sdk": "firebase", - "podspecs": ["Firebase.podspec"], - "filePatterns": [ - ".*.podspec", - "CoreOnly/.*" - ] - }, - { - "sdk": "firestore", - "podspecs": ["FirebaseFirestore.podspec", "FirebaseFirestoreSwift.podspec"], - "filePatterns": [ - "^Firestore/.*", - "FirebaseAppCheck/Interop/[^/]+\\.h", - "FirebaseAuth/Interop/[^/]+\\.h", - "FirebaseCore/Internal", - "FirebaseCore/Sources/Public", - "FirebaseFirestore\\.podspec", - "CMakeLists\\.txt", - "cmake/.*", - "\\.github/workflows/firestore\\.yml" - ] - }, - { - "sdk": "functions", - "podspecs": ["FirebaseFunctions.podspec"], - "filePatterns": [ - "^Functions.*", - "\\.github/workflows/functions\\.yml", - "FirebaseAuth/Interop/.*\\.h", - "FirebaseMessaging/Interop/[^/]+\\.h" - ] - }, - { - "sdk": "google-utilities-components", - "podspecs": ["GoogleUtilitiesComponents.podspec"], - "filePatterns": [ - "^GoogleUtilitiesComponents.*" - ] - }, - { - "sdk": "inappmessaging", - "podspecs": ["FirebaseInAppMessaging.podspec", "FirebaseInAppMessagingSwift.podspec"], - "filePatterns": [ - "^FirebaseInAppMessaging.*", - "Interop/Analytics/Public/[^/]+\\.h", - "\\.github/workflows/inappmessaging\\.yml" - ] - }, - { - "sdk": "installations", - "podspecs": ["FirebaseInstallations.podspec"], - "filePatterns": [ - "^FirebaseInstallations.*" - ] - }, - { - "sdk": "messaging", - "podspecs": ["FirebaseMessaging.podspec"], - "filePatterns": [ - "^FirebaseMessaging/.*", - "Interop/Analytics/Public/[^/]+\\.h", - "FirebaseMessaging\\.podspec", - "\\.github/workflows/messaging\\.yml" - ] - }, - { - "sdk": "mlmodeldownloader", - "podspecs": ["FirebaseMLModelDownloader.podspec"], - "filePatterns": [ - "^FirebaseMLModelDownloader/.*", - "FirebaseMLModelDownloader\\.podspec" - ] - }, - { - "sdk": "performance", - "podspecs": ["FirebasePerformance.podspec"], - "filePatterns": [ - "^FirebasePerformance/.*", - "FirebasePerformance\\.podspec", - "\\.github/workflows/performance\\.yml" - ] - }, - { - "sdk": "remoteconfig", - "podspecs": ["FirebaseRemoteConfig.podspec", "FirebaseRemoteConfigSwift.podspec"], - "filePatterns": [ - "^FirebaseRemoteConfig.*", - "Interop/Analytics/Public/[^/]+\\.h", - "\\.github/workflows/remoteconfig\\.yml", - "scripts/generate_access_token\\.sh" - ] - }, - { - "sdk": "shared-swift", - "podspecs": ["FirebaseSharedSwift.podspec"], - "filePatterns": [ - "^FirebaseSharedSwift.*" - ] - }, - { - "sdk": "storage", - "podspecs": ["FirebaseStorage.podspec"], - "filePatterns": [ - "^FirebaseStorage.*", - "FirebaseAuth/Interop/[^/]+\\.h", - "\\.github/workflows/storage\\.yml" - ] - } -] diff --git a/scripts/health_metrics/generate_code_coverage_report/Package.swift b/scripts/health_metrics/generate_code_coverage_report/Package.swift deleted file mode 100644 index caffe36..0000000 --- a/scripts/health_metrics/generate_code_coverage_report/Package.swift +++ /dev/null @@ -1,78 +0,0 @@ -// swift-tools-version:5.3 -// The swift-tools-version declares the minimum version of Swift required to build this package. -/* - * Copyright 2021 Google LLC - * - * 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. - */ - -import PackageDescription - -let package = Package( - name: "CodeCoverage", - products: [ - // Products define the executables and libraries a package produces, and make them visible to - // other packages. - .executable( - name: "CoverageReportGenerator", - targets: ["CoverageReportGenerator"] - ), - .executable( - name: "UpdatedFilesCollector", - targets: ["UpdatedFilesCollector"] - ), - .executable( - name: "IncrementalCoverageReportGenerator", - targets: ["IncrementalCoverageReportGenerator"] - ), - .executable( - name: "BinarySizeReportGenerator", - targets: ["BinarySizeReportGenerator"] - ), - ], - dependencies: [ - .package(url: "https://github.com/apple/swift-argument-parser", from: "0.2.0"), - ], - targets: [ - .target( - name: "CoverageReportGenerator", - dependencies: [ - .product(name: "ArgumentParser", package: "swift-argument-parser"), - "Utils", - ] - ), - .target( - name: "UpdatedFilesCollector", - dependencies: [ - .product(name: "ArgumentParser", package: "swift-argument-parser"), - ] - ), - .target( - name: "IncrementalCoverageReportGenerator", - dependencies: [ - .product(name: "ArgumentParser", package: "swift-argument-parser"), - "Utils", - ] - ), - .target( - name: "BinarySizeReportGenerator", - dependencies: [ - .product(name: "ArgumentParser", package: "swift-argument-parser"), - "Utils", - ] - ), - .target( - name: "Utils" - ), - ] -) diff --git a/scripts/health_metrics/generate_code_coverage_report/README.md b/scripts/health_metrics/generate_code_coverage_report/README.md deleted file mode 100644 index 455f58a..0000000 --- a/scripts/health_metrics/generate_code_coverage_report/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# coverage_report_parser - -This is a tool to read test coverages of xcresult bundle and generate a json report. This json -report will be sent to Metrics Service to create a coverage report as a comment in a PR or to update -the coverage database. - -## Usage - -This is tool will be used for both pull_request and merge. Common flags are shown below. - -``` -swift run CoverageReportGenerator --presubmit "${REPO}" --commit "${GITHUB_SHA}" --token "${TOKEN}" \ ---xcresult-dir "${XCRESULT_DIR}" --log-link "${}" --pull-request-num "${PULL_REQUEST_NUM}" \ ---base-commit "${BASE_COMMIT}" --branch "${BRANCH}" -``` -Common parameters for both pull_request and merge: -- `presubmit/merge`: A required flag to know if the request is for pull requests or merge. -- `REPO`: A required argument for a repo where coverage data belong. -- `commit`: The current commit sha. -- `token`: A token to access a service account of Metrics Service -- `xcresult-dir`: A directory containing all xcresult bundles. - -### Create a report in a pull request - -In a workflow, this will run for each pull request update. The command below will generate a report -in a PR. After a workflow of test coverage is done, a new coverage report will be posted on a -comment of a pull request. If such comment has existed, this comment will be overriden by the latest -report. - -Since the flag is `presubmit` here, the following options are required for a PR request: -- `log-link`: Log link to unit tests. This is generally a actions/runs/ link in GitHub Actions. -- `pull-request-num`: A report will be posted in this pull request. -- `base-commit`: The commit sha used to compare the diff of the current`commit`. - -An example in a GitHub Actions workflow: -``` -swift run CoverageReportGenerator --presubmit "firebase/firebase-ios-sdk" --commit "${GITHUB_SHA}" \ ---token $(gcloud auth print-identity-token) --xcresult-dir "/Users/runner/test/codecoverage" \ ---log-link "https://github.com/firebase/firebase-ios-sdk/actions/runs/${GITHUB_RUN_ID}" \ ---pull-request-num ${{github.event.pull_request.number}} --base-commit "$base_commit" - -``` - -### Add new coverage data to the storage of Metrics Service - -In a workflow, this will run in merge events or postsubmit tests. After each merge, all pod tests -will run to add a new commit and its corresponding coverage data. -``` -swift run CoverageReportGenerator --merge "firebase/firebase-ios-sdk" --commit "${GITHUB_SHA}" \ ---token $(gcloud auth print-identity-token) --xcresult-dir "/Users/runner/test/codecoverage" \ ---log-link "https://github.com/firebase/firebase-ios-sdk/actions/runs/${GITHUB_RUN_ID}" --branch \ -"${GITHUB_REF##*/}" -``` -- `branch`: this is for merge and the new commit with coverage data will be linked with the branch -in the database of Metrics Service. - -### Details - -More details in go/firebase-ios-sdk-test-coverage-metrics. Can also run -`swift run CoverageReportGenerator -h` for help info. diff --git a/scripts/health_metrics/generate_code_coverage_report/Sources/BinarySizeReportGenerator/BinarySizeReportGeneration.swift b/scripts/health_metrics/generate_code_coverage_report/Sources/BinarySizeReportGenerator/BinarySizeReportGeneration.swift deleted file mode 100644 index f30ce3d..0000000 --- a/scripts/health_metrics/generate_code_coverage_report/Sources/BinarySizeReportGenerator/BinarySizeReportGeneration.swift +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * 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. - */ - -import ArgumentParser -import Foundation -import Utils - -private enum Constants {} - -extension Constants { - // Binary Size Metrics flag for the Metrics Service. - static let metric = "BinarySize" - static let cocoapodSizeReportFile = "binary_report.json" -} - -/// Pod Config -struct PodConfigs: Codable { - let pods: [Pod] -} - -struct Pod: Codable { - let sdk: String - let path: String -} - -/// Cocoapods-size tool report, -struct SDKBinaryReport: Codable { - let combinedPodsExtraSize: Int - - enum CodingKeys: String, CodingKey { - case combinedPodsExtraSize = "combined_pods_extra_size" - } -} - -/// Metrics Service API request data -public struct BinaryMetricsReport: Codable { - let metric: String - let results: [Result] - let log: String -} - -public struct Result: Codable { - let sdk, type: String - let value: Int -} - -extension BinaryMetricsReport { - func toData() -> Data { - let jsonData = try! JSONEncoder().encode(self) - return jsonData - } -} - -func CreatePodConfigJSON(of sdks: [String], from sdk_dir: URL) throws { - var pods: [Pod] = [] - for sdk in sdks { - let pod = Pod(sdk: sdk, path: sdk_dir.path) - pods.append(pod) - } - let podConfigs = PodConfigs(pods: pods) - try JSONParser.writeJSON(of: podConfigs, to: "./cocoapods_source_config.json") -} - -// Create a JSON format data prepared to send to the Metrics Service. -func CreateMetricsRequestData(of sdks: [String], type: String, - log: String) throws -> BinaryMetricsReport { - var reports: [Result] = [] - // Create a report for each individual SDK and collect all of them into reports. - for sdk in sdks { - // Create a report, generated by cocoapods-size, for each SDK. `.stdout` is used - // since `pipe` could cause the tool to hang. That is probably caused by cocopod-size - // is using pipe and a pipe is shared by multiple parent/child process and cause - // deadlock. `.stdout` is a quick way to resolve at the moment. The difference is - // that `.stdout` will print out logs in the console while pipe can assign logs a - // variable. - Shell.run( - "cd cocoapods-size && python3 measure_cocoapod_size.py --cocoapods \(sdk) --spec_repos specsstaging master --cocoapods_source_config ../cocoapods_source_config.json --json \(Constants.cocoapodSizeReportFile)", - stdout: .stdout - ) - let SDKBinarySize = try JSONParser.readJSON( - of: SDKBinaryReport.self, - from: "cocoapods-size/\(Constants.cocoapodSizeReportFile)" - ) - // Append reports for data for API request to the Metrics Service. - reports.append(Result(sdk: sdk, type: type, value: SDKBinarySize.combinedPodsExtraSize)) - } - let metricsRequestReport = BinaryMetricsReport( - metric: Constants.metric, - results: reports, - log: log - ) - return metricsRequestReport -} - -public func CreateMetricsRequestData(SDK: [String], SDKRepoDir: URL, - logPath: String) throws -> BinaryMetricsReport? { - try CreatePodConfigJSON(of: SDK, from: SDKRepoDir) - let data = try CreateMetricsRequestData( - of: SDK, - type: "CocoaPods", - log: logPath - ) - return data -} diff --git a/scripts/health_metrics/generate_code_coverage_report/Sources/BinarySizeReportGenerator/main.swift b/scripts/health_metrics/generate_code_coverage_report/Sources/BinarySizeReportGenerator/main.swift deleted file mode 100644 index cc24a86..0000000 --- a/scripts/health_metrics/generate_code_coverage_report/Sources/BinarySizeReportGenerator/main.swift +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * 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. - */ - -import ArgumentParser -import Foundation -import Utils - -enum RequestType: EnumerableFlag { - case presubmit - case merge -} - -struct BinarySizeReportGenerator: ParsableCommand { - @Option( - help: "Cocoapods-size tool directory from https://github.com/google/cocoapods-size.", - transform: URL.init(fileURLWithPath:) - ) - var binarySizeToolDir: URL - - @Option(help: "Local SDK repo.", transform: URL.init(fileURLWithPath:)) - var SDKRepoDir: URL - - @Option(parsing: .upToNextOption, help: "SDKs to be measured.") - var SDK: [String] - - @Option(help: "Link to the log, leave \"\" if none.") - var logLink: String - - // Send to the Metrics Service - @Flag(help: "Determine if the request to Metrics Service is for pull_requests or merge.") - var requestType: RequestType - - @Argument(help: "A repo coverage data will be related to.") - var repo: String - - @Option( - help: "presubmit: compare the diff to the base_commit; merge: store coverage data linking to this commit." - ) - var headCommit: String - - @Option(help: "Token to access an account of the Metrics Service") - var token: String - - @Option( - help: "This is for presubmit request. Number of a pull request that a coverage report will be posted on." - ) - var pullRequestNum: Int? - - @Option(help: "This is for presubmit request. Additional note for the report.") - var pullRequestNote: String? - - @Option( - help: "This is for presubmit request. Coverage of commit will be compared to the coverage of this base commit." - ) - var baseCommit: String? - - @Option( - help: "This is for merge request. Branch here will be linked to the coverage data, with the merged commit, in the database. " - ) - var sourceBranch: String? - - func run() throws { - if let binarySizeRequest = try CreateMetricsRequestData( - SDK: SDK, - SDKRepoDir: SDKRepoDir, - logPath: logLink - ) { - sendMetricsServiceRequest( - repo: repo, - commits: headCommit, - jsonContent: binarySizeRequest.toData(), - token: token, - is_presubmit: requestType == RequestType.presubmit, - branch: sourceBranch, - pullRequest: pullRequestNum, - pullRequestNote: pullRequestNote, - baseCommit: baseCommit - ) - } else { - print("coverageRequest is nil.") - } - } -} - -BinarySizeReportGenerator.main() diff --git a/scripts/health_metrics/generate_code_coverage_report/Sources/CoverageReportGenerator/CoverageReportParser.swift b/scripts/health_metrics/generate_code_coverage_report/Sources/CoverageReportGenerator/CoverageReportParser.swift deleted file mode 100644 index 8f0d1ea..0000000 --- a/scripts/health_metrics/generate_code_coverage_report/Sources/CoverageReportGenerator/CoverageReportParser.swift +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * 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. - */ - -import Foundation -import Utils - -// This will contain code coverage result from a xcresult bundle. -struct CoverageReportSource: Codable { - let coveredLines: Int - let lineCoverage: Double - let targets: [Target] - - struct Target: Codable { - let name: String - let lineCoverage: Double - let files: [File] - struct File: Codable { - let coveredLines: Int - let lineCoverage: Double - let path: String - let name: String - } - } -} - -// This will contains data that will be eventually transferred to a json file -// sent to the Metrics Service. -struct CoverageReportRequestData: Codable { - var metric: String - var results: [FileCoverage] - var log: String - - struct FileCoverage: Codable { - let sdk: String - let type: String - let value: Double - } -} - -// In the tool here, this will contain add all CoverageReportSource objects from -// different xcresult bundles. -extension CoverageReportRequestData { - init() { - metric = "Coverage" - results = [] - log = "" - } - - mutating func addCoverageData(from source: CoverageReportSource, resultBundle: String) { - for target in source.targets { - // Get sdk name. resultBundle is like ${SDK}-${platform}. E.g. FirebaseDatabase-ios. - // To display only sdk related tests and exclude non related testing, e.g. - // FirebaseDatabase-ios-GoogleDataTransport.framework, - // FirebaseDatabase-ios-FirebaseCore-Unit-unit.xctest, - // FirebaseDatabase-ios-FirebaseCore.framework, a regex pattern will be - // used to exclude results that are not related in terms of the target names. - let sdk_name = resultBundle.components(separatedBy: "-")[0] - let range = NSRange(location: 0, length: target.name.utf16.count) - let target_pattern = ".*\(sdk_name).*framework" - let sdk_related_coverage_file_pattern = try! NSRegularExpression( - pattern: target_pattern, - options: NSRegularExpression.Options(rawValue: 0) - ) - print("Target: \(target.name) is detected.") - - if sdk_related_coverage_file_pattern.firstMatch(in: target.name, range: range) != nil { - print( - "Target, \(target.name), fit the pattern, \(target_pattern), and will be involved in the report." - ) - results - .append(FileCoverage(sdk: resultBundle + "-" + target.name, type: "", - value: target.lineCoverage)) - for file in target.files { - results - .append(FileCoverage(sdk: resultBundle + "-" + target.name, type: file.name, - value: file.lineCoverage)) - } - } - } - } - - mutating func addLogLink(_ logLink: String) { - log = logLink - } - - func toData() -> Data { - let jsonData = try! JSONEncoder().encode(self) - return jsonData - } -} - -// Read json file and transfer to CoverageReportSource. -func readLocalFile(forName name: String) -> CoverageReportSource? { - do { - let fileURL = URL(fileURLWithPath: FileManager().currentDirectoryPath) - .appendingPathComponent(name) - let data = try Data(contentsOf: fileURL) - let coverageReportSource = try JSONDecoder().decode(CoverageReportSource.self, from: data) - return coverageReportSource - } catch { - print("CoverageReportSource is not able to be generated. \(error)") - } - - return nil -} - -// Get in the dir, xcresultDirPathURL, which contains all xcresult bundles, and -// create CoverageReportRequestData which will have all coverage data for in -// the dir. -func combineCodeCoverageResultBundles(from xcresultDirPathURL: URL, - log: String) throws -> CoverageReportRequestData? { - let fileManager = FileManager.default - do { - var coverageReportRequestData = CoverageReportRequestData() - coverageReportRequestData.addLogLink(log) - let fileURLs = try fileManager.contentsOfDirectory( - at: xcresultDirPathURL, - includingPropertiesForKeys: nil - ) - let xcresultURLs = fileURLs.filter { $0.pathExtension == "xcresult" } - for xcresultURL in xcresultURLs { - let resultBundleName = xcresultURL.deletingPathExtension().lastPathComponent - let coverageSourceJSONFile = "\(resultBundleName).json" - try? fileManager.removeItem(atPath: coverageSourceJSONFile) - Shell - .run("xcrun xccov view --report --json \(xcresultURL.path) >> \(coverageSourceJSONFile)") - if let coverageReportSource = readLocalFile(forName: "\(coverageSourceJSONFile)") { - coverageReportRequestData.addCoverageData( - from: coverageReportSource, - resultBundle: resultBundleName - ) - } - } - return coverageReportRequestData - } catch { - print( - "Error while enuermating files \(xcresultDirPathURL): \(error.localizedDescription)" - ) - } - return nil -} diff --git a/scripts/health_metrics/generate_code_coverage_report/Sources/CoverageReportGenerator/main.swift b/scripts/health_metrics/generate_code_coverage_report/Sources/CoverageReportGenerator/main.swift deleted file mode 100644 index 0547cd4..0000000 --- a/scripts/health_metrics/generate_code_coverage_report/Sources/CoverageReportGenerator/main.swift +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * 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. - */ - -import ArgumentParser -import Foundation -import Utils - -enum RequestType: EnumerableFlag { - case presubmit - case merge -} - -struct CoverageReportGenerator: ParsableCommand { - @Flag(help: "Determine if the request to Metrics Service is for pull_requests or merge.") - var requestType: RequestType - - @Argument(help: "A repo coverage data will be related to.") - var repo: String - - @Option( - help: "presubmit: compare the diff to the base_commit; merge: store coverage data linking to this commit." - ) - var headCommit: String - - @Option(help: "Token to access an account of the Metrics Service") - var token: String - - @Option( - help: "The directory of all xcresult bundles with code coverage data. ONLY files/dirs under this directory will be searched." - ) - var xcresultDir: String - - @Option(help: "Link to the log, leave \"\" if none.") - var logLink: String - - @Option( - help: "This is for presubmit request. Number of a pull request that a coverage report will be posted on." - ) - var pullRequestNum: Int? - - @Option(help: "This is for presubmit request. Additional note for the report.") - var pullRequestNote: String? - - @Option( - help: "This is for presubmit request. Coverage of commit will be compared to the coverage of this base_commit." - ) - var baseCommit: String? - - @Option( - help: "This is for merge request. Branch here will be linked to the coverage data, with the merged commit, in the database. " - ) - var sourceBranch: String? - - func run() throws { - if let coverageRequest = try combineCodeCoverageResultBundles( - from: URL(fileURLWithPath: xcresultDir), - log: logLink - ) { - sendMetricsServiceRequest( - repo: repo, - commits: headCommit, - jsonContent: coverageRequest.toData(), - token: token, - is_presubmit: requestType == RequestType.presubmit, - branch: sourceBranch, - pullRequest: pullRequestNum, - pullRequestNote: pullRequestNote, - baseCommit: baseCommit - ) - } else { - print("coverageRequest is nil.") - } - } -} - -CoverageReportGenerator.main() diff --git a/scripts/health_metrics/generate_code_coverage_report/Sources/IncrementalCoverageReportGenerator/main.swift b/scripts/health_metrics/generate_code_coverage_report/Sources/IncrementalCoverageReportGenerator/main.swift deleted file mode 100644 index 89398c8..0000000 --- a/scripts/health_metrics/generate_code_coverage_report/Sources/IncrementalCoverageReportGenerator/main.swift +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * 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. - */ - -import ArgumentParser -import Foundation -import Utils - -private enum Constants {} - -extension Constants { - static let xcresultExtension = "xcresult" - // Command to get line execution counts of a file in a xcresult bundle. - static let xcovCommand = "xcrun xccov view --archive --file " - // The pattern is to match text "line_index: execution_counts" from the - // outcome of xcresult bundle, e.g. "305 : 0". - static let lineExecutionCountPattern = "[0-9]+\\s*:\\s*([0-9*]+)" - // Match to the group of the lineExecutionCountPattern, i.e "([0-9*]+)". - static let lineExecutionCountPatternGroup = 1 - // A file includes all newly added lines without tests covered. - static let defaultUncoveredLineReportFileName = "uncovered_file_lines.json" -} - -/// A JSON file from git_diff_to_json.sh will be decoded to the following instance. -struct FileIncrementalChanges: Codable { - // Name of a file with newly added lines - let file: String - // Indices of newly added lines in this file - let addedLines: [Int] - enum CodingKeys: String, CodingKey { - case file - case addedLines = "added_lines" - } -} - -/// `xccov` outcomes of a file from a xcresult bundle will be transfered -/// to the following instance. -struct LineCoverage: Codable { - var fileName: String - // Line execution counts for lines in this file, the indices of the array are - // the (line indices - 1) of this file. - var coverage: [Int?] - // The source/xcresult bundle of the coverage - var xcresultBundle: URL -} - -struct IncrementalCoverageReportGenerator: ParsableCommand { - @Option( - help: "Root path of archived files in a xcresult bundle." - ) - var fileArchiveRootPath: String - - @Option(help: "A dir of xcresult bundles.", - transform: URL.init(fileURLWithPath:)) - var xcresultDir: URL - - @Option( - help: """ - A JSON file with changed files and added line numbers. E.g. JSON file: - '[{"file": "FirebaseDatabase/Sources/Api/FIRDataSnapshot.m", "added_lines": [105,106,107,108,109]},{"file": "FirebaseDatabase/Sources/Core/Utilities/FPath.m", "added_lines": [304,305,306,307,308,309]}]' - """, - transform: { try JSONParser.readJSON(of: [FileIncrementalChanges].self, from: $0) } - ) - var changedFiles: [FileIncrementalChanges] - - @Option( - help: "Uncovered line JSON file output path" - ) - var uncoveredLineFileJson: String = Constants.defaultUncoveredLineReportFileName - - /// This will transfer line executions report from a xcresult bundle to an array of LineCoverage. - /// The output will have the file name, line execution counts and the xcresult bundle name. - func createLineCoverageRecord(from coverageFile: URL, changedFile: String, - lineCoverage: [LineCoverage]? = nil) -> [LineCoverage] { - // The indices of the array represent the (line index - 1), while the value is the execution - // counts. - // Unexecutable lines, e.g. comments, will be nil here. - var lineExecutionCounts: [Int?] = [] - do { - let inString = try String(contentsOf: coverageFile) - let lineCoverageRegex = try! NSRegularExpression(pattern: Constants - .lineExecutionCountPattern) - for line in inString.components(separatedBy: "\n") { - let range = NSRange(location: 0, length: line.utf16.count) - if let match = lineCoverageRegex.firstMatch(in: line, options: [], range: range) { - // Get the execution counts and append. Line indices are - // consecutive and so the array will have counts for each line - // and nil for unexecutable lines, e.g. comments. - let nsRange = match.range(at: Constants.lineExecutionCountPatternGroup) - if let range = Range(nsRange, in: line) { - lineExecutionCounts.append(Int(line[range])) - } - } - } - } catch { - fatalError("Failed to open \(coverageFile): \(error)") - } - if var coverageData = lineCoverage { - coverageData - .append(LineCoverage(fileName: changedFile, coverage: lineExecutionCounts, - xcresultBundle: coverageFile)) - return coverageData - } else { - return [LineCoverage(fileName: changedFile, coverage: lineExecutionCounts, - xcresultBundle: coverageFile)] - } - } - - /// This function is to get union of newly added file lines and lines execution counts, from a - /// xcresult bundle. - /// Return an array of LineCoverage, which includes uncovered line indices of a file and its - /// xcresult bundle source. - func getUncoveredFileLines(fromDiff changedFiles: [FileIncrementalChanges], - xcresultFile: URL, - archiveRootPath rootPath: String) -> [LineCoverage] { - var uncoveredFiles: [LineCoverage] = [] - for change in changedFiles { - let archiveFilePath = URL(string: rootPath)!.appendingPathComponent(change.file) - // tempOutputFile is a temp file, with the xcresult bundle name, including line execution - // counts of a file - let tempOutputFile = xcresultFile.deletingPathExtension() - // Fetch line execution report of a file from a xcresult bundle into a temp file, which has - // the same name as the xcresult bundle. - Shell.run( - "\(Constants.xcovCommand) \(archiveFilePath.absoluteString) \(xcresultFile.path) > \(tempOutputFile.path)", - displayCommand: true, - displayFailureResult: false - ) - for coverageFile in createLineCoverageRecord( - from: tempOutputFile, - changedFile: change.file - ) { - var uncoveredLine = LineCoverage( - fileName: coverageFile.fileName, - coverage: [], - xcresultBundle: coverageFile.xcresultBundle - ) - print("The following lines shown below, if any, are found not tested in \(xcresultFile)") - for addedLineIndex in change.addedLines { - // `xccov` report will not involve unexecutable lines which are at - // the end of the file. That means if the last couple lines are - // comments, these lines will not be in the `coverageFile.coverage`. - // Indices in an array, starting from 0, correspond to lineIndex, - // starting from 0, minus 1. - if addedLineIndex <= coverageFile.coverage.count, - let testCoverRun = coverageFile.coverage[addedLineIndex - 1], testCoverRun == 0 { - print(addedLineIndex) - uncoveredLine.coverage.append(addedLineIndex) - } - } - if !uncoveredLine.coverage.isEmpty { uncoveredFiles.append(uncoveredLine) } - } - } - return uncoveredFiles - } - - func run() throws { - let enumerator = FileManager.default.enumerator(atPath: xcresultDir.path) - var uncoveredFiles: [LineCoverage] = [] - // Search xcresult bundles from xcresultDir and get union of `git diff` report and xccov output - // to generate a list of lineCoverage including files and their uncovered lines. - while let file = enumerator?.nextObject() as? String { - var isDir: ObjCBool = false - let xcresultURL = xcresultDir.appendingPathComponent(file, isDirectory: true) - if FileManager.default.fileExists(atPath: xcresultURL.path, isDirectory: &isDir) { - if isDir.boolValue, xcresultURL.path.hasSuffix(Constants.xcresultExtension) { - let uncoveredXcresult = getUncoveredFileLines( - fromDiff: changedFiles, - xcresultFile: xcresultURL, - archiveRootPath: fileArchiveRootPath - ) - uncoveredFiles.append(contentsOf: uncoveredXcresult) - } - } - } - // Output uncoveredFiles as a JSON file. - do { - let jsonData = try JSONEncoder().encode(uncoveredFiles) - try String(data: jsonData, encoding: .utf8)!.write( - to: URL(fileURLWithPath: FileManager.default.currentDirectoryPath) - .appendingPathComponent(uncoveredLineFileJson), - atomically: true, - encoding: String.Encoding.utf8 - ) - } catch { - print("Uncovered lines are not able to be parsed into a JSON file.\n \(error)\n") - } - } -} - -IncrementalCoverageReportGenerator.main() diff --git a/scripts/health_metrics/generate_code_coverage_report/Sources/UpdatedFilesCollector/main.swift b/scripts/health_metrics/generate_code_coverage_report/Sources/UpdatedFilesCollector/main.swift deleted file mode 100644 index 9941b2d..0000000 --- a/scripts/health_metrics/generate_code_coverage_report/Sources/UpdatedFilesCollector/main.swift +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * 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. - */ - -import ArgumentParser -import Foundation - -struct SDKFilePattern: Codable { - let sdk: String - let podspecs: [String] - let filePatterns: [String] -} - -/// SDKPodspec is to help generate an array of podspec in json file, e.g. -/// ``` output.json -/// [{"podspec":"FirebaseABTesting.podspec"},{"podspec":"FirebaseAnalytics.podspec"}] -/// ``` -struct SDKPodspec: Codable { - let podspec: String -} - -struct UpdatedFilesCollector: ParsableCommand { - @Option(help: "A txt File with updated files.", - transform: { str in - let url = URL(fileURLWithPath: str) - let data = try String(contentsOf: url) - return data.components(separatedBy: .newlines) - }) - var changedFilePaths: [String] - - @Option(help: "A JSON file path conforming to the struct SDKFilePattern", - transform: { str in - let url = URL(fileURLWithPath: str) - let jsonData = try Data(contentsOf: url) - return try JSONDecoder().decode([SDKFilePattern].self, from: jsonData) - }) - var codeCoverageFilePatterns: [SDKFilePattern] - - @Option(help: "A output file with all Podspecs with related changed files", - transform: { str in - print(FileManager.default.currentDirectoryPath) - let documentDir = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) - return documentDir.appendingPathComponent(str) - }) - var outputSDKFileURL: URL? - - /// Exclude pods from spec testings. - @Option(parsing: .upToNextOption, help: "Podspecs that will be excluded in the testings.") - var excludePodspecs: [String] = [] - - func run() throws { - var podspecsWithChangedFiles: [SDKPodspec] = [] - print("=============== list changed files ===============") - print(changedFilePaths.joined(separator: "\n")) - // Initiate all run_job flag to false. - for sdkPatterns in codeCoverageFilePatterns { - print("::set-output name=\(sdkPatterns.sdk)_run_job::false") - } - // Go through patterns of each sdk. Once there is a path of changed file matching - // any pattern of this sdk, the run_job flag of this sdk will be turned to true. - for sdkPatterns in codeCoverageFilePatterns { - var trigger_pod_test_for_coverage_report = false - for pattern in sdkPatterns.filePatterns { - let regex = try! NSRegularExpression(pattern: pattern) - // If one changed file path match one path of this sdk, the run_job flag of - // the sdk will be turned on. - for changedFilePath in changedFilePaths { - let range = NSRange(location: 0, length: changedFilePath.utf16.count) - if regex.firstMatch(in: changedFilePath, options: [], range: range) != nil { - print("=============== paths of changed files ===============") - print("::set-output name=\(sdkPatterns.sdk)_run_job::true") - for podspec in sdkPatterns.podspecs { - if !excludePodspecs.contains(podspec) { - podspecsWithChangedFiles.append(SDKPodspec(podspec: podspec)) - } else if let outputPath = outputSDKFileURL { - print( - "\(podspec) was excluded and will not be written in \(outputPath.absoluteString) " - ) - } - } - print("\(sdkPatterns.sdk): \(changedFilePath) is updated under the pattern, \(pattern)") - trigger_pod_test_for_coverage_report = true - // Once this sdk run_job flag is turned to true, then the loop - // will skip to the next sdk. - break - } - if trigger_pod_test_for_coverage_report { break } - } - if trigger_pod_test_for_coverage_report { break } - } - } - if let outputPath = outputSDKFileURL { - do { - // Instead of directly writing Data to a file, trasnferring Data to - // String can help trimming whitespaces and newlines in advance. - let str = try String( - decoding: JSONEncoder().encode(podspecsWithChangedFiles), - as: UTF8.self - ) - try str.trimmingCharacters(in: .whitespacesAndNewlines).write( - to: outputPath, - atomically: true, - encoding: String.Encoding.utf8 - ) - } catch { - fatalError("Error while writting in \(outputPath.path).\n\(error)") - } - } - } -} - -UpdatedFilesCollector.main() diff --git a/scripts/health_metrics/generate_code_coverage_report/Sources/Utils/JSONParser.swift b/scripts/health_metrics/generate_code_coverage_report/Sources/Utils/JSONParser.swift deleted file mode 100644 index 8d8b4a0..0000000 --- a/scripts/health_metrics/generate_code_coverage_report/Sources/Utils/JSONParser.swift +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * 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. - */ - -import Foundation - -public enum JSONParser { - // Decode an instance from a JSON file. - public static func readJSON(of dataStruct: T.Type, from path: String) throws -> T { - let fileURL = URL(fileURLWithPath: FileManager().currentDirectoryPath) - .appendingPathComponent(path) - let data = try Data(contentsOf: fileURL) - return try JSONDecoder().decode(dataStruct, from: data) - } - - public static func writeJSON(of data: T, to path: String) throws { - let fileURL = URL(fileURLWithPath: FileManager().currentDirectoryPath) - .appendingPathComponent(path) - let data = try JSONEncoder().encode(data) - try data.write(to: fileURL) - } -} diff --git a/scripts/health_metrics/generate_code_coverage_report/Sources/Utils/MetricsServiceRequest.swift b/scripts/health_metrics/generate_code_coverage_report/Sources/Utils/MetricsServiceRequest.swift deleted file mode 100644 index 6ae51ae..0000000 --- a/scripts/health_metrics/generate_code_coverage_report/Sources/Utils/MetricsServiceRequest.swift +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * 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. - */ - -import Foundation -#if canImport(FoundationNetworking) - import FoundationNetworking -#endif - -public func sendMetricsServiceRequest(repo: String, commits: String, jsonContent: Data, - token: String, - is_presubmit: Bool, branch: String?, pullRequest: Int?, - pullRequestNote: String?, baseCommit: String?) { - var request: URLRequest - var semaphore = DispatchSemaphore(value: 0) - let endpoint = - "https://api.firebase-sdk-health-metrics.com/repos/\(repo)/commits/\(commits)/reports?" - var pathPara: [String] = [] - if is_presubmit { - guard let pr = pullRequest else { - print( - "The pull request number should be specified for an API pull-request request to the Metrics Service." - ) - return - } - guard let bc = baseCommit else { - print( - "Base commit hash should be specified for an API pull-request request to the Metrics Service." - ) - return - } - pathPara.append("pull_request=\(String(pr))") - if let note = pullRequestNote { - let compatible_url_format_note = note - .addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! - pathPara.append("note=\(compatible_url_format_note))") - } - pathPara.append("base_commit=\(bc)") - } else { - guard let branch = branch else { - print("Targeted merged branch should be specified.") - return - } - pathPara.append("branch=\(branch)") - } - - let webURL = endpoint + pathPara.joined(separator: "&") - guard let metricsServiceURL = URL(string: webURL) else { - print("URL Path \(webURL) is not valid.") - return - } - request = URLRequest(url: metricsServiceURL, timeoutInterval: Double.infinity) - - request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization") - request.addValue("application/json", forHTTPHeaderField: "Content-Type") - - request.httpMethod = "POST" - request.httpBody = jsonContent - - let task = URLSession.shared.dataTask(with: request) { data, response, error in - guard let data = data else { - print(String(describing: error)) - return - } - print(String(data: data, encoding: .utf8)!) - semaphore.signal() - } - - task.resume() - semaphore.wait() -} diff --git a/scripts/health_metrics/generate_code_coverage_report/Sources/Utils/ShellUtils.swift b/scripts/health_metrics/generate_code_coverage_report/Sources/Utils/ShellUtils.swift deleted file mode 100644 index e5396dc..0000000 --- a/scripts/health_metrics/generate_code_coverage_report/Sources/Utils/ShellUtils.swift +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * 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. - */ - -import Foundation - -public enum ProcessCom { - case pipe - case stdout -} - -public enum Shell { - @discardableResult - public static func run(_ command: String, displayCommand: Bool = true, - displayFailureResult: Bool = true, - stdout: ProcessCom = .pipe) -> Int32 { - let task = Process() - let pipe = Pipe() - if stdout == ProcessCom.pipe { - task.standardOutput = pipe - } - task.launchPath = "/bin/zsh" - task.arguments = ["-c", command] - task.launch() - if displayCommand { - print("[Health Metrics] Command:\(command)\n") - } - task.waitUntilExit() - if stdout == ProcessCom.pipe { - let data = pipe.fileHandleForReading.readDataToEndOfFile() - let log = String(data: data, encoding: .utf8)! - if displayFailureResult, task.terminationStatus != 0 { - print("-----Exit code: \(task.terminationStatus)") - print("-----Log:\n \(log)") - } - } - return task.terminationStatus - } -} diff --git a/scripts/health_metrics/get_updated_files.sh b/scripts/health_metrics/get_updated_files.sh deleted file mode 100755 index 7b2cdf3..0000000 --- a/scripts/health_metrics/get_updated_files.sh +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright 2021 Google LLC -# -# 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. - -set -ex -# Updated files in paths in file_patterns.json will trigger code coverage workflows. -# Updates in a pull request will generate a code coverage report in a PR. -exclude_specs="" -while getopts "p:e:" flag -do - case "${flag}" in - p) spec_output_file=${OPTARG};; - e) exclude_specs=${OPTARG[*]};; - esac -done - -dir=$(pwd) - -target_branch_head=$(git rev-parse remotes/origin/${GITHUB_BASE_REF}) -echo "The target branch head commit is ${target_branch_head}." -# Set target branch head and this will be used to compare diffs of coverage to the current commit. -echo "::set-output name=target_branch_head::${target_branch_head}" - -cd scripts/health_metrics/generate_code_coverage_report - -# List changed file from the merged commit. This is generated by comparing the -# merge commit to the head commit from the target branch. -git diff --name-only remotes/origin/${GITHUB_BASE_REF} ${GITHUB_SHA} > updated_files.txt - -if [ -z $spec_output_file ] ; then - swift run UpdatedFilesCollector --changed-file-paths updated_files.txt --code-coverage-file-patterns ../file_patterns.json -else - swift run UpdatedFilesCollector --changed-file-paths updated_files.txt --code-coverage-file-patterns ../file_patterns.json --output-sdk-file-url "${spec_output_file}" --exclude-podspecs ${exclude_specs} - - mv "${spec_output_file}" "${dir}" -fi diff --git a/scripts/health_metrics/git_diff_to_json.sh b/scripts/health_metrics/git_diff_to_json.sh deleted file mode 100755 index 444b850..0000000 --- a/scripts/health_metrics/git_diff_to_json.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/bin/bash - -# Copyright 2021 Google LLC -# -# 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. - -# USAGE: git diff -U0 [base_commit] HEAD | get_diff_lines.sh -# -# This will generate a JSON output of changed files and their newly added -# lines. -oIFS=$IFS - -json_output="[" -# Concatenate files and line indices into a JSON file. -concatenate() { - local path=$1 - shift - IFS="," - local lines=$@ - echo "{\"file\": \"${path}\", \"added_lines\": [${lines[*]}]}" - IFS=$oIFS -} -diff-lines() { - local path= - local line= - local lines=() - while read; do - esc='\033' - # Skip lines starting with "---". e.g. "--- a/.github/workflows/database.yml". - # $REPLY, containing one line at a time, here and below are the default variable - # of `read`. - if [[ "$REPLY" =~ ---\ (a/)?.* ]]; then - continue - # Detect new changed files from `git diff`. e.g. "+++ b/.github/workflows/combine.yml". - elif [[ "$REPLY" =~ ^\+\+\+\ (b/)?([^[:blank:]$esc]+).* ]]; then - # Add the last changed file and its indices of added line to the output variable. - if [ ${#lines[@]} -ne 0 ]; then - json_output+="$(concatenate "${path}" ${lines[@]})," - fi - # Refresh the array of line indices and file path for the new changed file. - lines=() - path=${BASH_REMATCH[2]} - # Detect the started line index of a changed file, e.g. "@@ -53,0 +54,24 @@ jobs:" where "54" will be fetched. - elif [[ "$REPLY" =~ @@\ -[0-9]+(,[0-9]+)?\ \+([0-9]+)(,[0-9]+)?\ @@.* ]]; then - line=${BASH_REMATCH[2]} - # Detect newly added lines. e.g. "+ storage-combine-integration:" - elif [[ "$REPLY" =~ ^($esc\[[0-9;]+m)*([+]) ]]; then - lines+=($line) - ((line++)) - fi - done - json_output+=$(concatenate "${path}" ${lines[@]} ) -} - -diff-lines -json_output="${json_output}]" -echo $json_output diff --git a/scripts/health_metrics/pod_test_code_coverage_report.sh b/scripts/health_metrics/pod_test_code_coverage_report.sh deleted file mode 100755 index 1908c92..0000000 --- a/scripts/health_metrics/pod_test_code_coverage_report.sh +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright 2021 Google LLC -# -# 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 -# -# https://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. - -set -ex - -# Loop through arguments and process them -for arg in "$@" -do - case $arg in - --sdk=*) - SDK="${arg#*=}" - shift # Remove --sdk= from processing - ;; - --platform=*) - PLATFORM="${arg#*=}" - shift - ;; - --test_spec=*) - TEST_SPEC="${arg#*=}" - shift - ;; - --output_path=*) - OUTPUT_PATH="${arg#*=}" - shift - ;; - esac -done -DEFAULT_OUTPUT_PATH="/Users/runner/${SDK}-${PLATFORM}.xcresult" -DEFAULT_TEST_SPEC="unit" -OUTPUT_PATH="${OUTPUT_PATH:-${DEFAULT_OUTPUT_PATH}}" -TEST_SPEC="${TEST_SPEC:-${DEFAULT_TEST_SPEC}}" - -[[ -z "$SDK" ]] && { echo "Parameter --sdk should be specified, e.g. --sdk=FirebaseStorage" ; exit 1; } -[[ -z "$PLATFORM" ]] && { echo "Parameter --platform should be specified, e.g. --platform=ios" ; exit 1; } - -echo "SDK: ${SDK}" -echo "PLATFORM: ${PLATFORM}" -echo "OUTPUT_PATH: ${OUTPUT_PATH}" -echo "TEST_SPEC: ${TEST_SPEC}" - -if [ -d "/Users/runner/Library/Developer/Xcode/DerivedData" ]; then - rm -r /Users/runner/Library/Developer/Xcode/DerivedData/* -fi - -# Setup for pod unit tests -if [ $SDK == "FirebasePerformance" ]; then - scripts/setup_bundler.sh - scripts/third_party/travis/retry.sh scripts/build.sh Performance ${PLATFORM} unit -elif [ $SDK == "FirebaseFirestore" ]; then - scripts/install_prereqs.sh Firestore ${PLATFORM} xcodebuild - scripts/third_party/travis/retry.sh scripts/build.sh Firestore ${PLATFORM} xcodebuild -else - # Run unit tests of pods and put xcresult bundles into OUTPUT_PATH, which - # should be a targeted dir of actions/upload-artifact in workflows. - # In code coverage workflow, files under OUTPUT_PATH will be uploaded to - # GitHub Actions. - scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb "${SDK}".podspec --verbose --platforms="$(tr '[:upper:]' '[:lower:]'<<<${PLATFORM})" --test-specs="${TEST_SPEC}" -fi - -find /Users/runner/Library/Developer/Xcode/DerivedData -type d -regex ".*/.*\.xcresult" -execdir cp -R '{}' "${OUTPUT_PATH}" \; diff --git a/scripts/health_metrics/post_incremental_coverage_in_pr.rb b/scripts/health_metrics/post_incremental_coverage_in_pr.rb deleted file mode 100644 index 7e406b4..0000000 --- a/scripts/health_metrics/post_incremental_coverage_in_pr.rb +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright 2021 Google LLC -# -# 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. - -# USAGE: git diff -U0 [base_commit] HEAD | get_diff_lines.sh -# -# This will generate a JSON output of changed files and their newly added -# lines. - -require 'octokit' -require 'json' - -COMMENT_HEADER = "### Incremental code coverage report" -REMOVE_PATTERN = /### Incremental code coverage report/ -REPO = ENV['GITHUB_REPOSITORY'] -GITHUB_WORKFLOW_URL = "https://github.com/#{REPO}/actions/runs/#{ENV['GITHUB_RUN_ID']}" -UNCOVERED_LINE_FILE = ENV["UNCOVERED_LINE_FILE"] -TESTING_COMMIT = ENV["TESTING_COMMIT"] -PULL_REQUEST = ENV["PULL_REQUEST"].to_i - -client = Octokit::Client.new(access_token: ENV["INPUT_ACCESS_TOKEN"]) -uncovered_files = JSON.parse(File.read(UNCOVERED_LINE_FILE)) - -# Clean comments matching REMOVE_PATTERN. -def clean_coverage_comments(client) - comment_page = 0 - loop do - comment_page += 1 - cur_page_comment = client.pull_request_comments(REPO, PULL_REQUEST, { :per_page =>100, :page => comment_page }) - if cur_page_comment.length == 0 - break - end - for cmt in cur_page_comment do - # Remove comments when the comment body meets the REMOVE_PATTERN. - if cmt.body =~ REMOVE_PATTERN - client.delete_pull_request_comment(REPO,cmt.id) - end - end - end -end - -def generate_comment(comment_header, xcresult_file) - body = "Tests for New code lines are not detected in [#{xcresult_file}](#{GITHUB_WORKFLOW_URL}), please add tests on highlighted lines." - return "#{comment_header} \n #{body}" -end - -def add_coverage_comments(client, uncovered_files) - for changed_file in uncovered_files do - coverage_line = changed_file['coverage'] - xcresult_file = changed_file['xcresultBundle'].split('/').last - start_line = -1 - coverage_line.each_with_index do |line, idx| - # Init start_line to the first uncovered line of a file. - if start_line == -1 - start_line = line - end - if idx < coverage_line.length() && line + 1 == coverage_line[idx+1] - next - else - comment = generate_comment(COMMENT_HEADER, xcresult_file) - if start_line == line - # One line code comment will have nil in start_line and override - # the position param, which is 0 here. The position param is a - # relative number in the `git diff`, instead of a absolute line - # index. - client.create_pull_request_comment(REPO,PULL_REQUEST, comment, TESTING_COMMIT,changed_file['fileName'], 0, {:side=>"RIGHT", :line=>line}) - else - # multiple-line code block comment needs start_line and line options, - # which will override the position param. - client.create_pull_request_comment(REPO,PULL_REQUEST, comment, TESTING_COMMIT,changed_file['fileName'],0, {:side=>"RIGHT", :start_line=>start_line, :line=>line}) - end - start_line = coverage_line[idx+1] - end - end - end -end - -clean_coverage_comments(client) -add_coverage_comments(client, uncovered_files) diff --git a/scripts/install_prereqs.sh b/scripts/install_prereqs.sh deleted file mode 100755 index ee883ff..0000000 --- a/scripts/install_prereqs.sh +++ /dev/null @@ -1,188 +0,0 @@ -#!/usr/bin/env bash - -# Copyright 2018 Google LLC -# -# 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. - -# Within Travis, installs prerequisites for a build. - -# Examines the following configured environment variables that should be -# specified in an env: block -# - PROJECT - Firebase or Firestore -# - METHOD - xcodebuild or cmake; default is xcodebuild - -set -euo pipefail - -# apt_install program package -# -# Installs the given package if the given command is missing -function apt_install() { - local program="$1" - local package="$2" - which "$program" >& /dev/null || sudo apt-get install "$package" -} - -function install_xcpretty() { - gem install xcpretty -} - -# Default values, if not supplied on the command line or environment -platform="iOS" -method="xcodebuild" - -if [[ $# -eq 0 ]]; then - # Take arguments from the environment - project=$PROJECT - platform=${PLATFORM:-${platform}} - method=${METHOD:-${method}} - -else - project="$1" - - if [[ $# -gt 1 ]]; then - platform="$2" - fi - - if [[ $# -gt 2 ]]; then - method="$3" - fi -fi - -echo "Installing prerequisites for $project for $platform using $method" - -if [[ "$method" != "cmake" ]]; then - scripts/setup_bundler.sh -fi - -case "$project-$platform-$method" in - - FirebasePod-iOS-*) - install_xcpretty - bundle exec pod install --project-directory=CoreOnly/Tests/FirebasePodTest --repo-update - ;; - - Auth-*) - # Install the workspace for integration testing. - install_xcpretty - bundle exec pod install --project-directory=FirebaseAuth/Tests/Sample --repo-update - ;; - - Crashlytics-*) - ;; - - CombineSwift-*) - ;; - - Database-*) - ;; - - Functions-*) - # Start server for Functions integration tests. - ./FirebaseFunctions/Backend/start.sh synchronous - ;; - - Storage-*) - ;; - - InAppMessaging-*-xcodebuild) - install_xcpretty - bundle exec pod install --project-directory=FirebaseInAppMessaging/Tests/Integration/DefaultUITestApp --no-repo-update - ;; - - Firestore-*-xcodebuild | Firestore-*-fuzz) - install_xcpretty - - # The Firestore Podfile is multi-platform by default, but this doesn't work - # with command-line builds using xcodebuild. The PLATFORM environment - # variable forces the project to be set for just that single platform. - export PLATFORM="$platform" - bundle exec pod install --project-directory=Firestore/Example --repo-update - ;; - - Firestore-iOS-cmake | Firestore-tvOS-cmake | Firestore-macOS-cmake) - brew outdated cmake || brew upgrade cmake - brew outdated go || brew upgrade go # Somehow the build for Abseil requires this. - brew install ccache - brew install ninja - - # Install python packages required to generate proto sources - pip install six - ;; - - Firestore-Linux-cmake) - apt_install ccache ccache - apt_install cmake cmake - apt_install go golang-go - apt_install ninja ninja-build - - # Install python packages required to generate proto sources - pip install six - ;; - - SymbolCollision-*-*) - install_xcpretty - bundle exec pod install --project-directory=SymbolCollisionTest --repo-update - ;; - - MessagingSample-*) - install_xcpretty - bundle exec pod install --project-directory=FirebaseMessaging/Apps/Sample --repo-update - ;; - - SwiftUISample-*) - install_xcpretty - bundle exec pod install --project-directory=FirebaseMessaging/Apps/SwiftUISample --repo-update - ;; - - MessagingSampleStandaloneWatchApp-*) - install_xcpretty - bundle exec pod install --project-directory=FirebaseMessaging/Apps/SampleStandaloneWatchApp --repo-update - ;; - - MLModelDownloaderSample-*) - install_xcpretty - bundle exec pod install --project-directory=FirebaseMLModelDownloader/Apps/Sample --repo-update - ;; - - RemoteConfigSample-*) - install_xcpretty - bundle exec pod install --project-directory=FirebaseRemoteConfig/Tests/Sample --repo-update - ;; - - WatchOSSample-*) - install_xcpretty - bundle exec pod install --project-directory=Example/watchOSSample --repo-update - ;; - - GoogleDataTransport-watchOS-xcodebuild) - install_xcpretty - bundle exec pod install --project-directory=GoogleDataTransport/GDTWatchOSTestApp/ --repo-update - bundle exec pod install --project-directory=GoogleDataTransport/GDTCCTWatchOSTestApp/ - ;; - - ClientApp-iOS-xcodebuild) - install_xcpretty - bundle exec pod install --project-directory=IntegrationTesting/ClientApp/ --repo-update - ;; - - *-pod-lib-lint) - ;; - - *) - echo "Unknown project-platform-method combo" 1>&2 - echo " PROJECT=$project" 1>&2 - echo " PLATFORM=$platform" 1>&2 - echo " METHOD=$method" 1>&2 - exit 1 - ;; -esac diff --git a/scripts/lib/__init__.py b/scripts/lib/__init__.py deleted file mode 100644 index 6c8f4a6..0000000 --- a/scripts/lib/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2019 Google -# -# 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. diff --git a/scripts/lib/checker.py b/scripts/lib/checker.py deleted file mode 100644 index 2168e42..0000000 --- a/scripts/lib/checker.py +++ /dev/null @@ -1,122 +0,0 @@ -# Copyright 2019 Google LLC -# -# 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. - -from __future__ import division - -import math -import multiprocessing.pool -import sys -import threading - -# Python 3 renamed Queue to queue -try: - import queue -except ImportError: - import Queue as queue - - -_TASKS = multiprocessing.cpu_count() - - -_output_lock = threading.Lock() - - -def shard(items): - """Breaks down the given items into roughly equal sized lists. - - The number of lists will be equal to the number of available processor cores. - """ - if not items: - return [] - - n = int(math.ceil(len(items) / _TASKS)) - return _chunks(items, n) - - -def _chunks(items, n): - """Yield successive n-sized chunks from items.""" - for i in range(0, len(items), n): - yield items[i:i + n] - - -class Result(object): - - def __init__(self, num_errors, output): - self.errors = num_errors - self.output = (output - if isinstance(output, str) - else output.decode('utf8', errors='replace')) - - @staticmethod - def from_list(errors): - return Result(len(errors), '\n'.join(errors)) - - -class Pool(object): - - def __init__(self): - # Checkers submit tasks to be run and these are dropped in the _pending - # queue. Workers process that queue and results are put in the _results - # queue. _results is drained by the thread that calls join(). - self._pending = queue.Queue() - self._results = queue.Queue() - - def worker(): - while True: - task, args = self._pending.get() - result = task(*args) - if result is not None: - self._results.put(result) - self._pending.task_done() - - for i in range(_TASKS): - t = threading.Thread(target=worker) - t.daemon = True - t.start() - - def submit(self, task, *args): - """Submits a task for execution by the pool. - - Args: - task: A callable routine that will perform the work. - *args: A list of arguments to pass that routine. - """ - self._pending.put((task, args)) - - def join(self): - """Waits for the completion of all submitted tasks. - - Returns: - The number of errors encountered. - """ - self._pending.join() - - num_errors = 0 - while not self._results.empty(): - result = self._results.get() - num_errors += result.errors - sys.stdout.write(result.output) - self._results.task_done() - - self._results.join() - return num_errors - - def exit(self): - """Waits for the completion of the submitted tasks and exits. - - This calls join() and then exits with a 0 status code if there were no - errors, or 1 if there were. - """ - errors = self.join() - sys.exit(errors > 0) diff --git a/scripts/lib/command_trace.py b/scripts/lib/command_trace.py deleted file mode 100644 index c443af8..0000000 --- a/scripts/lib/command_trace.py +++ /dev/null @@ -1,79 +0,0 @@ -# Copyright 2019 Google -# -# 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. - -import logging - -from lib import terminal - -_commands = logging.getLogger('commands') - - -def log(command_args): - """Logs that a command has run. - - Args: - command_args: A list of the command and its arguments. - """ - if _commands.isEnabledFor(logging.DEBUG): - columns = terminal.columns() - - text = ' '.join(command_args) - - # When just passing --trace, shorten output to the width of the current - # window. When running extra verbose don't shorten. - if not logging.root.isEnabledFor(logging.INFO): - if len(text) >= columns: - text = text[0:columns - 5] + ' ...' - - _commands.debug('%s', text) - - -def add_arguments(parser): - """Adds standard arguments to the given ArgumentParser.""" - parser.add_argument('--trace', action='store_true', - help='show commands') - parser.add_argument('--verbose', '-v', action='count', default=0, - help='run verbosely') - - -def enable_tracing(): - """Enables tracing of command execution.""" - _commands.setLevel(logging.DEBUG) - - -def setup(args): - """Prepares for tracing/verbosity based on the given parsed arguments.""" - level = logging.WARN - - if args.trace: - enable_tracing() - - if args.verbose >= 2: - level = logging.DEBUG - elif args.verbose >= 1: - level = logging.INFO - - logging.basicConfig(format='%(message)s', level=level) - - -def parse_args(parser): - """Shortcut that adds arguments, parses, and runs setup. - - Returns: - The args result from parser.parse_args(). - """ - add_arguments(parser) - args = parser.parse_args() - setup(args) - return args diff --git a/scripts/lib/git.py b/scripts/lib/git.py deleted file mode 100644 index 9c155bc..0000000 --- a/scripts/lib/git.py +++ /dev/null @@ -1,144 +0,0 @@ -# Copyright 2019 Google LLC -# -# 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. - -import os -import subprocess - -from lib import command_trace -from lib import source - - -def find_changed_or_files(all, rev_or_files, patterns): - """Finds files. - - Args: - all: Force finding all files. - rev_or_files: A single revision, a list of files, or empty. - patterns: A list of git matching patterns - - Returns: - Files that match. - - If rev_or_files is a single revision, the result is all files that match - the patterns that have changed since the revision. - - If rev_or_files is a list of files, the result is all the files that match - that list of files. The files can be patterns. - - If rev_or_files is empty, the result is all the files that match patterns. - """ - if all: - return find_files(patterns) - - if not rev_or_files: - return find_changed('origin/main', patterns) - - if len(rev_or_files) == 1 and is_revision(rev_or_files[0]): - return find_changed(rev_or_files[0], patterns) - else: - return find_files(rev_or_files) - - -def is_revision(word): - """Returns true if the given word is a revision name according to git.""" - command = ['git', 'rev-parse', word, '--'] - with open(os.devnull, 'w') as dev_null: - command_trace.log(command) - rc = subprocess.call(command, stdout=dev_null, stderr=dev_null) - return rc == 0 - - -def find_changed(revision, patterns): - """Finds files changed since a revision.""" - - # Always include -- indicate that revision is known to be a revision, even - # if no patterns follow. - command = ['git', 'diff', '-z', '--name-only', '--diff-filter=ACMR', - revision, '--'] - command.extend(patterns) - command.extend(standard_exclusions()) - return _null_split_output(command) - - -def find_files(patterns=None): - """Finds files matching the given patterns using git ls-files.""" - command = ['git', 'ls-files', '-z', '--'] - if patterns: - command.extend(patterns) - command.extend(standard_exclusions()) - return _null_split_output(command) - - -def find_lines_matching(pattern, sources=None): - command = [ - 'git', 'grep', - '-n', # show line numbers - '-I', # exclude binary files - pattern, - '--' - ] - if sources: - command.extend(sources) - command.extend(standard_exclusions()) - - command_trace.log(command) - - bufsize = 4096 - proc = subprocess.Popen(command, bufsize=bufsize, stdout=subprocess.PIPE) - result = [] - try: - while proc.poll() is None: - result.append(proc.stdout.read(bufsize)) - except KeyboardInterrupt: - proc.terminate() - proc.wait() - - return b''.join(result).decode('utf8', errors='replace') - - -def make_patterns(dirs): - """Returns a list of git match patterns for the given directories.""" - return ['%s/**' % d for d in dirs] - - -def make_exclusions(dirs): - return [':(exclude)' + d for d in dirs] - - -def standard_exclusions(): - result = make_exclusions(source.IGNORE) - result.append(':(exclude)**/third_party/**') - return result - - -def is_within_repo(): - """Returns whether the current working directory is within a git repo.""" - try: - subprocess.check_output(['git', 'status']) - return True - except subprocess.CalledProcessError: - return False - - -def get_repo_root(): - """Returns the absolute path to the root of the current git repo.""" - command = ['git', 'rev-parse', '--show-toplevel'] - return subprocess.check_output(command, text=True, errors='replace').rstrip() - - -def _null_split_output(command): - """Runs the given command and splits its output on the null byte.""" - command_trace.log(command) - result = subprocess.check_output(command, text=True, errors='replace') - return [name for name in result.rstrip().split('\0') if name] diff --git a/scripts/lib/source.py b/scripts/lib/source.py deleted file mode 100644 index a3c6826..0000000 --- a/scripts/lib/source.py +++ /dev/null @@ -1,246 +0,0 @@ -# Copyright 2019 Google -# -# 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. - -import fnmatch -import logging -import os -import re -import textwrap - -from lib import command_trace - - -# Paths under which all files should be ignored -IGNORE = frozenset([ - 'Firestore/Protos/nanopb', - 'Firestore/Protos/cpp', - 'Firestore/Protos/objc', - 'Firestore/third_party/abseil-cpp', -]) - -FIRESTORE_CORE = ['Firestore/core'] -FIRESTORE_OBJC = ['Firestore/Source', 'Firestore/Example/Tests'] -FIRESTORE_SWIFT = ['Firestore/Swift'] - -FIRESTORE_TESTS = ['Firestore/core/test', 'Firestore/Example/Tests'] - -CC_DIRS = FIRESTORE_CORE -CC_EXTENSIONS = ['.h', '.cc'] - -OBJC_DIRS = FIRESTORE_CORE + FIRESTORE_OBJC -OBJC_EXTENSIONS = ['.h', '.m', '.mm'] - -PYTHON_DIRS = ['scripts'] -PYTHON_EXTENSIONS = ['.py'] - -SOURCE_EXTENSIONS = [ - '.c', - '.cc', - '.cmake', - '.h', - '.js', - '.m', - '.mm', - '.py', - '.rb', - '.sh', - '.swift' -] - -_DEFINITE_EXTENSIONS = { - '.cc': 'cc', - '.m': 'objc', - '.mm': 'objc', - '.py': 'py', -} - - -_classify_logger = logging.getLogger('lint.classify') - - -class LanguageBreakdown: - """Files broken down by source language.""" - - def __init__(self): - self.cc = [] - self.objc = [] - self.py = [] - self.all = [] - - self.kinds = { - 'cc': self.cc, - 'objc': self.objc, - 'py': self.py, - } - - def classify(self, kind, reason, filename): - _classify_logger.debug('classify %s: %s (%s)' % (kind, filename, reason)) - self.kinds[kind].append(filename) - self.all.append(filename) - - @staticmethod - def ignore(filename): - _classify_logger.debug('classify ignored: %s' % filename) - - -def categorize_files(files): - """Breaks down the given list of files by language. - - Args: - files: a list of files - - Returns: - A LanguageBreakdown instance containing all the files that match a - recognized source language. - """ - result = LanguageBreakdown() - - for filename in files: - if _in_directories(filename, IGNORE): - continue - - ext = os.path.splitext(filename)[1] - definite = _DEFINITE_EXTENSIONS.get(ext) - if definite: - result.classify(definite, 'extension', filename) - continue - - if ext == '.h': - if _in_directories(filename, CC_DIRS): - # If a header exists in the C++ core, ignore related files. Some classes - # may transiently have an implementation in a .mm file, but hold the - # header to the higher standard: the implementation should eventually - # be in a .cc, otherwise the file doesn't belong in the core. - result.classify('cc', 'directory', filename) - continue - - related_ext = _related_file_ext(filename) - if related_ext == '.cc': - result.classify('cc', 'related file', filename) - continue - - if related_ext in ('.m', '.mm'): - result.classify('objc', 'related file', filename) - continue - - if _in_directories(filename, OBJC_DIRS): - result.classify('objc', 'directory', filename) - continue - - raise NotImplementedError(textwrap.dedent( - """ - Don't know how to handle the header %s. - - If C++ add a parent directory to CC_DIRS in lib/source.py. - - If Objective-C add to OBJC_DIRS or consider changing the default here - and removing this exception.""" % filename)) - - result.ignore(filename) - - return result - - -def shard(group, num_shards): - """Breaks the group apart into num_shards shards. - - Args: - group: a breakdown, perhaps returned from categorize_files. - num_shards: The number of shards into which to break down the group. - - Returns: - A list of shards. - """ - shards = [] - for i in range(num_shards): - shards.append(LanguageBreakdown()) - - pos = 0 - for kind, files in group.kinds.items(): - for filename in files: - shards[pos].kinds[kind].append(filename) - pos = (pos + 1) % num_shards - - return shards - - -_PLUS = re.compile(r'\+.*') - - -def _related_file_ext(header): - """Returns the dominant extension among related files. - - A file is related if it starts with the same prefix. Prefix is the basename - without extension, and stripping off any + category names that are common in - Objective-C. - - For example: executor.h has related files executor_std.cc and - executor_libdispatch.mm. - - If there are multiple related files, the implementation chooses one based - on which language is most restrictive. That is, if a header serves both C++ - and Objective-C++ implementations, lint the header as C++ to prevent issues - that might arise in that mode. - - Returns: - The file extension (e.g. '.cc') - """ - parent = os.path.dirname(header) - basename = os.path.basename(header) - - root = os.path.splitext(basename)[0] - root = _PLUS.sub('', root) - root = os.path.join(parent, root) - - files = _related_files(root) - exts = {os.path.splitext(f)[1] for f in files} - - for ext in ('.cc', '.m', '.mm'): - if ext in exts: - return ext - return None - - -def _related_files(root): - """Returns a list of files related to the given root. - """ - parent = os.path.dirname(root) - if not parent: - # dirname returns empty for filenames that are already a basename. - parent = '.' - - pattern = os.path.basename(root) + '*' - return fnmatch.filter(_list_files(parent), pattern) - - -def _list_files(parent): - """Lists files contained directly in the parent directory.""" - result = _list_files.cache.get(parent) - if result is None: - command_trace.log(['ls', parent]) - result = os.listdir(parent) - _list_files.cache[parent] = result - return result - - -_list_files.cache = {} - - -def _in_directories(filename, dirs): - """Tests whether `filename` is anywhere in any of the given dirs.""" - for dirname in dirs: - if (filename.startswith(dirname) - and (len(filename) == len(dirname) or filename[len(dirname)] == '/')): - return True - return False diff --git a/scripts/lib/terminal.py b/scripts/lib/terminal.py deleted file mode 100644 index cbf6c16..0000000 --- a/scripts/lib/terminal.py +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright 2019 Google -# -# 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. - -import subprocess -import threading - - -_lock = threading.Lock() -_columns = None - - -def columns(): - """Returns the number of columns in the terminal's display.""" - - global _columns - with _lock: - if _columns is None: - _columns = _find_terminal_columns() - return _columns - - -def _find_terminal_columns(): - try: - result = subprocess.check_output(['tput', 'cols']) - return int(result.rstrip()) - except subprocess.CalledProcessError: - return 80 diff --git a/scripts/lldb/firestore.py b/scripts/lldb/firestore.py deleted file mode 100644 index 70b9c3d..0000000 --- a/scripts/lldb/firestore.py +++ /dev/null @@ -1,256 +0,0 @@ -# Copyright 2020 Google LLC -# -# 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. - -import ast -import json - -""" -LLDB type summary providers for common Firestore types. - -This is primarily useful for debugging Firestore internals. It will add useful -summaries and consolidate common types in a way that makes them easier to -observe in the debugger. - -Use this by adding the following to your ~/.lldbinit file: - - command script import ~/path/to/firebase-ios-sdk/scripts/lldb/firestore.py - -Most of this implementation is based on "Variable Formatting" in the LLDB online -manual: https://lldb.llvm.org/use/variable.html. There are two major features -we're making use of: - - * Summary Providers: these are classes or functions that take an object and - produce a (typically one line) summary of the type - - * Synthetic Children Providers: these are classes that provide an alternative - view of the data. The children that are synthesized here show up in the - graphical debugger. -""" - - -class ForwardingSynthProvider(object): - """A synthetic child provider that forwards all methods to another provider. - - Override the `delegate` method to customize the target to which this forwards. - """ - - def __init__(self, value, params): - self.value = value - - def delegate(self): - return self.value - - def has_children(self): - return self.delegate().MightHaveChildren() - - def num_children(self): - return self.delegate().GetNumChildren() - - def get_child_index(self, name): - return self.delegate().GetIndexOfChildWithName(name) - - def get_child_at_index(self, index): - return self.delegate().GetChildAtIndex(index) - - def update(self): - # No additional state so nothing needs updating when the value changes. - pass - - -# Abseil - -class AbseilOptional_SynthProvider(object): - """A synthetic child provider that hides the internals of absl::optional. - """ - - def __init__(self, value, params): - self.value = value - self.engaged = None - self.data = None - - def update(self): - # Unwrap all the internal optional_data and similar types - value = self.value - while True: - if value.GetNumChildren() <= 0: - break - - child = value.GetChildAtIndex(0) - if not child.IsValid(): - break - - if 'optional_internal' not in child.GetType().GetName(): - break - - value = child - - # value should now point to the innermost absl::optional container type. - self.engaged = value.GetChildMemberWithName('engaged_') - - if self.has_children(): - self.data = value.GetChildMemberWithName('data_') - - else: - self.data = None - - def has_children(self): - return self.engaged.GetValueAsUnsigned(0) != 0 - - def num_children(self): - return 2 if self.has_children() else 1 - - def get_child_index(self, name): - if name == 'engaged_': - return 0 - if name == 'data_': - return 1 - return -1 - - def get_child_at_index(self, index): - if index == 0: - return self.engaged - if index == 1: - return self.data - - -def AbseilOptional_SummaryProvider(value, params): - # Operates on the synthetic children above, calling has_children. - return 'engaged={0}'.format(format_bool(value.MightHaveChildren())) - - -# model - -class DatabaseId_SynthProvider(ForwardingSynthProvider): - """Makes DatabaseId behave as if `*rep_` were inline, hiding its - `shared_ptr` implementation details. - """ - def delegate(self): - return deref_shared(self.value.GetChildMemberWithName('rep_')) - - -def DatabaseId_SummaryProvider(value, params): - # Operates on the result of the SynthProvider; value is *rep_. - parts = [ - get_string(value.GetChildMemberWithName('project_id')), - get_string(value.GetChildMemberWithName('database_id')) - ] - return format_string('/'.join(parts)) - - -def DocumentKey_SummaryProvider(value, params): - """Summarizes DocumentKey as if path_->segments_ were inline and a single, - slash-delimited string like `"users/foo"`. - """ - return deref_shared(value.GetChildMemberWithName('path_')).GetSummary() - - -def ResourcePath_SummaryProvider(value, params): - """Summarizes ResourcePath as if segments_ were a single string, - slash-delimited string like `"users/foo"`. - """ - segments = value.GetChildMemberWithName('segments_') - segment_text = [get_string(child) for child in segments] - return format_string('/'.join(segment_text)) - - -# api - -def DocumentReference_SummaryProvider(value, params): - """Summarizes DocumentReference as a single slash-delimited string like - `"users/foo"`. - """ - return value.GetChildMemberWithName('key_').GetSummary() - - -def DocumentSnapshot_SummaryProvider(value, params): - """Summarizes DocumentSnapshot as a single slash-delimited string like - `"users/foo"` that names the path of the document in the snapshot. - """ - return value.GetChildMemberWithName('internal_key_').GetSummary() - - -# Objective-C - -def FIRDocumentReference_SummaryProvider(value, params): - return value.GetChildMemberWithName('_documentReference').GetSummary() - - -def FIRDocumentSnapshot_SummaryProvider(value, params): - return value.GetChildMemberWithName('_snapshot').GetSummary() - - -def get_string(value): - """Returns a Python string from the underlying LLDB SBValue.""" - # TODO(wilhuff): Actually use the SBData API to get this. - # Get the summary as a C literal and parse it (for now). Using the SBData - # API would allow this to directly read the string contents. - summary = value.GetSummary() - return ast.literal_eval(summary) - - -def format_string(string): - """Formats a Python string as a C++ string literal.""" - # JSON and C escapes work the ~same. - return json.dumps(string) - - -def format_bool(value): - """Formats a Python value as a C++ bool literal.""" - return 'true' if value else 'false' - - -def deref_shared(value): - """Dereference a shared_ptr.""" - return value.GetChildMemberWithName('__ptr_').Dereference() - - -def __lldb_init_module(debugger, params): - def run(command): - debugger.HandleCommand(command) - - def add_summary(provider, typename, *args): - args = ' '.join(args) - run('type summary add -w firestore -F {0} {1} {2}'.format( - qname(provider), args, typename)) - - def add_synthetic(provider, typename, *args): - args = ' '.join(args) - run('type synthetic add -l {0} -w firestore {1} {2}'.format( - qname(provider), args, typename)) - - optional_matcher = '-x absl::[^:]*::optional<.*>' - add_summary(AbseilOptional_SummaryProvider, optional_matcher, '-e') - add_synthetic(AbseilOptional_SynthProvider, optional_matcher) - - api = 'firebase::firestore::api::' - add_summary(DocumentReference_SummaryProvider, api + 'DocumentReference') - add_summary(DocumentSnapshot_SummaryProvider, api + 'DocumentSnapshot', '-e') - - model = 'firebase::firestore::model::' - add_summary(DocumentKey_SummaryProvider, model + 'DocumentKey') - add_summary(ResourcePath_SummaryProvider, model + 'ResourcePath') - - add_summary(DatabaseId_SummaryProvider, model + 'DatabaseId') - add_synthetic(DatabaseId_SynthProvider, model + 'DatabaseId') - - add_summary(FIRDocumentReference_SummaryProvider, 'FIRDocumentReference') - - add_summary(FIRDocumentSnapshot_SummaryProvider, 'FIRDocumentSnapshot', '-e') - - run('type category enable firestore') - - -def qname(fn): - """Returns the module-qualified name of the given class or function.""" - return '{0}.{1}'.format(__name__, fn.__name__) diff --git a/scripts/localize_podfile.swift b/scripts/localize_podfile.swift deleted file mode 100755 index 8b60a2c..0000000 --- a/scripts/localize_podfile.swift +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/swift - -/* - * Copyright 2019 Google - * - * 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. - */ - -// Modify a Podfile to get any podspecs defined in the firebase-ios-sdk repo locally. - -import Foundation - -let arg_cnts: Int = .init(CommandLine.argc) - -let podfile = CommandLine.arguments[1] - -var releaseTesting = false - -if arg_cnts > 2 { - releaseTesting = CommandLine.arguments[1 ..< arg_cnts].contains("release_testing") -} - -// Always add these, since they may not be in the Podfile, but we still want the local -// versions when they're dependencies of other requested local pods. -let implicitPods = [ - "FirebaseCore", "FirebaseInstallations", "Firebase", - "FirebaseAuth", "FirebaseABTesting", - "FirebaseRemoteConfig", "FirebaseCoreExtension", - "FirebaseAppCheckInterop", "FirebaseAuthInterop", - "FirebaseMessagingInterop", "FirebaseCoreInternal", - "FirebaseSessions", "FirebaseSharedSwift", - "FirebaseRemoteConfigInterop", -] - -let binaryPods = [ - "GoogleAppMeasurement", - "GoogleAppMeasurementOnDeviceConversion", - "FirebaseAnalytics", -] - -var didImplicits = false - -var fileContents = "" -do { - fileContents = try String(contentsOfFile: podfile, encoding: .utf8) -} catch { - fatalError("Could not read \(podfile). \(error)") -} - -// Search the path upwards to find the root of the firebase-ios-sdk repo. -var url = URL(fileURLWithPath: FileManager().currentDirectoryPath) -while url.path != "/", url.lastPathComponent != "firebase-ios-sdk" { - url = url.deletingLastPathComponent() -} - -let repo = url -let lines = fileContents.components(separatedBy: .newlines) -var outBuffer = - "source 'https://github.com/firebase/SpecsDev.git'\n" + - "source 'https://github.com/firebase/SpecsStaging.git'\n" + - "source 'https://cdn.cocoapods.org/'\n" -for line in lines { - var newLine = line.trimmingCharacters(in: .whitespacesAndNewlines) - let tokens = newLine.components(separatedBy: [" ", ","] as CharacterSet) - if tokens.first == "pod", !releaseTesting { - let podNameRaw = String(tokens[1]).replacingOccurrences(of: "'", with: "") - var podName = podNameRaw - - // Firebase test Podfile's often use the Firebase subspec to access the - // desired pod. We want to get the requested pod directly. - if podNameRaw.starts(with: "Firebase/") { - podName = podName.replacingOccurrences(of: "Firebase/", with: "Firebase") - } - let podspec = repo.appendingPathComponent(podName + ".podspec").path - if !binaryPods.contains(podName), FileManager().fileExists(atPath: podspec) { - if didImplicits == false { - didImplicits = true - for implicit in implicitPods { - let implicitPodspec = repo.appendingPathComponent(implicit + ".podspec").path - outBuffer += "pod '\(implicit)', :path => '\(implicitPodspec)'\n" - } - } - newLine = "pod '\(podName)', :path => '\(podspec)'" - } else if podNameRaw.starts(with: "Firebase/") { - // Update closed source pods referenced via a subspec from the Firebase pod. - let firebasePodspec = repo.appendingPathComponent("Firebase.podspec").path - newLine = "pod '\(podNameRaw)', :path => '\(firebasePodspec)'" - } - } - outBuffer += newLine + "\n" -} - -// Write out the changed file. -do { - try outBuffer.write(toFile: podfile, atomically: false, encoding: String.Encoding.utf8) -} catch { - fatalError("Failed to write \(podfile). \(error)") -} diff --git a/scripts/make_release_notes.py b/scripts/make_release_notes.py deleted file mode 100755 index b6bd71e..0000000 --- a/scripts/make_release_notes.py +++ /dev/null @@ -1,285 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2019 Google LLC -# -# 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. - -"""Converts GitHub flavored markdown changelogs to release notes. -""" - -import argparse -import re -import subprocess -import string - -NO_HEADING = 'PRODUCT HAS NO HEADING' - - -PRODUCTS = { - 'FirebaseABTesting/CHANGELOG.md': '{{ab_testing}}', - 'FirebaseAppCheck/CHANGELOG.md': 'App Check', - 'FirebaseAppDistribution/CHANGELOG.md': 'App Distribution', - 'FirebaseAuth/CHANGELOG.md': '{{auth}}', - 'FirebaseCore/CHANGELOG.md': NO_HEADING, - 'Crashlytics/CHANGELOG.md': '{{crashlytics}}', - 'FirebaseDatabase/CHANGELOG.md': '{{database}}', - 'FirebaseDynamicLinks/CHANGELOG.md': '{{ddls}}', - 'FirebaseInAppMessaging/CHANGELOG.md': '{{inapp_messaging}}', - 'FirebaseInstallations/CHANGELOG.md': 'Installations', - 'FirebaseMessaging/CHANGELOG.md': '{{messaging}}', - 'FirebaseStorage/CHANGELOG.md': '{{storage}}', - 'Firestore/CHANGELOG.md': '{{firestore}}', - 'FirebaseFunctions/CHANGELOG.md': '{{cloud_functions}}', - 'FirebaseRemoteConfig/CHANGELOG.md': '{{remote_config}}', - 'FirebasePerformance/CHANGELOG.md': '{{perfmon}}', -} - - -def main(): - local_repo = find_local_repo() - - parser = argparse.ArgumentParser(description='Create release notes.') - parser.add_argument('--repo', '-r', default=local_repo, - help='Specify which GitHub repo is local.') - parser.add_argument('--only', metavar='VERSION', - help='Convert only a specific version') - parser.add_argument('--all', action='store_true', - help='Emits entries for all versions') - parser.add_argument('changelog', - help='The CHANGELOG.md file to parse') - args = parser.parse_args() - - if args.all: - text = read_file(args.changelog) - else: - text = read_changelog_section(args.changelog, args.only) - - product = None - if not args.all: - product = PRODUCTS.get(args.changelog) - - renderer = Renderer(args.repo, product) - translator = Translator(renderer) - - result = translator.translate(text) - print(result) - - -def find_local_repo(): - url = subprocess.check_output(['git', 'config', '--get', 'remote.origin.url'], - text=True, errors='replace') - - # ssh or https style URL - m = re.match(r'^(?:git@github\.com:|https://github\.com/)(.*)\.git$', url) - if m: - return m.group(1) - - raise LookupError('Can\'t figure local repo from remote URL %s' % url) - - -CHANGE_TYPE_MAPPING = { - 'added': 'feature' -} - - -class Renderer(object): - - def __init__(self, local_repo, product): - self.local_repo = local_repo - self.product = product - - def heading(self, heading): - if self.product: - if self.product == NO_HEADING: - return '' - else: - return '### %s\n' % self.product - - return heading - - def bullet(self, spacing): - """Renders a bullet in a list. - - All bulleted lists in devsite are '*' style. - """ - return '%s* ' % spacing - - def change_type(self, tag): - """Renders a change type tag as the appropriate double-braced macro. - - That is "[fixed]" is rendered as "{{fixed}}". - """ - tag = CHANGE_TYPE_MAPPING.get(tag, tag) - return '{{%s}}' % tag - - def url(self, url): - m = re.match(r'^(?:https:)?(//github.com/(.*)/issues/(\d+))$', url) - if m: - link = m.group(1) - repo = m.group(2) - issue = m.group(3) - - if repo == self.local_repo: - text = '#' + issue - else: - text = repo + '#' + issue - - return '[%s](%s)' % (text, link) - - return url - - def local_issue_link(self, issues): - """Renders a local issue link as a proper markdown URL. - - Transforms (#1234, #1235) into - ([#1234](//github.com/firebase/firebase-ios-sdk/issues/1234), - [#1235](//github.com/firebase/firebase-ios-sdk/issues/1235)). - """ - issue_link_list = [] - issue_list = issues.split(", ") - translate = str.maketrans('', '', string.punctuation) - for issue in issue_list: - issue = issue.translate(translate) - link = '//github.com/%s/issues/%s' % (self.local_repo, issue) - issue_link_list.append('[#%s](%s)' % (issue, link)) - return "(" + ", ".join(issue_link_list) + ")" - - def text(self, text): - """Passes through any other text.""" - return text - - -class Translator(object): - def __init__(self, renderer): - self.renderer = renderer - - def translate(self, text): - result = '' - while text: - for key in self.rules: - rule = getattr(self, key) - m = rule.match(text) - if not m: - continue - - callback = getattr(self, 'parse_' + key) - callback_result = callback(m) - result += callback_result - - text = text[len(m.group(0)):] - break - - return result - - heading = re.compile( - r'^#{1,6} .*' - ) - - def parse_heading(self, m): - return self.renderer.heading(m.group(0)) - - bullet = re.compile( - r'^(\s*)[*+-] ' - ) - - def parse_bullet(self, m): - return self.renderer.bullet(m.group(1)) - - change_type = re.compile( - r'\[' # opening square bracket - r'(\w+)' # tag word (like "feature" or "changed") - r'\]' # closing square bracket - r'(?!\()' # not followed by opening paren (that would be a link) - ) - - def parse_change_type(self, m): - return self.renderer.change_type(m.group(1)) - - url = re.compile(r'^(https?://[^\s<]+[^<.,:;"\')\]\s])') - - def parse_url(self, m): - return self.renderer.url(m.group(1)) - - local_issue_link = re.compile( - r'\(' # opening paren - r'(#(\d+)(, )?)+' # list of hash and issue number, comma-delimited - r'\)' # closing paren - ) - - def parse_local_issue_link(self, m): - return self.renderer.local_issue_link(m.group(0)) - - text = re.compile( - r'^[\s\S]+?(?=[(\[\n]|https?://|$)' - ) - - def parse_text(self, m): - return self.renderer.text(m.group(0)) - - rules = [ - 'heading', 'bullet', 'change_type', 'url', 'local_issue_link', 'text' - ] - - -def read_file(filename): - """Reads the contents of the file as a single string.""" - with open(filename, 'r') as fd: - return fd.read() - - -def read_changelog_section(filename, single_version=None): - """Reads a single section of the changelog from the given filename. - - If single_version is None, reads the first section with a number in its - heading. Otherwise, reads the first section with single_version in its - heading. - - Args: - - single_version: specifies a string to look for in headings. - - Returns: - A string containing the heading and contents of the heading. - """ - with open(filename, 'r') as fd: - # Discard all lines until we see a heading that either has the version the - # user asked for or any version. - if single_version: - initial_heading = re.compile(r'^#{1,6} .*%s' % re.escape(single_version)) - else: - initial_heading = re.compile(r'^#{1,6} ([^\d]*)\d') - - heading = re.compile(r'^#{1,6} ') - - initial = True - result = [] - for line in fd: - if initial: - if initial_heading.match(line): - initial = False - result.append(line) - - else: - if heading.match(line): - break - - result.append(line) - - # Prune extra newlines - while result and result[-1] == '\n': - result.pop() - - return ''.join(result) - - -if __name__ == '__main__': - main() diff --git a/scripts/nanopb/generate_protos.sh b/scripts/nanopb/generate_protos.sh deleted file mode 100755 index 4170001..0000000 --- a/scripts/nanopb/generate_protos.sh +++ /dev/null @@ -1,94 +0,0 @@ -#!/bin/bash - -# Copyright 2022 Google LLC -# -# 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. -# - -# Example usage: -# ./build_protos - -# Dependencies: git, protobuf, python-protobuf, pyinstaller - -readonly DIR="$( git rev-parse --show-toplevel )" - -# Current release of nanopb being used to build the CCT protos -readonly NANOPB_VERSION="0.3.9.9" -readonly NANOPB_TEMPDIR="${DIR}/scripts/nanopb/nanopb_temp" - -readonly PROTO_DIR="$1" -readonly PROTOGEN_DIR="$2" -readonly INCLUDE_PREFIX="$3" - -echoColor() { - COLOR='\033[0;35m' - NC='\033[0m' - printf "${COLOR}$1${NC}\n" -} - -echoRed() { - RED='\033[0;31m' - NC='\033[0m' - printf "${RED}$1${NC}\n" -} - -cleanup_and_exit() { - rm -rf "${NANOPB_TEMPDIR}" - exit -} - -rm -rf "${NANOPB_TEMPDIR}" - -echoColor "Downloading nanopb..." -git clone --branch "${NANOPB_VERSION}" https://github.com/nanopb/nanopb.git "${NANOPB_TEMPDIR}" - -echoColor "Building nanopb..." -pushd "${NANOPB_TEMPDIR}" -./tools/make_mac_package.sh -GIT_DESCRIPTION=`git describe --always`-macosx-x86 -NANOPB_BIN_DIR="dist/${GIT_DESCRIPTION}" -popd - -echoColor "Removing existing protos..." -rm -rf "${PROTOGEN_DIR}/*" - -echoColor "Generating protos..." - -if ! command -v python &> /dev/null -then - echoRed "" - echoRed "'python' command not found." - echoRed "You may be able to resolve this by installing python with homebrew and linking 'python' to 'python3'. Eg. run:" - echoRed "" - echoRed '$ brew install python && cd $(dirname $(which python3)) && ln -s python3 python' - echoRed "" - cleanup_and_exit -fi - -python "${DIR}/scripts/nanopb/proto_generator.py" \ - --nanopb \ - --protos_dir="${PROTO_DIR}" \ - --pythonpath="${NANOPB_TEMPDIR}/${NANOPB_BIN_DIR}/generator" \ - --output_dir="${PROTOGEN_DIR}" \ - --include="${PROTO_DIR}" \ - --include_prefix="${INCLUDE_PREFIX}" - -RED='\033[0;31m' -NC='\033[0m' -echoRed "" -echoRed "" -echoRed -e "Important: Any new proto fields of type string, repeated, or bytes must be specified in the proto's .options file with type:FT_POINTER" -echoRed "" -echoRed "" - -cleanup_and_exit diff --git a/scripts/nanopb/nanopb_objc_generator.py b/scripts/nanopb/nanopb_objc_generator.py deleted file mode 100755 index cec73ee..0000000 --- a/scripts/nanopb/nanopb_objc_generator.py +++ /dev/null @@ -1,222 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2022 Google LLC -# -# 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. - -"""Generates and massages protocol buffer outputs. -""" - -from __future__ import print_function - -import sys - -import io -import nanopb_generator as nanopb -import os -import os.path -import shlex - -from google.protobuf.descriptor_pb2 import FieldDescriptorProto - -# The plugin_pb2 package loads descriptors on import, but doesn't defend -# against multiple imports. Reuse the plugin package as loaded by the -# nanopb_generator. -plugin_pb2 = nanopb.plugin_pb2 -nanopb_pb2 = nanopb.nanopb_pb2 - - -def main(): - # Parse request - data = io.open(sys.stdin.fileno(), 'rb').read() - request = plugin_pb2.CodeGeneratorRequest.FromString(data) - - # Preprocess inputs, changing types and nanopb defaults - options = nanopb_parse_options(request) - use_anonymous_oneof(request) - use_bytes_for_strings(request) - - # Generate code - parsed_files = nanopb_parse_files(request, options) - results = nanopb_generate(request, options, parsed_files) - response = nanopb_write(results) - - # Write to stdout - io.open(sys.stdout.fileno(), 'wb').write(response.SerializeToString()) - - -def use_anonymous_oneof(request): - """Use anonymous unions for oneofs if they're the only one in a message. - - Equivalent to setting this option on messages where it applies: - - option (nanopb).anonymous_oneof = true; - - Args: - request: A CodeGeneratorRequest from protoc. The descriptors are modified - in place. - """ - for _, message_type in iterate_messages(request): - if len(message_type.oneof_decl) == 1: - ext = message_type.options.Extensions[nanopb_pb2.nanopb_msgopt] - ext.anonymous_oneof = True - - -def use_bytes_for_strings(request): - """Always use the bytes type instead of string. - - By default, nanopb renders proto strings as having the C type char* and does - not include a separate size field, getting the length of the string via - strlen(). Unfortunately this prevents using strings with embedded nulls, - which is something the wire format supports. - - Fortunately, string and bytes proto fields are identical on the wire and - nanopb's bytes representation does have an explicit length, so this function - changes the types of all string fields to bytes. The generated code will now - contain pb_bytes_array_t. - - There's no nanopb or proto option to control this behavior. The equivalent - would be to hand edit all the .proto files :-(. - - Args: - request: A CodeGeneratorRequest from protoc. The descriptors are modified - in place. - """ - for names, message_type in iterate_messages(request): - for field in message_type.field: - if field.type == FieldDescriptorProto.TYPE_STRING: - field.type = FieldDescriptorProto.TYPE_BYTES - - -def iterate_messages(request): - """Iterates over all messages in all files in the request. - - Args: - request: A CodeGeneratorRequest passed by protoc. - - Yields: - names: a nanopb.Names object giving a qualified name for the message - message_type: a DescriptorProto for the message. - """ - for fdesc in request.proto_file: - for names, message_type in nanopb.iterate_messages(fdesc): - yield names, message_type - - -def nanopb_parse_options(request): - """Parses nanopb_generator command-line options from the given request. - - Args: - request: A CodeGeneratorRequest passed by protoc. - - Returns: - Nanopb's options object, obtained via optparser. - """ - # Parse options the same as nanopb_generator.main_plugin() does. - args = shlex.split(request.parameter) - options, _ = nanopb.optparser.parse_args(args) - - # Force certain options - options.extension = '.nanopb' - options.verbose = True - - # Replicate options setup from nanopb_generator.main_plugin. - nanopb.Globals.verbose_options = options.verbose - - # Google's protoc does not currently indicate the full path of proto files. - # Instead always add the main file path to the search dirs, that works for - # the common case. - options.options_path.append(os.path.dirname(request.file_to_generate[0])) - return options - - -def nanopb_parse_files(request, options): - """Parses the files in the given request into nanopb ProtoFile objects. - - Args: - request: A CodeGeneratorRequest, as passed by protoc. - options: The command-line options from nanopb_parse_options. - - Returns: - A dictionary of filename to nanopb.ProtoFile objects, each one - representing the parsed form of a FileDescriptor in the request. - """ - # Process any include files first, in order to have them - # available as dependencies - parsed_files = {} - for fdesc in request.proto_file: - parsed_files[fdesc.name] = nanopb.parse_file(fdesc.name, fdesc, options) - - return parsed_files - - -def nanopb_generate(request, options, parsed_files): - """Generates C sources from the given parsed files. - - Args: - request: A CodeGeneratorRequest, as passed by protoc. - options: The command-line options from nanopb_parse_options. - parsed_files: A dictionary of filename to nanopb.ProtoFile, as returned by - nanopb_parse_files(). - - Returns: - A list of nanopb output dictionaries, each one representing the code - generation result for each file to generate. The output dictionaries have - the following form: - - { - 'headername': Name of header file, ending in .h, - 'headerdata': Contents of the header file, - 'sourcename': Name of the source code file, ending in .c, - 'sourcedata': Contents of the source code file - } - """ - output = [] - - for filename in request.file_to_generate: - for fdesc in request.proto_file: - if fdesc.name == filename: - results = nanopb.process_file( - filename, fdesc, options, parsed_files) - output.append(results) - - return output - - -def nanopb_write(results): - """Translates nanopb output dictionaries to a CodeGeneratorResponse. - - Args: - results: A list of generated source dictionaries, as returned by - nanopb_generate(). - - Returns: - A CodeGeneratorResponse describing the result of the code generation - process to protoc. - """ - response = plugin_pb2.CodeGeneratorResponse() - - for result in results: - f = response.file.add() - f.name = result['headername'] - f.content = result['headerdata'] - - f = response.file.add() - f.name = result['sourcename'] - f.content = result['sourcedata'] - - return response - - -if __name__ == '__main__': - main() diff --git a/scripts/nanopb/proto_generator.py b/scripts/nanopb/proto_generator.py deleted file mode 100755 index 9059dd4..0000000 --- a/scripts/nanopb/proto_generator.py +++ /dev/null @@ -1,347 +0,0 @@ -#! /usr/bin/env python - -# Copyright 2022 Google LLC -# -# 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. - -"""Generates and massages protocol buffer outputs. - -Example usage: - -python Crashlytics/ProtoSupport/build_protos.py \ - --nanopb \ - --protos_dir=Crashlytics/Classes/Protos/ \ - --pythonpath=~/Downloads/nanopb-0.3.9.2-macosx-x86/generator/ \ - --output_dir=Crashlytics/Protogen/ -""" - -from __future__ import print_function -from inspect import signature - -import sys - -import argparse -import os -import os.path -import re -import subprocess - - -OBJC_GENERATOR = 'nanopb_objc_generator.py' - -COPYRIGHT_NOTICE = ''' -/* - * Copyright 2022 Google LLC - * - * 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. - */ -'''.lstrip() - - -def main(): - parser = argparse.ArgumentParser( - description='Generates proto messages.') - parser.add_argument( - '--nanopb', action='store_true', - help='Generates nanopb messages.') - parser.add_argument( - '--objc', action='store_true', - help='Generates Objective-C messages.') - parser.add_argument( - '--protos_dir', - help='Source directory containing .proto files.') - parser.add_argument( - '--output_dir', '-d', - help='Directory to write files; subdirectories will be created.') - parser.add_argument( - '--protoc', default='protoc', - help='Location of the protoc executable') - parser.add_argument( - '--pythonpath', - help='Location of the protoc python library.') - parser.add_argument( - '--include', '-I', action='append', default=[], - help='Adds INCLUDE to the proto path.') - parser.add_argument( - '--include_prefix', '-p', action='append', default=[], - help='Adds include_prefix to the .nanopb.h include in' - ' .nanopb.c') - - args = parser.parse_args() - if args.nanopb is None and args.objc is None: - parser.print_help() - sys.exit(1) - - if args.protos_dir is None: - root_dir = os.path.abspath(os.path.dirname(__file__)) - args.protos_dir = os.path.join(root_dir, 'protos') - - if args.output_dir is None: - root_dir = os.path.abspath(os.path.dirname(__file__)) - args.output_dir = os.path.join( - root_dir, 'protogen-please-supply-an-outputdir') - - all_proto_files = collect_files(args.protos_dir, '.proto') - if args.nanopb: - NanopbGenerator(args, all_proto_files).run() - - if args.objc: - print('Generating objc code is unsupported because it depends on the' - 'main protobuf Podspec that adds a lot of size to SDKs.') - - -class NanopbGenerator(object): - """Builds and runs the nanopb plugin to protoc.""" - - def __init__(self, args, proto_files): - self.args = args - self.proto_files = proto_files - - def run(self): - """Performs the action of the generator.""" - - nanopb_out = os.path.join(self.args.output_dir, 'nanopb') - mkdir(nanopb_out) - - self.__run_generator(nanopb_out) - - sources = collect_files(nanopb_out, '.nanopb.h', '.nanopb.c') - post_process_files( - sources, - add_copyright, - nanopb_remove_extern_c, - nanopb_rename_delete, - nanopb_use_module_import, - make_use_absolute_import(nanopb_out, self.args) - ) - - def __run_generator(self, out_dir): - """Invokes protoc using the nanopb plugin.""" - cmd = protoc_command(self.args) - - gen = os.path.join(os.path.dirname(__file__), OBJC_GENERATOR) - cmd.append('--plugin=protoc-gen-nanopb=%s' % gen) - - nanopb_flags = [ - '--extension=.nanopb', - '--source-extension=.c', - '--no-timestamp' - ] - nanopb_flags.extend(['-I%s' % path for path in self.args.include]) - cmd.append('--nanopb_out=%s:%s' % (' '.join(nanopb_flags), out_dir)) - - cmd.extend(self.proto_files) - run_protoc(self.args, cmd) - - -def protoc_command(args): - """Composes the initial protoc command-line including its include path.""" - cmd = [args.protoc] - if args.include is not None: - cmd.extend(['-I=%s' % path for path in args.include]) - return cmd - - -def run_protoc(args, cmd): - """Actually runs the given protoc command. - - Args: - args: The command-line args (including pythonpath) - cmd: The command to run expressed as a list of strings - """ - kwargs = {} - if args.pythonpath: - env = os.environ.copy() - old_path = env.get('PYTHONPATH') - env['PYTHONPATH'] = os.path.expanduser(args.pythonpath) - if old_path is not None: - env['PYTHONPATH'] += os.pathsep + old_path - kwargs['env'] = env - - try: - outputString = subprocess.check_output( - cmd, stderr=subprocess.STDOUT, **kwargs) - print(outputString.decode("utf-8")) - except subprocess.CalledProcessError as error: - print('command failed: ', ' '.join(cmd), '\nerror: ', error.output) - - -def post_process_files(filenames, *processors): - for filename in filenames: - lines = [] - with open(filename, 'r') as fd: - lines = fd.readlines() - - for processor in processors: - sig = signature(processor) - if len(sig.parameters) == 1: - lines = processor(lines) - else: - lines = processor(lines, filename) - - write_file(filename, lines) - - -def write_file(filename, lines): - mkdir(os.path.dirname(filename)) - with open(filename, 'w') as fd: - fd.write(''.join(lines)) - - -def add_copyright(lines): - """Adds a copyright notice to the lines.""" - if COPYRIGHT_NOTICE in lines: - return lines - result = [COPYRIGHT_NOTICE, '\n'] - result.extend(lines) - return result - - -def nanopb_remove_extern_c(lines): - """Removes extern "C" directives from nanopb code. - - Args: - lines: A nanobp-generated source file, split into lines. - Returns: - A list of strings, similar to the input but modified to remove extern "C". - """ - result = [] - state = 'initial' - for line in lines: - if state == 'initial': - if '#ifdef __cplusplus' in line: - state = 'in-ifdef' - continue - - result.append(line) - - elif state == 'in-ifdef': - if '#endif' in line: - state = 'initial' - - return result - - -def nanopb_rename_delete(lines): - """Renames a delete symbol to delete_. - - If a proto uses a field named 'delete', nanopb happily uses that in the - message definition. Works fine for C; not so much for C++. - - Args: - lines: The lines to fix. - - Returns: - The lines, fixed. - """ - delete_keyword = re.compile(r'\bdelete\b') - return [delete_keyword.sub('delete_', line) for line in lines] - - -# Don't let Copybara alter these lines. -def nanopb_use_module_import(lines): - """Changes #include to include """ - return [line.replace('#include ', - '{}include '.format("#")) - for line in lines] - - -def make_use_absolute_import(nanopb_out, args): - import_file = collect_files(nanopb_out, '.nanopb.h')[0] - - def nanopb_use_absolute_import(lines, filename): - """Makes repo-relative imports - - #include "crashlytics.nanopb.h" => - #include "Crashlytics/Protogen/nanopb/crashlytics.nanopb.h" - - This only applies to .nanopb.c files because it causes errors if - .nanopb.h files import other .nanopb.h files with full relative - paths. - """ - if ".h" in filename: - return lines - include_prefix = args.include_prefix[0] - header = os.path.basename(import_file) - return [line.replace('#include "{0}"'.format(header), - '#include "{0}{1}"'.format(include_prefix, header)) - for line in lines] - - return nanopb_use_absolute_import - - -def strip_trailing_whitespace(lines): - """Removes trailing whitespace from the given lines.""" - return [line.rstrip() + '\n' for line in lines] - - -def objc_flatten_imports(lines): - """Flattens the import statements for compatibility with CocoaPods.""" - - long_import = re.compile(r'#import ".*/') - return [long_import.sub('#import "', line) for line in lines] - - -def objc_strip_extension_registry(lines): - """Removes extensionRegistry methods from the classes.""" - skip = False - result = [] - for line in lines: - if '+ (GPBExtensionRegistry*)extensionRegistry {' in line: - skip = True - if not skip: - result.append(line) - elif line == '}\n': - skip = False - - return result - - -def collect_files(root_dir, *extensions): - """Finds files with the given extensions in the root_dir. - - Args: - root_dir: The directory from which to start traversing. - *extensions: Filename extensions (including the leading dot) to find. - - Returns: - A list of filenames, all starting with root_dir, that have one of the - given extensions. - """ - result = [] - for root, _, files in os.walk(root_dir): - for basename in files: - for ext in extensions: - if basename.endswith(ext): - filename = os.path.join(root, basename) - result.append(filename) - return result - - -def mkdir(dirname): - if not os.path.isdir(dirname): - os.makedirs(dirname) - - -if __name__ == '__main__': - main() diff --git a/scripts/pod_lib_lint.rb b/scripts/pod_lib_lint.rb deleted file mode 100755 index dcc4f7b..0000000 --- a/scripts/pod_lib_lint.rb +++ /dev/null @@ -1,203 +0,0 @@ -#!/usr/bin/env ruby - -# Copyright 2019 Google -# -# 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. - -require 'cocoapods' -require 'set' - -# Enable ruby options after 'require' because cocoapods is noisy -$VERBOSE = true # ruby -w -#$DEBUG = true # ruby --debug - -def usage() - script = File.basename($0) - STDERR.puts <<~EOF - USAGE: #{script} podspec [options] - - podspec is the podspec to lint - - options can be any options for pod spec lint - - script options: - --no-analyze: don't run Xcode analyze on this podspec - --ignore-local-podspecs: list of podspecs that should not be added to - "--include-podspecs" list. If not specified, then all podspec - dependencies will be passed to "--include-podspecs". - Example: --ignore-local-podspecs=GoogleDataTransport.podspec - EOF -end - -def main(args) - if args.size < 1 then - usage() - exit(1) - end - - STDOUT.sync = true - - command = %w(bundle exec pod lib lint --sources=https://github.com/firebase/SpecsDev.git,https://github.com/firebase/SpecsStaging.git,https://cdn.cocoapods.org/) - - # Split arguments that need to be processed by the script itself and passed - # to the pod command. - pod_args = [] - ignore_local_podspecs = [] - analyze = true - - args.each do |arg| - if arg =~ /--ignore-local-podspecs=(.*)/ - ignore_local_podspecs = $1.split(',') - elsif arg =~ /--no-analyze/ - analyze = false - else - pod_args.push(arg) - end - end - - podspec_file = pod_args[0] - - # Assert that the given podspec to lint does not have binary sources. - podspec = Pod::Spec.from_file(podspec_file) - if spec_has_binary_source(podspec) then - STDERR.puts(""" - Error: `pod lib lint` does not work for specs with binary sources. - The given podspec, #{podspec_file}, has a binary source. - """) - exit(1) - end - - # Figure out which dependencies are local - deps = find_local_deps(podspec_file, ignore_local_podspecs.to_set) - arg = make_include_podspecs(deps) - command.push(arg) if arg - command.push('--analyze') if analyze - - command.push(*pod_args) - puts command.join(' ') - - # Run the lib lint command in a thread. - pod_lint_status = 1 - t = Thread.new do - system(*command) - pod_lint_status = $?.exitstatus - end - - # Print every minute since linting can run for >10m without output. - number_of_times_checked = 0 - while t.alive? do - sleep 1.0 - number_of_times_checked += 1 - if (number_of_times_checked % 60) == 0 then - puts "Still working, running for #{number_of_times_checked / 60}min." - end - end - - exit(pod_lint_status) -end - -# Returns whether or not the given `Pod::Spec` has binary sources. -def spec_has_binary_source(spec) - # Specs with binary sources specify a URL for the `:http` key - # in their `source`. - return spec.source.has_key?(:http) -end - -# Loads all the specs (including subspecs) from the given podspec file. -def load_specs(podspec_file) - trace('loading', podspec_file) - results = [] - - spec = Pod::Spec.from_file(podspec_file) - results.push(spec) - - results.push(*spec.subspecs) - return results -end - -# Finds all dependencies of the given list of specs -def all_deps(specs) - result = Set[] - - specs.each do |spec| - spec.dependencies.each do |dep| - name = dep.name.sub(/\/.*/, '') - result.add(name) - end - end - - result = result.to_a - trace(' deps', *result) - return result -end - -# Given a podspec file, finds all local dependencies that have a local podspec -# in the same directory. Modifies seen to include all seen podspecs, which -# guarantees that a given podspec will only be processed once. -def find_local_deps(podspec_file, seen = Set[]) - # Mark the current podspec seen to prevent a pod from depending upon itself - # (as might happen if a subspec of the pod depends upon another subpsec of - # the pod). - seen.add(File.basename(podspec_file)) - - results = [] - spec_dir = File.dirname(podspec_file) - - specs = load_specs(podspec_file) - deps = all_deps(specs) - - deps.each do |dep_name| - dep_file = File.join(spec_dir, "#{dep_name}.podspec") - if File.exist?(dep_file) then - local_dep_spec = Pod::Spec.from_file(dep_file) - if !spec_has_binary_source(local_dep_spec) then - dep_podspec = File.basename(dep_file) - if seen.add?(dep_podspec) - # Depend on the podspec we found and any podspecs it depends upon. - results.push(dep_podspec) - results.push(*find_local_deps(dep_file, seen)) - end - end - end - end - - return results -end - -# Returns an --include-podspecs argument that indicates the given deps are -# locally available. Returns nil if deps is empty. -def make_include_podspecs(deps) - return nil if deps.empty? - - if deps.size == 1 then - deps_joined = deps[0] - else - deps_joined = "{" + deps.join(',') + "}" - end - return "--include-podspecs=#{deps_joined}" -end - -def trace(*args) - return if not $DEBUG - - STDERR.puts(args.join(' ')) -end - -# Writes the text in +contents+ to the file named by +filename+. -def write_file(filename, contents) - File.open(filename, "w") do |file| - file.write(contents) - end -end - -main(ARGV) diff --git a/scripts/release_testing_setup.sh b/scripts/release_testing_setup.sh deleted file mode 100755 index 7fde674..0000000 --- a/scripts/release_testing_setup.sh +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright 2020 Google LLC -# -# 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. - -# This script will `git clone` the SDK repo to local and look for the latest -# release branch -set -xe - -TESTINGMODE=${1-} - -if [ -f "${HOME}/.cocoapods/repos" ]; then - find "${HOME}/.cocoapods/repos" -type d -maxdepth 1 -exec sh -c 'pod repo remove $(basename {})' \; -fi - -mkdir -p "${local_sdk_repo_dir}" -echo "git clone from github.com/firebase/firebase-ios-sdk.git to ${local_sdk_repo_dir}" -set +x -# Using token here to update tags later. -git clone -q https://"${BOT_TOKEN}"@github.com/firebase/firebase-ios-sdk.git "${local_sdk_repo_dir}" -set -x - -cd "${local_sdk_repo_dir}" -# The chunk below is to determine the latest version by searching -# Get the latest released tag Cocoapods-X.Y.Z for release and prerelease testing, beta version will be excluded. -test_version=$(git tag -l --sort=-version:refname CocoaPods-*[0-9] | head -n 1) - -git config --global user.email "google-oss-bot@example.com" -git config --global user.name "google-oss-bot" -git checkout "${podspec_repo_branch}" -# Ensure the tag version including pod version to avoid warnings. -# https://github.com/CocoaPods/Core/blob/e6451e172c33f3aa77a3f8baa7b6b5b8c3b5da14/lib/cocoapods-core/specification/linter.rb#L372-L374 -pod_testing_version=`echo "${test_version}" | sed "s/CocoaPods-//"` -if [ "$TESTINGMODE" = "release_testing" ]; then - git checkout "${test_version}" - echo "Podspecs tags of Nightly release testing will be updated to ${test_version}." - # Update source and tag, e.g. ":tag => 'CocoaPods-' + s.version.to_s" to - # ":tag => 'CocoaPods-7.9.0'" - sed -i "" "s/\s*:tag.*/:tag => '${test_version}'/" *.podspec - sed -i "" "s/s\.version[[:space:]]*=.*/s\.version='${pod_testing_version}'/" *.podspec -elif [ "$TESTINGMODE" = "prerelease_testing" ]; then - tag_version="${test_version}.nightly" - echo "A new tag, ${tag_version},for prerelease testing will be created." - set +e - # If tag_version is new to the remote, remote cannot delete a non-existent - # tag, so error is allowed here. - git push origin --delete "${tag_version}" - set -e - git tag -f -a "${tag_version}" -m "release testing" - git push origin "${tag_version}" - # Update source and tag, e.g. ":tag => 'CocoaPods-' + s.version.to_s" to - # ":tag => ${test_version}.nightly" - sed -i "" "s/\s*:tag.*/:tag => '${tag_version}'/" *.podspec - sed -i "" "s/s\.version[[:space:]]*=.*/s\.version='${pod_testing_version}'/" *.podspec -fi diff --git a/scripts/remove_data.sh b/scripts/remove_data.sh deleted file mode 100755 index 4cf508d..0000000 --- a/scripts/remove_data.sh +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright 2020 Google LLC -# -# 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. - -set -xe - -SDK="$1" -MODE=${2-} - -DIR="${SDK}" - -if [[ ! -z "$LEGACY" ]]; then - DIR="${SDK}/Legacy${SDK}Quickstart" -fi - - -if [ "$MODE" == "release_testing" ]; then - echo "Update podfiles release_testing." - sed -i "" "s/https:\/\/.*@github.com\/FirebasePrivate\/SpecsTesting.git/https:\/\/github.com\/FirebasePrivate\/SpecsTesting.git/g" quickstart-ios/"${DIR}"/Podfile quickstart-ios/"${DIR}"/Podfile.lock -fi -rm -f quickstart-ios/"${DIR}"/GoogleService-Info.plist diff --git a/scripts/run_database_emulator.sh b/scripts/run_database_emulator.sh deleted file mode 100755 index cfaba2e..0000000 --- a/scripts/run_database_emulator.sh +++ /dev/null @@ -1,116 +0,0 @@ -#!/bin/bash - -# Copyright 2020 Google LLC -# -# 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. - -# USAGE: run_database_emulator.sh { run | start | stop } -# -# Downloads and runs the Firebase Realtime Databas emulator - -set -euo pipefail - -VERSION='4.7.2' -FILENAME="firebase-database-emulator-v${VERSION}.jar" -URL="https://storage.googleapis.com/firebase-preview-drop/emulator/${FILENAME}" - -cache_dir="${HOME}/.cache/firebase/emulators" -jar="${cache_dir}/${FILENAME}" - -cd $(git rev-parse --show-toplevel) -pid_file="firebase-database-emulator.pid" -log_file="firebase-database-emulator.log" - -function help() { - cat 1>&2 <&2 - mkdir -p "${cache_dir}" - curl -s -o "${jar}" "${URL}" - fi -} - -# Runs the emulator synchronously -function run() { - exec java -jar "$jar" "$@" -} - -# Verifies the emulator isn't already running at the PID in the pid_file -function check_not_running() { - if [[ -f "${pid_file}" ]]; then - pid=$(cat "${pid_file}") - if kill -0 "${pid}" >& /dev/null; then - echo "Firebase Realtime Database emulator already running as PID ${pid}" 1>&2 - return 1 - fi - - echo "Removing stale PID file" 1>&2 - rm "${pid_file}" - fi -} - -# Starts the emulator in the background -function start() { - check_not_running - - run "$@" >& "${log_file}" & - pid="$!" - echo "$pid" > "${pid_file}" - echo "Firebase Realtime Database emulator running as PID ${pid}" 1>&2 -} - -# Stops the emulator if it's running -function stop() { - if [[ -f "${pid_file}" ]]; then - pid=$(cat "${pid_file}") - kill "${pid}" || true - rm "${pid_file}" - fi -} - -command=run -if [[ $# -gt 0 ]]; then - command="$1" - shift -fi - -case "${command}" in - run) - ensure_exists - run "$@" - ;; - - start) - ensure_exists - start - ;; - - stop) - stop - ;; - - download) - ensure_exists - ;; - - *) - help - exit 1 - ;; -esac diff --git a/scripts/run_firestore_emulator.sh b/scripts/run_firestore_emulator.sh deleted file mode 100755 index dac3f81..0000000 --- a/scripts/run_firestore_emulator.sh +++ /dev/null @@ -1,121 +0,0 @@ -#!/bin/bash - -# Copyright 2019 Google LLC -# -# 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. - -# USAGE: run_firestore_emulator.sh { run | start | stop } -# -# Downloads and runs the Firestore emulator - -set -euo pipefail - -# Use Java 11 if it is available on the runner image -if [[ ! -z "${JAVA_HOME_11_X64:-}" ]]; then - export JAVA_HOME=$JAVA_HOME_11_X64 -fi - -VERSION='1.18.2' -FILENAME="cloud-firestore-emulator-v${VERSION}.jar" -URL="https://storage.googleapis.com/firebase-preview-drop/emulator/${FILENAME}" - -cache_dir="${HOME}/.cache/firebase/emulators" -jar="${cache_dir}/${FILENAME}" - -cd $(git rev-parse --show-toplevel) -pid_file="cloud-firestore-emulator.pid" -log_file="cloud-firestore-emulator.log" - -function help() { - cat 1>&2 <&2 - mkdir -p "${cache_dir}" - curl -s -o "${jar}" "${URL}" - fi -} - -# Runs the emulator synchronously -function run() { - exec java -jar "$jar" "$@" -} - -# Verifies the emulator isn't already running at the PID in the pid_file -function check_not_running() { - if [[ -f "${pid_file}" ]]; then - pid=$(cat "${pid_file}") - if kill -0 "${pid}" >& /dev/null; then - echo "Firestore emulator already running as PID ${pid}" 1>&2 - return 1 - fi - - echo "Removing stale PID file" 1>&2 - rm "${pid_file}" - fi -} - -# Starts the emulator in the background -function start() { - check_not_running - - run "$@" >& "${log_file}" & - pid="$!" - echo "$pid" > "${pid_file}" - echo "Firestore emulator running as PID ${pid}" 1>&2 -} - -# Stops the emulator if it's running -function stop() { - if [[ -f "${pid_file}" ]]; then - pid=$(cat "${pid_file}") - kill "${pid}" || true - rm "${pid_file}" - fi -} - -command=run -if [[ $# -gt 0 ]]; then - command="$1" - shift -fi - -case "${command}" in - run) - ensure_exists - run "$@" - ;; - - start) - ensure_exists - start - ;; - - stop) - stop - ;; - - download) - ensure_exists - ;; - - *) - help - exit 1 - ;; -esac diff --git a/scripts/setup_bundler.sh b/scripts/setup_bundler.sh deleted file mode 100755 index 8944030..0000000 --- a/scripts/setup_bundler.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -# Copyright 2020 Google LLC -# -# 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. - - -# To test another version of Xcode for all of CI: -# - Add any edit, like a blank line, to Gemfile. -# - Uncomment the following line and choose the alternative Xcode version. -#sudo xcode-select -s /Applications/Xcode_13.0.app/Contents/Developer - -bundle update --bundler # ensure bundler version is high enough for Gemfile.lock -bundle install -bundle --version -bundle exec pod --version diff --git a/scripts/setup_check.sh b/scripts/setup_check.sh deleted file mode 100755 index eb323eb..0000000 --- a/scripts/setup_check.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash - -# Copyright 2020 Google LLC -# -# 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. - -# Prepares a host for running check.sh - -set -euo pipefail - -export HOMEBREW_NO_ANALYTICS=1 -export HOMEBREW_NO_AUTO_UPDATE=1 - -echo "python --version: $(python --version)" -pip install --upgrade pip -pip install flake8 -pip install six - -# Using actions/checkout@v3 creates a shallow clone that's missing the main -# branch. If it's not present, add it. -if ! git rev-parse origin/main >& /dev/null; then - git remote set-branches --add origin main - git fetch origin -fi - -# install clang-format -brew update -brew install clang-format@18 - -# mint installs tools from Mintfile on demand. -brew install mint - -# Explicitly mint bootstrap to show its version in the "Setup check" GHA phase -mint bootstrap diff --git a/scripts/setup_quickstart.sh b/scripts/setup_quickstart.sh deleted file mode 100755 index 53c31b5..0000000 --- a/scripts/setup_quickstart.sh +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright 2019 Google -# -# 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. - - -# Script to run in a CI `before_install` phase to setup the quickstart repo -# so that it can be used for integration testing. - -set -xeuo pipefail - -scripts_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -root_dir="$(dirname "$scripts_dir")" - -$scripts_dir/setup_bundler.sh - -# Source function to check if CI secrets are available. -source $scripts_dir/check_secrets.sh - -SAMPLE=$1 - -RELEASE_TESTING=${2-} - -WORKSPACE_DIR="quickstart-ios/${SAMPLE}" -PODFILE="quickstart-ios/"$SAMPLE"/Podfile" - -if [[ ! -z "${LEGACY:-}" ]]; then - WORKSPACE_DIR="quickstart-ios/${SAMPLE}/Legacy${SAMPLE}Quickstart" - PODFILE="quickstart-ios/"$SAMPLE"/Legacy${SAMPLE}Quickstart/Podfile" -fi - -gem install xcpretty - -# Installations is the only quickstart that doesn't need a real -# GoogleService-Info.plist for its tests. -if check_secrets || [[ ${SAMPLE} == "installations" ]]; then - - # Specify repo so the Firebase module and header can be found in a - # development pod install. This is needed for the `pod install` command. - export FIREBASE_POD_REPO_FOR_DEV_POD=`pwd` - - git clone https://github.com/firebase/quickstart-ios.git - - $scripts_dir/localize_podfile.swift "$WORKSPACE_DIR"/Podfile "$RELEASE_TESTING" - if [ "$RELEASE_TESTING" == "nightly_release_testing" ]; then - set +x - sed -i "" '1i\'$'\n'"source 'https://${BOT_TOKEN}@github.com/FirebasePrivate/SpecsTesting.git'"$'\n' "$PODFILE" - set -x - echo "Source of Podfile for nightly release testing is updated." - fi - if [ "$RELEASE_TESTING" == "prerelease_testing" ]; then - set +x - sed -i "" '1i\'$'\n'"source 'https://${BOT_TOKEN}@github.com/FirebasePrivate/SpecsReleasing.git'"$'\n' "$PODFILE" - set -x - echo "Source of Podfile for prerelease testing is updated." - fi - cd "${WORKSPACE_DIR}" - - # To test a branch, uncomment the following line - # git checkout {BRANCH_NAME} - - bundle update --bundler - bundle install - pod update -fi diff --git a/scripts/setup_quickstart_framework.sh b/scripts/setup_quickstart_framework.sh deleted file mode 100755 index 51499ab..0000000 --- a/scripts/setup_quickstart_framework.sh +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2020 Google LLC -# -# 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. - -set -ex - -REPO=`pwd` -if [ ! -d "quickstart-ios" ]; then - git clone https://github.com/firebase/quickstart-ios.git -fi -QS_SCRIPTS="${REPO}"/quickstart-ios/scripts -cd quickstart-ios/"${SAMPLE}" - -if [[ ! -z "$LEGACY" ]]; then - cd "Legacy${SAMPLE}Quickstart" -fi - -# Make sure the Xcode project has at least one Swift file. -# See https://forums.swift.org/t/using-binary-swift-sdks-from-non-swift-apps/55989 -touch foo.swift -"${REPO}"/scripts/update_xcode_target.rb "${SAMPLE}Example.xcodeproj" "${SAMPLE}Example" foo.swift - -mkdir -p Firebase/ -# Create non Firebase Frameworks and move to Firebase/ dir. -if [[ ! -z "$NON_FIREBASE_SDKS" ]]; then - REPO="${REPO}" NON_FIREBASE_SDKS="${NON_FIREBASE_SDKS}" "${REPO}"/scripts/build_non_firebase_sdks.sh -fi -if [ ! -f "Firebase/Firebase.h" ]; then - cp "${HOME}"/ios_frameworks/Firebase/Firebase.h Firebase/ -fi -if [ ! -f "Firebase/module.modulemap" ]; then - cp "${HOME}"/ios_frameworks/Firebase/module.modulemap Firebase/ -fi -for file in "$@" -do - if [ ! -d "Firebase/$(basename ${file})" ]; then - rsync -a ${file} Firebase/ - fi -done - -if [[ "${SAMPLE}" == "Authentication" ]]; then - "${QS_SCRIPTS}"/add_framework_script.rb --sdk "${SAMPLE}" --target "${TARGET}" --framework_path usr/lib/libc++.dylib - "${QS_SCRIPTS}"/add_framework_script.rb --sdk "${SAMPLE}" --target "${TARGET}" --framework_path accelerate.framework --source_tree DEVELOPER_FRAMEWORKS_DIR -fi - -if [[ "${SAMPLE}" == "Firestore" ]]; then - "${QS_SCRIPTS}"/add_framework_script.rb --sdk "${SAMPLE}" --target "${TARGET}" --framework_path Firebase/FirebaseAuthUI.xcframework/Resources/FirebaseAuthUI.bundle - "${QS_SCRIPTS}"/add_framework_script.rb --sdk "${SAMPLE}" --target "${TARGET}" --framework_path Firebase/FirebaseEmailAuthUI.xcframework/Resources/FirebaseEmailAuthUI.bundle -fi - -"${QS_SCRIPTS}"/add_framework_script.rb --sdk "${SAMPLE}" --target "${TARGET}" --framework_path Firebase/ diff --git a/scripts/setup_spm_tests.sh b/scripts/setup_spm_tests.sh deleted file mode 100755 index 8877100..0000000 --- a/scripts/setup_spm_tests.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash - -# Copyright 2021 Google LLC -# -# 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. - - -# Point SPM CI to the tip of main of https://github.com/google/GoogleAppMeasurement -# so that the release process can defer publishing the GoogleAppMeasurement tag -# until after testing. - -# For example: Change `exact: "8.3.1"` to `branch: "main"` - -sed -i '' 's#exact:[[:space:]]*"[0-9.]*"#branch: "main"#' Package.swift - - -# Move schemes into place to run Swift Package Manager tests -# These cannot be stored in the correct location because they cause -# clutter for SDK clients. -# Details in https://github.com/firebase/firebase-ios-sdk/issues/8167 and -# https://forums.swift.org/t/swiftpm-and-library-unit-testing/26255/21 - -mkdir -p .swiftpm/xcode/xcshareddata/xcschemes -cp scripts/spm_test_schemes/* .swiftpm/xcode/xcshareddata/xcschemes/ -xcodebuild -list diff --git a/scripts/spm_test_schemes/ABTestingUnit.xcscheme b/scripts/spm_test_schemes/ABTestingUnit.xcscheme deleted file mode 100644 index 43969f2..0000000 --- a/scripts/spm_test_schemes/ABTestingUnit.xcscheme +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/AnalyticsObjCAPI.xcscheme b/scripts/spm_test_schemes/AnalyticsObjCAPI.xcscheme deleted file mode 100644 index 29e9a74..0000000 --- a/scripts/spm_test_schemes/AnalyticsObjCAPI.xcscheme +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/AnalyticsSwiftUnit.xcscheme b/scripts/spm_test_schemes/AnalyticsSwiftUnit.xcscheme deleted file mode 100644 index 314f571..0000000 --- a/scripts/spm_test_schemes/AnalyticsSwiftUnit.xcscheme +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/AppDistributionUnit.xcscheme b/scripts/spm_test_schemes/AppDistributionUnit.xcscheme deleted file mode 100644 index 7e88608..0000000 --- a/scripts/spm_test_schemes/AppDistributionUnit.xcscheme +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/AuthUnit.xcscheme b/scripts/spm_test_schemes/AuthUnit.xcscheme deleted file mode 100644 index 06dec59..0000000 --- a/scripts/spm_test_schemes/AuthUnit.xcscheme +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/CoreUnit.xcscheme b/scripts/spm_test_schemes/CoreUnit.xcscheme deleted file mode 100644 index 16e458a..0000000 --- a/scripts/spm_test_schemes/CoreUnit.xcscheme +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/DatabaseUnit.xcscheme b/scripts/spm_test_schemes/DatabaseUnit.xcscheme deleted file mode 100644 index d52f153..0000000 --- a/scripts/spm_test_schemes/DatabaseUnit.xcscheme +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/DatabaseUnitSwift.xcscheme b/scripts/spm_test_schemes/DatabaseUnitSwift.xcscheme deleted file mode 100644 index 9a8f09e..0000000 --- a/scripts/spm_test_schemes/DatabaseUnitSwift.xcscheme +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/FirebaseAppCheckUnit.xcscheme b/scripts/spm_test_schemes/FirebaseAppCheckUnit.xcscheme deleted file mode 100644 index 77e1cc7..0000000 --- a/scripts/spm_test_schemes/FirebaseAppCheckUnit.xcscheme +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/FirebaseAppCheckUnitSwift.xcscheme b/scripts/spm_test_schemes/FirebaseAppCheckUnitSwift.xcscheme deleted file mode 100644 index b55b0cc..0000000 --- a/scripts/spm_test_schemes/FirebaseAppCheckUnitSwift.xcscheme +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/FirebaseCoreInternalTests.xcscheme b/scripts/spm_test_schemes/FirebaseCoreInternalTests.xcscheme deleted file mode 100644 index eb9dc6b..0000000 --- a/scripts/spm_test_schemes/FirebaseCoreInternalTests.xcscheme +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/FirebaseCrashlyticsUnit.xcscheme b/scripts/spm_test_schemes/FirebaseCrashlyticsUnit.xcscheme deleted file mode 100644 index 0fdca3c..0000000 --- a/scripts/spm_test_schemes/FirebaseCrashlyticsUnit.xcscheme +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/FirebaseFunctionsIntegration.xcscheme b/scripts/spm_test_schemes/FirebaseFunctionsIntegration.xcscheme deleted file mode 100644 index 6f520b1..0000000 --- a/scripts/spm_test_schemes/FirebaseFunctionsIntegration.xcscheme +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/FirebaseFunctionsObjCIntegration.xcscheme b/scripts/spm_test_schemes/FirebaseFunctionsObjCIntegration.xcscheme deleted file mode 100644 index 2a4c975..0000000 --- a/scripts/spm_test_schemes/FirebaseFunctionsObjCIntegration.xcscheme +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/FirebaseFunctionsUnit.xcscheme b/scripts/spm_test_schemes/FirebaseFunctionsUnit.xcscheme deleted file mode 100644 index 9b9601a..0000000 --- a/scripts/spm_test_schemes/FirebaseFunctionsUnit.xcscheme +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/FirebaseMLModelDownloaderUnit.xcscheme b/scripts/spm_test_schemes/FirebaseMLModelDownloaderUnit.xcscheme deleted file mode 100644 index 1d68ba2..0000000 --- a/scripts/spm_test_schemes/FirebaseMLModelDownloaderUnit.xcscheme +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/FirebaseSessionsUnit.xcscheme b/scripts/spm_test_schemes/FirebaseSessionsUnit.xcscheme deleted file mode 100644 index e21757f..0000000 --- a/scripts/spm_test_schemes/FirebaseSessionsUnit.xcscheme +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/FirebaseSharedSwiftTests.xcscheme b/scripts/spm_test_schemes/FirebaseSharedSwiftTests.xcscheme deleted file mode 100644 index 6314775..0000000 --- a/scripts/spm_test_schemes/FirebaseSharedSwiftTests.xcscheme +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/FirebaseStorageUnit.xcscheme b/scripts/spm_test_schemes/FirebaseStorageUnit.xcscheme deleted file mode 100644 index 62f711b..0000000 --- a/scripts/spm_test_schemes/FirebaseStorageUnit.xcscheme +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/FirestoreTestingSupportTests.xcscheme b/scripts/spm_test_schemes/FirestoreTestingSupportTests.xcscheme deleted file mode 100644 index 3a1d45c..0000000 --- a/scripts/spm_test_schemes/FirestoreTestingSupportTests.xcscheme +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/FunctionsCombineUnit.xcscheme b/scripts/spm_test_schemes/FunctionsCombineUnit.xcscheme deleted file mode 100644 index 8d1913b..0000000 --- a/scripts/spm_test_schemes/FunctionsCombineUnit.xcscheme +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/MessagingUnit.xcscheme b/scripts/spm_test_schemes/MessagingUnit.xcscheme deleted file mode 100644 index d2f11c0..0000000 --- a/scripts/spm_test_schemes/MessagingUnit.xcscheme +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/PerformanceUnit.xcscheme b/scripts/spm_test_schemes/PerformanceUnit.xcscheme deleted file mode 100644 index e8ed627..0000000 --- a/scripts/spm_test_schemes/PerformanceUnit.xcscheme +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/RemoteConfigFakeConsole.xcscheme b/scripts/spm_test_schemes/RemoteConfigFakeConsole.xcscheme deleted file mode 100644 index c05be8c..0000000 --- a/scripts/spm_test_schemes/RemoteConfigFakeConsole.xcscheme +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/RemoteConfigUnit.xcscheme b/scripts/spm_test_schemes/RemoteConfigUnit.xcscheme deleted file mode 100644 index 957318d..0000000 --- a/scripts/spm_test_schemes/RemoteConfigUnit.xcscheme +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/analytics-import-test.xcscheme b/scripts/spm_test_schemes/analytics-import-test.xcscheme deleted file mode 100644 index e47accc..0000000 --- a/scripts/spm_test_schemes/analytics-import-test.xcscheme +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/objc-import-test.xcscheme b/scripts/spm_test_schemes/objc-import-test.xcscheme deleted file mode 100644 index 02bcfcc..0000000 --- a/scripts/spm_test_schemes/objc-import-test.xcscheme +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/swift-test.xcscheme b/scripts/spm_test_schemes/swift-test.xcscheme deleted file mode 100644 index e193f57..0000000 --- a/scripts/spm_test_schemes/swift-test.xcscheme +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/spm_test_schemes/version-test.xcscheme b/scripts/spm_test_schemes/version-test.xcscheme deleted file mode 100644 index b71ff2d..0000000 --- a/scripts/spm_test_schemes/version-test.xcscheme +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/scripts/style.sh b/scripts/style.sh deleted file mode 100755 index e8fcf12..0000000 --- a/scripts/style.sh +++ /dev/null @@ -1,221 +0,0 @@ -#!/bin/bash - -# Copyright 2017 Google LLC -# -# 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. - -# Usage: -# ./scripts/style.sh [branch-name | filenames] -# -# With no arguments, formats all eligible files in the repo -# Pass a branch name to format all eligible files changed since that branch -# Pass a specific file or directory name to format just files found there -# -# Commonly -# ./scripts/style.sh main - -# Set the environment variable FIR_CLANG_FORMAT_PATH to use a specific version -# of clang-format, regardless of its order in the shell PATH. -# -# Example (add to ~/.bash_profile, ~/.zshrc or run in interactive shell): -# FIR_CLANG_FORMAT_PATH="$(brew --prefix clang-format)/bin/clang-format" -# export FIR_CLANG_FORMAT_PATH -if [[ -n "$FIR_CLANG_FORMAT_PATH" ]]; then - clang_format_bin="$FIR_CLANG_FORMAT_PATH" -elif ! clang_format_bin=$(command -v clang-format); then - echo "clang-format not found, install with 'brew install clang-format'" - exit 1 -fi - -# Strip the clang-format version output down to the major version. Examples: -# clang-format version 7.0.0 (tags/google/stable/2018-01-11) -# clang-format version google3-trunk (trunk r333779) -version=$("$clang_format_bin" --version) - -# Log the version in non-interactive use as it can be useful in travis logs. -if [[ ! -t 1 ]]; then - echo "Clang-format Path: $clang_format_bin" - echo "Clang-format Version: $version" -fi - -# Remove leading "clang-format version" -version="${version/*version /}" -# Remove trailing parenthetical version details -version="${version/ (*)/}" -# Remove everything after the first dot (note this is a glob, not a regex) -version="${version/.*/}" - -case "$version" in - 18) - ;; - google3-trunk) - echo "Please use a publicly released clang-format; a recent LLVM release" - echo "from http://releases.llvm.org/download.html will work." - echo "Prepend to PATH when running style.sh." - exit 1 - ;; - *) - echo "Please upgrade to clang-format version 18." - echo "If it's installed via homebrew you can run:" - echo "brew upgrade clang-format" - exit 1 - ;; -esac - -# Ensure that tools in `Mintfile` are installed locally to avoid permissions -# problems that would otherwise arise from the default of installing in -# /usr/local. -export MINT_PATH=Mint - -if ! which mint >/dev/null 2>&1; then - echo "mint is not available, install with 'brew install mint'" - exit 1 -fi - -system=$(uname -s) - -# Joins the given arguments with the separator given as the first argument. -function join() { - local IFS="$1" - shift - echo "$*" -} - -clang_options=(-style=file) - -# Rules to disable in swiftformat: -swift_disable=( - # sortedImports is broken, sorting into the middle of the copyright notice. - sortedImports - - # Too many of our swift files have simplistic examples. While technically - # it's correct to remove the unused argument labels, it makes our examples - # look wrong. - unusedArguments - - # We prefer trailing braces. - wrapMultilineStatementBraces -) - -swift_options=( - # Mimic Objective-C style. - --indent 2 - --maxwidth 100 - --wrapparameters afterfirst - - --disable $(join , "${swift_disable[@]}") -) - -if [[ $# -gt 0 && "$1" == "test-only" ]]; then - test_only=true - clang_options+=(-output-replacements-xml) - swift_options+=(--dryrun) - shift -else - test_only=false - clang_options+=(-i) -fi - -#TODO(#2223) - Find a way to handle spaces in filenames - -files=$( -( - if [[ $# -gt 0 ]]; then - if git rev-parse "$1" -- >& /dev/null; then - # Argument was a branch name show files changed since that branch - git diff --name-only --relative --diff-filter=ACMR "$1" - else - # Otherwise assume the passed things are files or directories - find "$@" -type f - fi - else - # Do everything by default - find . -type f - fi -) | sed -E -n ' -# find . includes a leading "./" that git does not include -s%^./%% - -# Build outputs -\%/Pods/% d -\%^build/% d -\%^Debug/% d -\%^Release/% d -\%^cmake-build-debug/% d -\%^.build/% d -\%^.swiftpm/% d - -# pod gen output -\%^gen/% d - -# FirestoreEncoder is under 'third_party' for licensing reasons but should be -# formatted. -\%Firestore/third_party/FirestoreEncoder/.*\.swift% p - -# Sources controlled outside this tree -\%/third_party/% d - -# Public headers for closed sourced FirebaseAnalytics -\%^(FirebaseAnalyticsWrapper)/% d - -# Generated source -\%/Firestore/core/src/util/config.h% d - -# Sources pulled in by travis bundler, with and without a leading slash -\%^/?vendor/bundle/% d - -# Sources pulled in by the Mint package manager -\%^mint% d - -# Auth Sample Objective-C does not format well -\%^(FirebaseAuth/Tests/Sample/Sample)/% d - -# Keep Firebase.h indenting -\%^CoreOnly/Sources/Firebase.h% d - -# Checked-in generated code -\%\.pb(objc|rpc)\.% d -\%\.pb\.% d -\%\.nanopb\.% d - -# Format C-ish sources only -\%\.(h|m|mm|cc|swift)$% p -' -) - -needs_formatting=false -for f in $files; do - if [[ "${f: -6}" == '.swift' ]]; then - if [[ "$system" == 'Darwin' ]]; then - # Match output that says: - # 1/1 files would have been formatted. (with --dryrun) - # 1/1 files formatted. (without --dryrun) - mint run swiftformat "${swift_options[@]}" "$f" 2>&1 | grep '^1/1 files' > /dev/null - else - false - fi - else - "$clang_format_bin" "${clang_options[@]}" "$f" | grep " /dev/null - fi - - if [[ "$test_only" == true && $? -ne 1 ]]; then - echo "$f needs formatting." - needs_formatting=true - fi -done - -if [[ "$needs_formatting" == true ]]; then - echo "Proposed commit is not style compliant." - echo "Run scripts/style.sh and git add the result." - exit 1 -fi diff --git a/scripts/sync_project.rb b/scripts/sync_project.rb deleted file mode 100755 index fdf03f8..0000000 --- a/scripts/sync_project.rb +++ /dev/null @@ -1,863 +0,0 @@ -#!/usr/bin/env ruby - -# Copyright 2018 Google -# -# 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. - -# Syncs Xcode project folder and target structure with the filesystem. This -# script finds all files on the filesystem that match the patterns supplied -# below and changes the project to match what it found. -# -# Run this script after adding/removing tests to keep the project in sync. - -require 'cocoapods' -require 'optparse' -require 'pathname' - -# Note that xcodeproj 1.5.8 appears to be broken -# https://github.com/CocoaPods/Xcodeproj/issues/572 -gem 'xcodeproj', '!= 1.5.8' -require 'xcodeproj' - - -ROOT_DIR = Pathname.new(__FILE__).dirname().join('..').expand_path() -PODFILE_DIR = ROOT_DIR.join('Firestore', 'Example') - - -def main() - test_only = false - OptionParser.new do |opts| - opts.on('--test-only', 'Check diffs without writing') do |v| - test_only = v - end - end.parse! - - # Make all filenames relative to the project root. - Dir.chdir(ROOT_DIR.to_s) - - changes = sync_firestore(test_only) - status = test_only && changes > 0 ? 2 : 0 - exit(status) -end - - -# Make it so that you can "add" hash literals together by merging their -# contents. -class Hash - def +(other) - return merge(other) - end -end - - -def sync_firestore(test_only) - project = Xcodeproj::Project.open('Firestore/Example/Firestore.xcodeproj') - spec = Pod::Spec.from_file('FirebaseFirestoreInternal.podspec') - swift_spec = Pod::Spec.from_file('FirebaseFirestore.podspec') - - # Enable warnings after opening the project to avoid the warnings in - # xcodeproj itself - $VERBOSE = true - - s = Syncer.new(project, ROOT_DIR) - - # Files on the filesystem that should be ignored. - s.ignore_files = [ - 'CMakeLists.txt', - 'README.md', - 'InfoPlist.strings', - '*.orig', - '*.plist', - '.*', - ] - - # Folder groups in the Xcode project that contain tests. - s.groups = [ - 'Tests', - 'CoreTests', - 'CoreTestsProtos', - 'SwiftTests', - ] - - # Copy key settings from the podspec - podspec_settings = [ - 'CLANG_CXX_LANGUAGE_STANDARD', - 'GCC_C_LANGUAGE_STANDARD', - ] - xcconfig_spec = spec.attributes_hash['pod_target_xcconfig'].dup - xcconfig_spec.select! { |k, v| podspec_settings.include?(k) } - - # Settings for all Objective-C/C++ targets - xcconfig_objc = xcconfig_spec + { - 'INFOPLIST_FILE' => '"${SRCROOT}/Tests/Tests-Info.plist"', - - # Duplicate the header search paths from the main podspec because they're - # phrased in terms of PODS_TARGET_SRCROOT, which isn't defined for other - # targets. - 'HEADER_SEARCH_PATHS' => [ - # Include fully qualified from the root of the repo - '"${PODS_ROOT}/../../.."', - - # Make public headers available as "FIRQuery.h" - '"${PODS_ROOT}/../../../Firestore/Source/Public/FirebaseFirestore"', - - # Generated protobuf and nanopb output expects to search relative to the - # output path. - '"${PODS_ROOT}/../../../Firestore/Protos/cpp"', - '"${PODS_ROOT}/../../../Firestore/Protos/nanopb"', - - # Other dependencies that assume #includes are relative to their roots. - '"${PODS_ROOT}/../../../Firestore/third_party/abseil-cpp"', - '"${PODS_ROOT}/GoogleBenchmark/include"', - '"${PODS_ROOT}/GoogleTest/googlemock/include"', - '"${PODS_ROOT}/GoogleTest/googletest/include"', - '"${PODS_ROOT}/leveldb-library/include"', - ], - - 'SYSTEM_HEADER_SEARCH_PATHS' => [ - # Nanopb wants to #include - '"${PODS_ROOT}/nanopb"', - - # Protobuf wants to #include - '"${PODS_ROOT}/ProtobufCpp/src"', - ], - - 'OTHER_CFLAGS' => [ - # Protobuf C++ generates dead code. - '-Wno-unreachable-code', - - # Our public build can't include -Werror, but for development it's quite - # helpful. - '-Werror' - ] - } - - xcconfig_swift = { - 'SWIFT_OBJC_BRIDGING_HEADER' => - '${PODS_ROOT}/../../../Firestore/Swift/Tests/BridgingHeader.h', - 'SWIFT_VERSION' => pick_swift_version(swift_spec), - } - - ['iOS', 'macOS', 'tvOS'].each do |platform| - s.target "Firestore_Example_#{platform}" do |t| - t.xcconfig = xcconfig_objc + xcconfig_swift + { - # Passing -all_load is required to get all our C++ code into the test - # host. - # - # Normally when running tests, the test target contains only the tests - # proper, and links against the test host for the code under test. The - # test host doesn't do anything though, so the linker strips C++-only - # object code away. - # - # This is particular to C++ because by default CocoaPods configures the - # test host to link with the -ObjC flag. This causes the linker to pull - # in all Objective-C object code. -all_load fixes this by forcing the - # linker to pull in everything. - 'OTHER_LDFLAGS' => '-all_load', - } - end - - s.target "Firestore_Tests_#{platform}" do |t| - t.source_files = [ - 'Firestore/Example/Tests/**', - 'Firestore/core/test/**', - 'Firestore/Protos/cpp/**', - ] - t.exclude_files = [ - # needs to be in project but not in target - 'Firestore/Example/Tests/Tests-Info.plist', - - # These files are integration tests, handled below - 'Firestore/Example/Tests/Integration/**', - ] - t.xcconfig = xcconfig_objc + xcconfig_swift - end - end - - ['iOS', 'macOS', 'tvOS'].each do |platform| - s.target "Firestore_IntegrationTests_#{platform}" do |t| - t.source_files = [ - 'Firestore/Example/Tests/**', - 'Firestore/Protos/cpp/**', - 'Firestore/Swift/Tests/**', - 'Firestore/core/test/**', - ] - t.exclude_files = [ - # needs to be in project but not in target - 'Firestore/Example/Tests/Tests-Info.plist', - ] - t.xcconfig = xcconfig_objc + xcconfig_swift - end - - s.target 'Firestore_Benchmarks_iOS' do |t| - t.xcconfig = xcconfig_objc + { - 'INFOPLIST_FILE' => '${SRCROOT}/Benchmarks/Info.plist', - } - end - - s.target 'Firestore_FuzzTests_iOS' do |t| - t.xcconfig = xcconfig_objc + { - 'INFOPLIST_FILE' => - '${SRCROOT}/FuzzTests/Firestore_FuzzTests_iOS-Info.plist', - 'OTHER_CFLAGS' => [ - '-fsanitize=fuzzer', - ] - } - - end - end - - changes = s.sync(test_only) - if not test_only - sort_project(project) - if project.dirty? - project.save() - end - end - return changes -end - - -# Picks a swift version to use from a podspec's swift_versions -def pick_swift_version(spec) - versions = spec.attributes_hash['swift_versions'] - if versions.is_a?(Array) - return versions[-1] - end - return versions -end - - -# A list of filesystem patterns -class PatternList - def initialize() - @patterns = [] - end - - attr_accessor :patterns - - # Evaluates the rel_path against the given list of fnmatch patterns. - def matches?(rel_path) - @patterns.each do |pattern| - if rel_path.fnmatch?(pattern) - return true - end - end - return false - end -end - - -# The definition of a test target including the target name, its source_files -# and exclude_files. A file is considered part of a target if it matches a -# pattern in source_files but does not match a pattern in exclude_files. -class TargetDef - def initialize(name) - @name = name - @sync_sources = false - @source_files = PatternList.new() - @exclude_files = PatternList.new() - - @xcconfig = {} - end - - attr_reader :name, :sync_sources, :source_files, :exclude_files - attr_accessor :xcconfig - - def source_files=(value) - @sync_sources = true - @source_files.patterns.replace(value) - end - - def exclude_files=(value) - @exclude_files.patterns.replace(value) - end - - # Returns true if the given rel_path matches this target's source_files - # but not its exclude_files. - # - # Args: - # - rel_path: a Pathname instance with a path relative to the project root. - def matches?(rel_path) - return @source_files.matches?(rel_path) && !@exclude_files.matches?(rel_path) - end - - def diff(project_files, target) - diff = Diff.new - - project_files.each do |file_ref| - if matches?(relative_path(file_ref)) - entry = diff.track(file_ref.real_path) - entry.in_source = true - entry.ref = file_ref - end - end - - each_target_file(target) do |file_ref| - entry = diff.track(file_ref.real_path) - entry.in_target = true - entry.ref = file_ref - end - - return diff - end - - # We're only managing synchronization of files in these phases. - INTERESTING_PHASES = [ - Xcodeproj::Project::Object::PBXHeadersBuildPhase, - Xcodeproj::Project::Object::PBXSourcesBuildPhase, - Xcodeproj::Project::Object::PBXResourcesBuildPhase, - ] - - # Finds all the files referred to by any phase in a target - def each_target_file(target) - target.build_phases.each do |phase| - next if not INTERESTING_PHASES.include?(phase.class) - - phase.files.each do |build_file| - yield build_file.file_ref - end - end - end -end - - -class Syncer - HEADERS = %w{.h} - SOURCES = %w{.c .cc .m .mm .swift} - - def initialize(project, root_dir) - @project = project - @finder = DirectoryLister.new(root_dir) - - @groups = [] - @targets = [] - - @seen_groups = {} - end - - # Considers the given fnmatch glob patterns to be ignored by the syncer. - # Patterns are matched both against the basename and project-relative - # qualified pathname. - def ignore_files=(patterns) - @finder.add_patterns(patterns) - end - - # Names the groups within the project that serve as roots for tests within - # the project. - def groups=(groups) - @groups = [] - groups.each do |group| - project_group = @project[group] - if project_group.nil? - raise "Project does not contain group #{group}" - end - @groups.push(@project[group]) - end - end - - # Starts a new target block. Creates a new TargetDef and yields it. - def target(name, &block) - t = TargetDef.new(name) - @targets.push(t) - - block.call(t) - end - - # Finds the target definition with the given name. - def find_target(name) - @targets.each do |target| - if target.name == name - return target - end - end - return nil - end - - # Synchronizes the filesystem with the project. - # - # Generally there are three separate ways a file is referenced within a project: - # - # 1. The file must be in the global list of files, assigning it a UUID. - # 2. The file must be added to folder groups, describing where it is in the - # folder view of the Project Navigator. - # 3. The file must be added to a target phase describing how it's built. - # - # The Xcodeproj library handles (1) for us automatically if we do (2). - # - # Returns the number of changes made during synchronization. - def sync(test_only = false) - # Figure the diff between the filesystem and the group structure - group_differ = GroupDiffer.new(@finder) - group_diff = group_differ.diff(@groups) - changes = group_diff.changes - to_remove = group_diff.to_remove - - # Add all files first, to ensure they exist for later steps - add_to_project(group_diff.to_add) - - project_files = find_project_files_after_removal(@project.files, to_remove) - - @project.native_targets.each do |target| - target_def = find_target(target.name) - next if target_def.nil? - - if target_def.sync_sources - target_diff = target_def.diff(project_files, target) - target_diff.sorted_entries.each do |entry| - changes += sync_target_entry(target, entry) - end - end - - if not test_only - # Don't sync xcconfig changes in test-only mode. - sync_xcconfig(target_def, target) - end - end - - remove_from_project(to_remove) - return changes - end - - private - - def find_project_files_after_removal(files, to_remove) - remove_paths = Set.new() - to_remove.each do |entry| - remove_paths.add(entry.path) - end - - result = [] - files.each do |file_ref| - next if file_ref.source_tree != '' - - next if remove_paths.include?(file_ref.real_path) - - path = file_ref.real_path - next if @finder.ignore_basename?(path.basename) - next if @finder.ignore_pathname?(path) - - result.push(file_ref) - end - return result - end - - # Adds the given file to the project, in a path starting from the test root - # that fully prefixes the file. - def add_to_project(to_add) - to_add.each do |entry| - path = entry.path - root_group = find_group_containing(path) - - # Find or create the group to contain the path. - dir_rel_path = path.relative_path_from(root_group.real_path).dirname - group = root_group.find_subpath(dir_rel_path.to_s, true) - - file_ref = group.new_file(path.to_s) - ext = path.extname - - entry.ref = file_ref - end - end - - # Finds a group whose path prefixes the given entry. Starting from the - # project root may not work since not all directories exist within the - # example app. - def find_group_containing(path) - @groups.each do |group| - rel = path.relative_path_from(group.real_path) - next if rel.to_s.start_with?('..') - - return group - end - - raise "Could not find an existing group that's a parent of #{entry.path}" - end - - # Removes the given file references from the project after the file is found - # to not exist on the filesystem but references to it still exist in the - # project. - def remove_from_project(to_remove) - to_remove.each do |entry| - file_ref = entry.ref - file_ref.remove_from_project - end - end - - # Syncs a single build file for a given phase. Returns the number of changes - # made. - def sync_target_entry(target, entry) - return 0 if entry.unchanged? - - phase = find_phase(target, entry.path) - return 0 if phase.nil? - - mark_change_in_group(target.display_name) - if entry.to_add? - printf(" %s - added\n", basename(entry.ref)) - phase.add_file_reference(entry.ref) - else - printf(" %s - removed\n", basename(entry.ref)) - phase.remove_file_reference(entry.ref) - end - - return 1 - end - - # Finds the phase to which the given pathname belongs based on its file - # extension. - # - # Returns nil if the path does not belong in any phase. - def find_phase(target, path) - path = normalize_to_pathname(path) - ext = path.extname - if SOURCES.include?(ext) - return target.source_build_phase - elsif HEADERS.include?(ext) - # TODO(wilhuff): sync headers - #return target.headers_build_phase - return nil - else - return target.resources_build_phase - end - end - - # Syncs build settings to the .xcconfig file for the build configuration, - # avoiding any changes to the Xcode project file. - def sync_xcconfig(target_def, target) - dirty = false - target.build_configurations.each do |config| - requested = flatten(target_def.xcconfig) - - if config.base_configuration_reference.nil? - # Running pod install with PLATFORM set to something other than "all" - # ends up removing baseConfigurationReference entries from the project - # file. Skip these entries when re-running. - puts "Skipping #{target.name} (#{config.name})" - next - end - - path = PODFILE_DIR.join(config.base_configuration_reference.real_path) - if !File.file?(path) - puts "Skipping #{target.name} (#{config.name}); missing xcconfig" - next - end - - contents = Xcodeproj::Config.new(path) - contents.merge!(requested) - contents.save_as(path) - end - end - - # Converts a hash of lists to a flat hash of strings. - def flatten(xcconfig) - result = {} - xcconfig.each do |key, value| - if value.is_a?(Array) - value = value.join(' ') - end - result[key] = value - end - return result - end -end - - -def normalize_to_pathname(file_ref) - if !file_ref.is_a? Pathname - if file_ref.is_a? String - file_ref = Pathname.new(file_ref) - else - file_ref = file_ref.real_path - end - end - return file_ref -end - - -def basename(file_ref) - return normalize_to_pathname(file_ref).basename -end - - -def relative_path(file_ref) - path = normalize_to_pathname(file_ref) - return path.relative_path_from(ROOT_DIR) -end - - -def mark_change_in_group(group) - path = group.to_s - if !@seen_groups.has_key?(path) - puts "#{path} ..." - @seen_groups[path] = true - end -end - - -def sort_project(project) - project.groups.each do |group| - sort_group(group) - end - - project.targets.each do |target| - target.build_phases.each do |phase| - phase.files.sort! { |a, b| - a.file_ref.real_path.basename <=> b.file_ref.real_path.basename - } - end - end -end - - -def sort_group(group) - group.groups.each do |child| - sort_group(child) - end - - group.children.sort! do |a, b| - # Sort groups first - if a.isa == 'PBXGroup' && b.isa != 'PBXGroup' - -1 - elsif a.isa != 'PBXGroup' && b.isa == 'PBXGroup' - 1 - elsif a.display_name && b.display_name - File.basename(a.display_name) <=> File.basename(b.display_name) - else - 0 - end - end -end - - -# Tracks how a file is referenced: in the project file, on the filesystem, -# neither, or both. -class DiffEntry - def initialize(path) - @path = path - @in_source = false - @in_target = false - @ref = nil - end - - attr_reader :path - attr_accessor :in_source, :in_target, :ref - - def unchanged?() - return @in_source && @in_target - end - - def to_add?() - return @in_source && !@in_target - end - - def to_remove?() - return !@in_source && @in_target - end -end - - -# A set of differences between some source and a target. -class Diff - def initialize() - @entries = {} - end - - attr_accessor :entries - - def track(path) - if @entries.has_key?(path) - return @entries[path] - end - - entry = DiffEntry.new(path) - @entries[path] = entry - return entry - end - - # Returns a list of entries that are to be added to the target - def to_add() - return @entries.values.select { |entry| entry.to_add? } - end - - # Returns a list of entries that are to be removed to the target - def to_remove() - return @entries.values.select { |entry| entry.to_remove? } - end - - # Returns a list of entries in sorted order. - def sorted_entries() - return @entries.values.sort { |a, b| a.path.basename <=> b.path.basename } - end - - def changes() - return @entries.values.count { |entry| entry.to_add? || entry.to_remove? } - end -end - - -# Diffs folder groups against the filesystem directories referenced by those -# folder groups. -# -# Folder groups in the project may each refer to an arbitrary path, so -# traversing from a parent group to a subgroup may jump to a radically -# different filesystem location or alias a previously processed directory. -# -# This class performs a diff by essentially tracking only whether or not a -# given absolute path has been seen in either the filesystem or the group -# structure, without paying attention to where in the group structure the file -# reference actually occurs. -# -# This helps ensure that the default arbitrary splits in group structure are -# preserved. For example, "Supporting Files" is an alias for the same directory -# as the parent group, and Apple's default project setup hides some files in -# "Supporting Files". The approach this diff takes preserves this arrangement -# without understanding specifically which files should be hidden and which -# should exist in the parent. -# -# However, this approach has limitations: removing a file from "Supporting -# Files" will be handled, but re-adding the file is likely to add it to the -# group that mirrors the filesystem hierarchy rather than back into its -# original position. So far this approach has been acceptable because there's -# nothing of value in these aliasing folders. Should this change we'll have to -# revisit. -class GroupDiffer - def initialize(dir_lister) - @dir_lister = dir_lister - @dirs = {} - - @diff = Diff.new() - end - - # Finds all files on the filesystem contained within the paths of the given - # groups and computes a list of DiffEntries describing the state of the - # files. - # - # Args: - # - groups: A list of PBXGroup objects representing folder groups within the - # project that contain files of interest. - # - # Returns: - # A hash of Pathname to DiffEntry objects, one for each file found. If the - # file exists on the filesystem, :in_source will be true. If the file exists - # in the project :in_target will be true and :ref will be set to the - # PBXFileReference naming the file. - def diff(groups) - groups.each do |group| - diff_project_files(group) - end - - return @diff - end - - private - # Recursively traverses all the folder groups in the Xcode project and finds - # files both on the filesystem and the group file listing. - def diff_project_files(group) - find_fs_files(group.real_path) - - group.groups.each do |child| - diff_project_files(child) - end - - group.files.each do |file_ref| - path = file_ref.real_path - entry = @diff.track(path) - entry.in_target = true - entry.ref = file_ref - - if path.file? - entry.in_source = true - end - end - end - - def find_fs_files(parent_path) - # Avoid re-traversing the filesystem - if @dirs.has_key?(parent_path) - return - end - @dirs[parent_path] = true - - @dir_lister.entries(parent_path).each do |path| - if path.directory? - find_fs_files(path) - next - end - - entry = @diff.track(path) - entry.in_source = true - end - end -end - - -# Finds files on the filesystem while ignoring files that have been declared to -# be ignored. -class DirectoryLister - def initialize(root_dir) - @root_dir = root_dir - @ignore_basenames = ['.', '..'] - @ignore_pathnames = [] - end - - def add_patterns(patterns) - patterns.each do |pattern| - if File.basename(pattern) != pattern - @ignore_pathnames.push(File.join(@root_dir, pattern)) - else - @ignore_basenames.push(pattern) - end - end - end - - # Finds filesystem entries that are immediate children of the given Pathname, - # ignoring files that match the global ignore_files patterns. - def entries(path) - result = [] - return result if not path.exist? - - path.entries.each do |entry| - next if ignore_basename?(entry) - - file = path.join(entry) - next if ignore_pathname?(file) - - result.push(file) - end - return result - end - - def ignore_basename?(basename) - @ignore_basenames.each do |ignore| - if basename.fnmatch(ignore) - return true - end - end - return false - end - - def ignore_pathname?(file) - @ignore_pathnames.each do |ignore| - if file.fnmatch(ignore) - return true - end - end - return false - end -end - - -if __FILE__ == $0 - main() -end diff --git a/scripts/test_archiving.sh b/scripts/test_archiving.sh deleted file mode 100755 index e2d5cb0..0000000 --- a/scripts/test_archiving.sh +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env bash - -# Copyright 2020 Google LLC -# -# 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. - - -# USAGE: test_archiving.sh pod platform outputPath -# -# Generates the project for the given CocoaPod and attempts to archive it to the provided -# path. - -set -xeuo pipefail - -pod="$1" -platform="$2" -output_path="$3" - -# watchOS is unsupported - `pod gen` can't generate the test schemes. -case "$platform" in - ios) - scheme_name="App-iOS" - ;; - - macos) - scheme_name="App-macOS" - ;; - - tvos) - scheme_name="App-tvOS" - ;; - - # Fail for anything else, invalid input. - *) - exit 1; - ;; -esac - -bundle exec pod gen --local-sources=./ --sources=https://github.com/firebase/SpecsDev.git,https://github.com/firebase/SpecsStaging.git,https://cdn.cocoapods.org/ \ - "$pod".podspec --platforms="$platform" - -args=( - # Run the `archive` command. - "archive" - # Write the archive to a given path. - "-archivePath" "$output_path" - # The generated workspace. - "-workspace" "gen/$pod/$pod.xcworkspace" - # Specify the generated App scheme. - "-scheme" "$scheme_name" - # Disable signing. - "CODE_SIGN_IDENTITY=-" "CODE_SIGNING_REQUIRED=NO" "CODE_SIGNING_ALLOWED=NO" -) - -source scripts/buildcache.sh -args=("${args[@]}" "${buildcache_xcb_flags[@]}") - -xcodebuild -version -gem install xcpretty -xcodebuild "${args[@]}" | xcpretty - -# Print the size if the Xcode build was successful. -if [ $? -eq 0 ]; then - echo "Size of archive:" - # Use `du` to print the file size of all .apps found. The `k` argument prints in KB. - du -sk $(find "$output_path" -name "*.app") -fi diff --git a/scripts/test_catalyst.sh b/scripts/test_catalyst.sh deleted file mode 100755 index eeb87af..0000000 --- a/scripts/test_catalyst.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env bash - -# Copyright 2020 Google LLC -# -# 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. - - -# USAGE: test_catalyst.sh pod build_mode [scheme] -# -# Builds and run tests for Catalyst since it's not yet supported by -# `pod lib lint`. -# The second argument should be "build" or "test". "test" indicates both build -# and test. - -# TODO - Determine why test specs that include `requires_app_host` fail to -# launch tests. Locally, they will pass if the only Objective-C unit test scheme -# is specified. However, on GHA, they fail to launch both from the test scheme -# and the app scheme. - -set -xeuo pipefail -pod="$1" -build_mode="$2" - -if [[ $# -gt 2 ]]; then - scheme="$3" -else - scheme="$pod" -fi - -bundle exec pod gen --local-sources=./ --sources=https://github.com/firebase/SpecsDev.git,https://github.com/firebase/SpecsStaging.git,https://cdn.cocoapods.org/ \ - "$pod".podspec --platforms=ios - -args=( - # Build or test. - "$build_mode" - # Tests that require NSAssert's to fire need Debug. - "-configuration" "Debug" - # The generated workspace. - "-workspace" "gen/$pod/$pod.xcworkspace" - # Specify the app if all test should run. Otherwise, specify the test scheme. - "-scheme" "$scheme" - # Specify Catalyst. - "ARCHS=x86_64" "VALID_ARCHS=x86_64" "SUPPORTS_MACCATALYST=YES" - # Run on macOS. - "-sdk" "macosx" "-destination platform=\"OS X\"" "TARGETED_DEVICE_FAMILY=2" - # Disable signing. - "CODE_SIGN_IDENTITY=-" "CODE_SIGNING_REQUIRED=NO" "CODE_SIGNING_ALLOWED=NO" - # GHA is still running 10.15. - "MACOSX_DEPLOYMENT_TARGET=10.15" -) - -source scripts/buildcache.sh -args=("${args[@]}" "${buildcache_xcb_flags[@]}") - -xcodebuild -version -gem install xcpretty -xcodebuild "${args[@]}" | xcpretty diff --git a/scripts/test_quickstart.sh b/scripts/test_quickstart.sh deleted file mode 100755 index 4db8200..0000000 --- a/scripts/test_quickstart.sh +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright 2019 Google -# -# 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. - - -# Run a CI `script` phase to build the associated quickstart -# sample and run its tests. - -set -xeuo pipefail - -sample="$1" -test="$2" -language="${3-}" - -# Source function to check if CI secrets are available. -source scripts/check_secrets.sh - -if check_secrets; then - cd quickstart-ios - if [ "$language" = "swift" ]; then - have_secrets=true SAMPLE="$sample" TEST="$test" SWIFT_SUFFIX="Swift" ./scripts/test.sh - else - have_secrets=true SAMPLE="$sample" TEST="$test" ./scripts/test.sh - fi - -fi diff --git a/scripts/test_quickstart_framework.sh b/scripts/test_quickstart_framework.sh deleted file mode 100755 index 8b39053..0000000 --- a/scripts/test_quickstart_framework.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -# Copyright 2020 Google LLC -# -# 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. - - -# Run a CI `script` phase to build the associated quickstart -# sample and run its tests. - -set -xeuo pipefail - -sample="$1" -platform="${2-}" - -REPO=`pwd` - -# Source function to check if CI secrets are available. -source scripts/check_secrets.sh - -if check_secrets; then - cd quickstart-ios - if [ "$platform" = "swift" ]; then - have_secrets=true SAMPLE="$sample" SWIFT_SUFFIX="Swift" "${REPO}"/scripts/zip_quickstart_test.sh - else - have_secrets=true SAMPLE="$sample" "${REPO}"/scripts/zip_quickstart_test.sh - fi -fi diff --git a/scripts/test_quickstart_ftl.sh b/scripts/test_quickstart_ftl.sh deleted file mode 100755 index 76148af..0000000 --- a/scripts/test_quickstart_ftl.sh +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright 2022 Google LLC -# -# 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. - - -# This script is modified from test_quickstart.sh. -# Run a CI `script` phase to build the associated quickstart sample -# and generate build-for-testing artfacts, which can be used to -# run test on Firebase Test Lab. -# The artifacts are under dir: `quickstart-ios/build-for-testing` - -set -xeuo pipefail - -sample="$1" -language="${2-}" - -# Source function to check if CI secrets are available. -source scripts/check_secrets.sh - -if check_secrets; then - cd quickstart-ios - if [ "$language" = "swift" ]; then - have_secrets=true SAMPLE="$sample" SWIFT_SUFFIX="Swift" ./scripts/build-for-testing.sh - else - have_secrets=true SAMPLE="$sample" ./scripts/build-for-testing.sh - fi - -fi diff --git a/scripts/third_party/travis/LICENSE b/scripts/third_party/travis/LICENSE deleted file mode 100644 index 0cafc33..0000000 --- a/scripts/third_party/travis/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -MIT LICENSE - -Copyright (c) 2018 Travis CI GmbH - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/scripts/third_party/travis/retry.sh b/scripts/third_party/travis/retry.sh deleted file mode 100755 index 4f55eeb..0000000 --- a/scripts/third_party/travis/retry.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/bash - -#MIT LICENSE -# -#Copyright (c) 2018 Travis CI GmbH -# -#Permission is hereby granted, free of charge, to any person obtaining a copy of -#this software and associated documentation files (the "Software"), to deal in -#the Software without restriction, including without limitation the rights to -#use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -#the Software, and to permit persons to whom the Software is furnished to do so, -#subject to the following conditions: -# -#The above copyright notice and this permission notice shall be included in all -#copies or substantial portions of the Software. -# -#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -#FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -#COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -#IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -#CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -# From https://github.com/tianon/travis-build/blob/e3400b7bd417407492e3916e9f7a62315f584ad5/lib/travis/build/templates/header.sh - -ANSI_RED="\033[31;1m" -ANSI_GREEN="\033[32;1m" -ANSI_YELLOW="\033[33;1m" -ANSI_RESET="\033[0m" -ANSI_CLEAR="\033[0K" - -# Number of attempts. -RETRY_COUNT=2 - -travis_retry() { - local result=0 - local count=1 - while [ $count -le $RETRY_COUNT ]; do - [ $result -ne 0 ] && { - echo -e "\n${ANSI_RED}The command \"$@\" failed. Retrying, $count of ${RETRY_COUNT}.${ANSI_RESET}\n" >&2 - } - "$@" && { result=0 && break; } || result=$? - count=$(($count + 1)) - sleep 1 - done - - [ $count -gt $RETRY_COUNT ] && { - echo -e "\n${ANSI_RED}The command \"$@\" failed ${RETRY_COUNT} times.${ANSI_RESET}\n" >&2 - } - - return $result -} - -travis_retry "$@" diff --git a/scripts/update_xcode_target.rb b/scripts/update_xcode_target.rb deleted file mode 100755 index f9f5838..0000000 --- a/scripts/update_xcode_target.rb +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env ruby - -# Copyright 2021 Google LLC -# -# 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. - -# Script to add a file to an Xcode target. -# Adapted from https://github.com/firebase/quickstart-ios/blob/main/scripts/info_script.rb - -require 'xcodeproj' -project_path = ARGV[0] -target = ARGV[1] -file_name = ARGV[2] - -project = Xcodeproj::Project.open(project_path) - -# Add a file to the project in the main group -file = project.new_file(file_name) - -# Add the file to the all targets -project.targets.each do |t| - if t.to_s == target - if file_name.end_with?(".json") then - t.add_resources([file]) - else - t.add_file_references([file]) - end - end -end - -# Save project -project.save() diff --git a/scripts/xcresult_logs.py b/scripts/xcresult_logs.py deleted file mode 100755 index de73f92..0000000 --- a/scripts/xcresult_logs.py +++ /dev/null @@ -1,290 +0,0 @@ -#!/usr/bin/env python - -# Copyright 2020 Google LLC -# -# 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. - -"""Prints logs from test runs captured in Apple .xcresult bundles. - -USAGE: xcresult_logs.py -workspace -scheme [other flags...] - -xcresult_logs.py finds and displays the log output associated with an xcodebuild -invocation. Pass your entire xcodebuild command-line as arguments to this script -and it will find the output associated with the most recent invocation. -""" - -import json -import logging -import os -import re -import shutil -import subprocess -import sys - -from lib import command_trace - -_logger = logging.getLogger('xcresult') - - -def main(): - args = sys.argv[1:] - if not args: - sys.stdout.write(__doc__) - sys.exit(1) - - logging.basicConfig(format='%(message)s', level=logging.DEBUG) - - flags = parse_xcodebuild_flags(args) - - # If the result bundle path is specified in the xcodebuild flags, use that - # otherwise, deduce - xcresult_path = flags.get('-resultBundlePath') - if xcresult_path is None: - project = project_from_workspace_path(flags['-workspace']) - scheme = flags['-scheme'] - xcresult_path = find_xcresult_path(project, scheme) - - log_id = find_log_id(xcresult_path) - log = export_log(xcresult_path, log_id) - - # Avoid a potential UnicodeEncodeError raised by sys.stdout.write() by - # doing a relaxed encoding ourselves. - if hasattr(sys.stdout, 'buffer'): - log_encoded = log.encode('utf8', errors='backslashreplace') - sys.stdout.flush() - sys.stdout.buffer.write(log_encoded) - else: - log_encoded = log.encode('ascii', errors='backslashreplace') - log_decoded = log_encoded.decode('ascii', errors='strict') - sys.stdout.write(log_decoded) - - -# Most flags on the xcodebuild command-line are uninteresting, so only pull -# flags with known behavior with names in this set. -INTERESTING_FLAGS = { - '-resultBundlePath', - '-scheme', - '-workspace', -} - - -def parse_xcodebuild_flags(args): - """Parses the xcodebuild command-line. - - Extracts flags like -workspace and -scheme that dictate the location of the - logs. - """ - result = {} - key = None - for arg in args: - if arg.startswith('-'): - if arg in INTERESTING_FLAGS: - key = arg - elif key is not None: - result[key] = arg - key = None - - return result - - -def project_from_workspace_path(path): - """Extracts the project name from a workspace path. - Args: - path: The path to a .xcworkspace file - - Returns: - The project name from the basename of the path. For example, if path were - 'Firestore/Example/Firestore.xcworkspace', returns 'Firestore'. - """ - root, ext = os.path.splitext(os.path.basename(path)) - if ext == '.xcworkspace': - _logger.debug('Using project %s from workspace %s', root, path) - return root - - raise ValueError('%s is not a valid workspace path' % path) - - -def find_xcresult_path(project, scheme): - """Finds an xcresult bundle for the given project and scheme. - - Args: - project: The project name, like 'Firestore' - scheme: The Xcode scheme that was tested - - Returns: - The path to the newest xcresult bundle that matches. - """ - project_path = find_project_path(project) - bundle_dir = os.path.join(project_path, 'Logs/Test') - prefix = re.compile('([^-]*)-' + re.escape(scheme) + '-') - - _logger.debug('Logging for xcresult bundles in %s', bundle_dir) - xcresult = find_newest_matching_prefix(bundle_dir, prefix) - if xcresult is None: - raise LookupError( - 'Could not find xcresult bundle for %s in %s' % (scheme, bundle_dir)) - - _logger.debug('Found xcresult: %s', xcresult) - return xcresult - - -def find_project_path(project): - """Finds the newest project output within Xcode's DerivedData. - - Args: - project: A project name; the Foo in Foo.xcworkspace - - Returns: - The path containing the newest project output. - """ - path = os.path.expanduser('~/Library/Developer/Xcode/DerivedData') - prefix = re.compile(re.escape(project) + '-') - - # DerivedData has directories like Firestore-csljdukzqbozahdjizcvrfiufrkb. Use - # the most recent one if there are more than one such directory matching the - # project name. - result = find_newest_matching_prefix(path, prefix) - if result is None: - raise LookupError( - 'Could not find project derived data for %s in %s' % (project, path)) - - _logger.debug('Using project derived data in %s', result) - return result - - -def find_newest_matching_prefix(path, prefix): - """Lists the given directory and returns the newest entry matching prefix. - - Args: - path: A directory to list - prefix: A regular expression that matches the filenames to consider - - Returns: - The path to the newest entry in the directory whose basename starts with - the prefix. - """ - entries = os.listdir(path) - result = None - for entry in entries: - if prefix.match(entry): - fq_entry = os.path.join(path, entry) - if result is None: - result = fq_entry - else: - result_mtime = os.path.getmtime(result) - entry_mtime = os.path.getmtime(fq_entry) - if entry_mtime > result_mtime: - result = fq_entry - - return result - - -def find_legacy_log_files(xcresult_path): - """Finds the log files produced by Xcode 10 and below.""" - - result = [] - - for root, dirs, files in os.walk(xcresult_path, topdown=True): - for file in files: - if file.endswith('.txt'): - file = os.path.join(root, file) - result.append(file) - - # Sort the files by creation time. - result.sort(key=lambda f: os.stat(f).st_ctime) - return result - - -def cat_files(files, output): - """Reads the contents of all the files and copies them to the output. - - Args: - files: A list of filenames - output: A file-like object in which all the data should be copied. - """ - for file in files: - with open(file, 'r') as fd: - shutil.copyfileobj(fd, output) - - -def find_log_id(xcresult_path): - """Finds the id of the last action's logs. - - Args: - xcresult_path: The path to an xcresult bundle. - - Returns: - The id of the log output, suitable for use with xcresulttool get --id. - """ - parsed = xcresulttool_json('get', '--path', xcresult_path) - actions = parsed['actions']['_values'] - action = actions[-1] - - result = action['actionResult']['logRef']['id']['_value'] - _logger.debug('Using log id %s', result) - return result - - -def export_log(xcresult_path, log_id): - """Exports the log data with the given id from the xcresult bundle. - - Args: - xcresult_path: The path to an xcresult bundle. - log_id: The id that names the log output (obtained by find_log_id) - - Returns: - The logged output, as a string. - """ - contents = xcresulttool_json('get', '--path', xcresult_path, '--id', log_id) - - result = [] - collect_log_output(contents, result) - return ''.join(result) - - -def collect_log_output(activity_log, result): - """Recursively collects emitted output from the activity log. - - Args: - activity_log: Parsed JSON of an xcresult activity log. - result: An array into which all log data should be appended. - """ - output = activity_log.get('emittedOutput') - if output: - result.append(output['_value']) - else: - subsections = activity_log.get('subsections') - if subsections: - for subsection in subsections['_values']: - collect_log_output(subsection, result) - - -def xcresulttool(*args): - """Runs xcresulttool and returns its output as a string.""" - cmd = ['xcrun', 'xcresulttool'] - cmd.extend(args) - - command_trace.log(cmd) - - return subprocess.check_output(cmd) - - -def xcresulttool_json(*args): - """Runs xcresulttool and its output as parsed JSON.""" - args = list(args) + ['--format', 'json'] - contents = xcresulttool(*args) - return json.loads(contents) - - -if __name__ == '__main__': - main() diff --git a/scripts/zip_quickstart_test.sh b/scripts/zip_quickstart_test.sh deleted file mode 100755 index 3fb13eb..0000000 --- a/scripts/zip_quickstart_test.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash - -# Copyright 2022 Google LLC -# -# 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. - - -# Build the quickstart. If we're running on the main repo (not a fork), we -# also run the tests along with the decoded GoogleService-Info.plist files. - -set -eo pipefail - -set -x - -EXIT_STATUS=0 - -cd "${SAMPLE}" - -if [[ ! -z "$LEGACY" ]]; then - cd "Legacy${SAMPLE}Quickstart" -fi - -( -xcodebuild \ --project ${SAMPLE}Example.xcodeproj \ --scheme ${SAMPLE}Example${SWIFT_SUFFIX} \ --destination 'platform=iOS Simulator,name=iPhone 14' "SWIFT_VERSION=5.3" "OTHER_LDFLAGS=\$(OTHER_LDFLAGS) -ObjC" "FRAMEWORK_SEARCH_PATHS= \$(PROJECT_DIR)/Firebase/" HEADER_SEARCH_PATHS='$(PROJECT_DIR)/Firebase' \ -build \ -) || EXIT_STATUS=$? - -exit $EXIT_STATUS diff --git a/scripts/api_diff_report/prerequisite.sh b/setup-scripts.sh similarity index 69% rename from scripts/api_diff_report/prerequisite.sh rename to setup-scripts.sh index 7e91874..42eecd6 100755 --- a/scripts/api_diff_report/prerequisite.sh +++ b/setup-scripts.sh @@ -1,20 +1,22 @@ -#!/bin/bash -e +#!/bin/bash -# Copyright 2023 Google LLC -# +# Copyright 2021 Google LLC # 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. -gem install jazzy -v 0.14.3 -pip install BeautifulSoup4 -pip install pytz -pip install requests +git clone \ + --depth 1 \ + --no-checkout \ + https://github.com/firebase/firebase-ios-sdk.git \ +; +cd firebase-ios-sdk +git checkout main -- scripts +cd .. +ln -s firebase-ios-sdk/scripts scripts