(raulenrique) | dfdda47 | 2018-06-04 12:02:29 -0700 | [diff] [blame] | 1 | # Copyright (C) 2018 The Android Open Source Project |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | # you may not use this file except in compliance with the License. |
| 5 | # You may obtain a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | # See the License for the specific language governing permissions and |
| 13 | # limitations under the License. |
| 14 | """Tool functions to deal with files.""" |
| 15 | |
| 16 | import datetime |
Dan Albert | 308130a | 2023-04-12 23:07:23 +0000 | [diff] [blame] | 17 | from functools import cache |
(raulenrique) | dfdda47 | 2018-06-04 12:02:29 -0700 | [diff] [blame] | 18 | import os |
Haibo Huang | 329e681 | 2020-05-29 14:12:20 -0700 | [diff] [blame] | 19 | from pathlib import Path |
Haibo Huang | 27d2e0b | 2021-02-10 15:20:19 -0800 | [diff] [blame] | 20 | import textwrap |
(raulenrique) | dfdda47 | 2018-06-04 12:02:29 -0700 | [diff] [blame] | 21 | |
Haibo Huang | 329e681 | 2020-05-29 14:12:20 -0700 | [diff] [blame] | 22 | # pylint: disable=import-error |
| 23 | from google.protobuf import text_format # type: ignore |
(raulenrique) | dfdda47 | 2018-06-04 12:02:29 -0700 | [diff] [blame] | 24 | |
Haibo Huang | 329e681 | 2020-05-29 14:12:20 -0700 | [diff] [blame] | 25 | # pylint: disable=import-error |
| 26 | import metadata_pb2 # type: ignore |
(raulenrique) | dfdda47 | 2018-06-04 12:02:29 -0700 | [diff] [blame] | 27 | |
Dan Albert | 0486187 | 2023-04-07 21:15:47 +0000 | [diff] [blame] | 28 | |
(raulenrique) | dfdda47 | 2018-06-04 12:02:29 -0700 | [diff] [blame] | 29 | METADATA_FILENAME = 'METADATA' |
| 30 | |
| 31 | |
Dan Albert | 308130a | 2023-04-12 23:07:23 +0000 | [diff] [blame] | 32 | @cache |
| 33 | def external_path() -> Path: |
| 34 | """Returns the path to //external. |
| 35 | |
| 36 | We cannot use the relative path from this file to find the top of the tree because |
| 37 | this will often be run in a "compiled" form from an arbitrary location in the out |
| 38 | directory. We can't fully rely on ANDROID_BUILD_TOP because not all contexts will |
| 39 | have run envsetup/lunch either. We use ANDROID_BUILD_TOP whenever it is set, but if |
| 40 | it is not set we instead rely on the convention that the CWD is the root of the tree |
| 41 | (updater.sh will cd there before executing). |
| 42 | |
| 43 | There is one other context where this function cannot succeed: CI. Tests run in CI |
| 44 | do not have a source tree to find, so calling this function in that context will |
| 45 | fail. |
| 46 | """ |
| 47 | android_top = Path(os.environ.get("ANDROID_BUILD_TOP", os.getcwd())) |
| 48 | top = android_top / 'external' |
| 49 | |
| 50 | if not top.exists(): |
| 51 | raise RuntimeError( |
| 52 | f"{top} does not exist. This program must be run from the " |
| 53 | f"root of an Android tree (CWD is {os.getcwd()})." |
| 54 | ) |
| 55 | return top |
| 56 | |
| 57 | |
Haibo Huang | a08fb60 | 2020-05-29 16:24:13 -0700 | [diff] [blame] | 58 | def get_absolute_project_path(proj_path: Path) -> Path: |
(raulenrique) | dfdda47 | 2018-06-04 12:02:29 -0700 | [diff] [blame] | 59 | """Gets absolute path of a project. |
| 60 | |
| 61 | Path resolution starts from external/. |
| 62 | """ |
Dan Albert | 308130a | 2023-04-12 23:07:23 +0000 | [diff] [blame] | 63 | return external_path() / proj_path |
(raulenrique) | dfdda47 | 2018-06-04 12:02:29 -0700 | [diff] [blame] | 64 | |
| 65 | |
Haibo Huang | a08fb60 | 2020-05-29 16:24:13 -0700 | [diff] [blame] | 66 | def get_metadata_path(proj_path: Path) -> Path: |
(raulenrique) | dfdda47 | 2018-06-04 12:02:29 -0700 | [diff] [blame] | 67 | """Gets the absolute path of METADATA for a project.""" |
Haibo Huang | a08fb60 | 2020-05-29 16:24:13 -0700 | [diff] [blame] | 68 | return get_absolute_project_path(proj_path) / METADATA_FILENAME |
(raulenrique) | dfdda47 | 2018-06-04 12:02:29 -0700 | [diff] [blame] | 69 | |
| 70 | |
Haibo Huang | a08fb60 | 2020-05-29 16:24:13 -0700 | [diff] [blame] | 71 | def get_relative_project_path(proj_path: Path) -> Path: |
(raulenrique) | dfdda47 | 2018-06-04 12:02:29 -0700 | [diff] [blame] | 72 | """Gets the relative path of a project starting from external/.""" |
Dan Albert | 308130a | 2023-04-12 23:07:23 +0000 | [diff] [blame] | 73 | return get_absolute_project_path(proj_path).relative_to(external_path()) |
(raulenrique) | dfdda47 | 2018-06-04 12:02:29 -0700 | [diff] [blame] | 74 | |
| 75 | |
Dan Albert | 25ea592 | 2023-03-23 22:32:54 +0000 | [diff] [blame] | 76 | def canonicalize_project_path(proj_path: Path) -> Path: |
| 77 | """Returns the canonical representation of the project path. |
| 78 | |
| 79 | For paths that are in the same tree as external_updater (the common case), the |
| 80 | canonical path is the path of the project relative to //external. |
| 81 | |
| 82 | For paths that are in a different tree (an uncommon case used for updating projects |
| 83 | in other builds such as the NDK), the canonical path is the absolute path. |
| 84 | """ |
| 85 | try: |
| 86 | return get_relative_project_path(proj_path) |
| 87 | except ValueError: |
| 88 | # A less common use case, but the path might be to a non-local tree, in which case |
| 89 | # the path will not be relative to our tree. This happens when using |
| 90 | # external_updater in another project like the NDK or rr. |
| 91 | if proj_path.is_absolute(): |
| 92 | return proj_path |
| 93 | |
| 94 | # Not relative to //external, and not an absolute path. This case hasn't existed |
| 95 | # before, so it has no canonical form. |
| 96 | raise ValueError( |
Dan Albert | 308130a | 2023-04-12 23:07:23 +0000 | [diff] [blame] | 97 | f"{proj_path} must be either an absolute path or relative to {external_path()}" |
Dan Albert | 25ea592 | 2023-03-23 22:32:54 +0000 | [diff] [blame] | 98 | ) |
| 99 | |
| 100 | |
Haibo Huang | a08fb60 | 2020-05-29 16:24:13 -0700 | [diff] [blame] | 101 | def read_metadata(proj_path: Path) -> metadata_pb2.MetaData: |
(raulenrique) | dfdda47 | 2018-06-04 12:02:29 -0700 | [diff] [blame] | 102 | """Reads and parses METADATA file for a project. |
| 103 | |
| 104 | Args: |
| 105 | proj_path: Path to the project. |
| 106 | |
| 107 | Returns: |
| 108 | Parsed MetaData proto. |
| 109 | |
| 110 | Raises: |
| 111 | text_format.ParseError: Occurred when the METADATA file is invalid. |
| 112 | FileNotFoundError: Occurred when METADATA file is not found. |
| 113 | """ |
| 114 | |
Haibo Huang | 329e681 | 2020-05-29 14:12:20 -0700 | [diff] [blame] | 115 | with get_metadata_path(proj_path).open('r') as metadata_file: |
(raulenrique) | dfdda47 | 2018-06-04 12:02:29 -0700 | [diff] [blame] | 116 | metadata = metadata_file.read() |
| 117 | return text_format.Parse(metadata, metadata_pb2.MetaData()) |
| 118 | |
| 119 | |
Chih-Hung Hsieh | dd1915d | 2020-09-29 14:22:12 -0700 | [diff] [blame] | 120 | def write_metadata(proj_path: Path, metadata: metadata_pb2.MetaData, keep_date: bool) -> None: |
(raulenrique) | dfdda47 | 2018-06-04 12:02:29 -0700 | [diff] [blame] | 121 | """Writes updated METADATA file for a project. |
| 122 | |
| 123 | This function updates last_upgrade_date in metadata and write to the project |
| 124 | directory. |
| 125 | |
| 126 | Args: |
| 127 | proj_path: Path to the project. |
| 128 | metadata: The MetaData proto to write. |
Chih-Hung Hsieh | dd1915d | 2020-09-29 14:22:12 -0700 | [diff] [blame] | 129 | keep_date: Do not change date. |
(raulenrique) | dfdda47 | 2018-06-04 12:02:29 -0700 | [diff] [blame] | 130 | """ |
| 131 | |
Chih-Hung Hsieh | dd1915d | 2020-09-29 14:22:12 -0700 | [diff] [blame] | 132 | if not keep_date: |
| 133 | date = metadata.third_party.last_upgrade_date |
| 134 | now = datetime.datetime.now() |
| 135 | date.year = now.year |
| 136 | date.month = now.month |
| 137 | date.day = now.day |
Dan Albert | 98dfa74 | 2023-03-23 23:07:27 +0000 | [diff] [blame] | 138 | try: |
Dan Albert | 12e45e4 | 2023-03-29 21:50:05 +0000 | [diff] [blame] | 139 | rel_proj_path = str(get_relative_project_path(proj_path)) |
Dan Albert | 98dfa74 | 2023-03-23 23:07:27 +0000 | [diff] [blame] | 140 | except ValueError: |
| 141 | # Absolute paths to other trees will not be relative to our tree. There are |
| 142 | # not portable instructions for upgrading that project, since the path will |
| 143 | # differ between machines (or checkouts). |
| 144 | rel_proj_path = "<absolute path to project>" |
Sadaf Ebrahimi | 47d0bac | 2022-10-21 17:47:06 +0000 | [diff] [blame] | 145 | usage_hint = textwrap.dedent(f"""\ |
| 146 | # This project was upgraded with external_updater. |
Sadaf Ebrahimi | 4f27e4c | 2022-11-02 16:34:03 +0000 | [diff] [blame] | 147 | # Usage: tools/external_updater/updater.sh update {rel_proj_path} |
Sadaf Ebrahimi | 47d0bac | 2022-10-21 17:47:06 +0000 | [diff] [blame] | 148 | # For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md |
| 149 | |
| 150 | """) |
| 151 | text_metadata = usage_hint + text_format.MessageToString(metadata) |
Haibo Huang | 329e681 | 2020-05-29 14:12:20 -0700 | [diff] [blame] | 152 | with get_metadata_path(proj_path).open('w') as metadata_file: |
Haibo Huang | 27d2e0b | 2021-02-10 15:20:19 -0800 | [diff] [blame] | 153 | if metadata.third_party.license_type == metadata_pb2.LicenseType.BY_EXCEPTION_ONLY: |
| 154 | metadata_file.write(textwrap.dedent("""\ |
Sadaf Ebrahimi | 47d0bac | 2022-10-21 17:47:06 +0000 | [diff] [blame] | 155 | # THIS PACKAGE HAS SPECIAL LICENSING CONDITIONS. PLEASE |
| 156 | # CONSULT THE OWNERS AND opensource-licensing@google.com BEFORE |
| 157 | # DEPENDING ON IT IN YOUR PROJECT. |
| 158 | |
Haibo Huang | 27d2e0b | 2021-02-10 15:20:19 -0800 | [diff] [blame] | 159 | """)) |
(raulenrique) | dfdda47 | 2018-06-04 12:02:29 -0700 | [diff] [blame] | 160 | metadata_file.write(text_metadata) |