1*3c875a21SAndroid Build Coastguard Worker# Copyright (C) 2018 The Android Open Source Project 2*3c875a21SAndroid Build Coastguard Worker# 3*3c875a21SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 4*3c875a21SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 5*3c875a21SAndroid Build Coastguard Worker# You may obtain a copy of the License at 6*3c875a21SAndroid Build Coastguard Worker# 7*3c875a21SAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 8*3c875a21SAndroid Build Coastguard Worker# 9*3c875a21SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 10*3c875a21SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 11*3c875a21SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*3c875a21SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 13*3c875a21SAndroid Build Coastguard Worker# limitations under the License. 14*3c875a21SAndroid Build Coastguard Worker"""Tool functions to deal with files.""" 15*3c875a21SAndroid Build Coastguard Worker 16*3c875a21SAndroid Build Coastguard Workerimport datetime 17*3c875a21SAndroid Build Coastguard Workerimport enum 18*3c875a21SAndroid Build Coastguard Workerimport os 19*3c875a21SAndroid Build Coastguard Workerfrom pathlib import Path 20*3c875a21SAndroid Build Coastguard Workerimport textwrap 21*3c875a21SAndroid Build Coastguard Worker 22*3c875a21SAndroid Build Coastguard Worker# pylint: disable=import-error 23*3c875a21SAndroid Build Coastguard Workerfrom google.protobuf import text_format # type: ignore 24*3c875a21SAndroid Build Coastguard Worker 25*3c875a21SAndroid Build Coastguard Worker# pylint: disable=import-error 26*3c875a21SAndroid Build Coastguard Workerimport metadata_pb2 # type: ignore 27*3c875a21SAndroid Build Coastguard Worker 28*3c875a21SAndroid Build Coastguard Worker 29*3c875a21SAndroid Build Coastguard WorkerMETADATA_FILENAME = 'METADATA' 30*3c875a21SAndroid Build Coastguard Worker 31*3c875a21SAndroid Build Coastguard Worker 32*3c875a21SAndroid Build Coastguard Worker@enum.unique 33*3c875a21SAndroid Build Coastguard Workerclass IdentifierType(enum.Enum): 34*3c875a21SAndroid Build Coastguard Worker """A subset of different Identifier types""" 35*3c875a21SAndroid Build Coastguard Worker GIT = 'Git' 36*3c875a21SAndroid Build Coastguard Worker SVN = 'SVN' 37*3c875a21SAndroid Build Coastguard Worker HG = 'Hg' 38*3c875a21SAndroid Build Coastguard Worker DARCS = 'Darcs' 39*3c875a21SAndroid Build Coastguard Worker ARCHIVE = 'Archive' 40*3c875a21SAndroid Build Coastguard Worker OTHER = 'Other' 41*3c875a21SAndroid Build Coastguard Worker 42*3c875a21SAndroid Build Coastguard Worker 43*3c875a21SAndroid Build Coastguard Workerdef find_tree_containing(project: Path) -> Path: 44*3c875a21SAndroid Build Coastguard Worker """Returns the path to the repo tree parent of the given project. 45*3c875a21SAndroid Build Coastguard Worker 46*3c875a21SAndroid Build Coastguard Worker The parent tree is found by searching up the directory tree until a 47*3c875a21SAndroid Build Coastguard Worker directory is found that contains a .repo directory. Other methods of 48*3c875a21SAndroid Build Coastguard Worker finding this directory won't necessarily work: 49*3c875a21SAndroid Build Coastguard Worker 50*3c875a21SAndroid Build Coastguard Worker * Using ANDROID_BUILD_TOP might find the wrong tree (if external_updater 51*3c875a21SAndroid Build Coastguard Worker is used to manage a project that is not in AOSP, as it does for CMake, 52*3c875a21SAndroid Build Coastguard Worker rr, and a few others), since ANDROID_BUILD_TOP will be the one that built 53*3c875a21SAndroid Build Coastguard Worker external_updater rather than the given project. 54*3c875a21SAndroid Build Coastguard Worker * Paths relative to __file__ are no good because we'll run from a "built" 55*3c875a21SAndroid Build Coastguard Worker PAR somewhere in the soong out directory, or possibly somewhere more 56*3c875a21SAndroid Build Coastguard Worker arbitrary when run from CI. 57*3c875a21SAndroid Build Coastguard Worker * Paths relative to the CWD require external_updater to be run from a 58*3c875a21SAndroid Build Coastguard Worker predictable location. Doing so prevents the user from using relative 59*3c875a21SAndroid Build Coastguard Worker paths (and tab complete) from directories other than the expected location. 60*3c875a21SAndroid Build Coastguard Worker 61*3c875a21SAndroid Build Coastguard Worker The result for one project should not be reused for other projects, 62*3c875a21SAndroid Build Coastguard Worker as it's possible that the user has provided project paths from multiple 63*3c875a21SAndroid Build Coastguard Worker trees. 64*3c875a21SAndroid Build Coastguard Worker """ 65*3c875a21SAndroid Build Coastguard Worker if (project / ".repo").exists(): 66*3c875a21SAndroid Build Coastguard Worker return project 67*3c875a21SAndroid Build Coastguard Worker if project.parent == project: 68*3c875a21SAndroid Build Coastguard Worker raise FileNotFoundError( 69*3c875a21SAndroid Build Coastguard Worker f"Could not find a .repo directory in any parent of {project}" 70*3c875a21SAndroid Build Coastguard Worker ) 71*3c875a21SAndroid Build Coastguard Worker return find_tree_containing(project.parent) 72*3c875a21SAndroid Build Coastguard Worker 73*3c875a21SAndroid Build Coastguard Worker 74*3c875a21SAndroid Build Coastguard Workerdef external_path() -> Path: 75*3c875a21SAndroid Build Coastguard Worker """Returns the path to //external. 76*3c875a21SAndroid Build Coastguard Worker 77*3c875a21SAndroid Build Coastguard Worker We cannot use the relative path from this file to find the top of the 78*3c875a21SAndroid Build Coastguard Worker tree because this will often be run in a "compiled" form from an 79*3c875a21SAndroid Build Coastguard Worker arbitrary location in the out directory. We can't fully rely on 80*3c875a21SAndroid Build Coastguard Worker ANDROID_BUILD_TOP because not all contexts will have run envsetup/lunch 81*3c875a21SAndroid Build Coastguard Worker either. We use ANDROID_BUILD_TOP whenever it is set, but if it is not set 82*3c875a21SAndroid Build Coastguard Worker we instead rely on the convention that the CWD is the root of the tree ( 83*3c875a21SAndroid Build Coastguard Worker updater.sh will cd there before executing). 84*3c875a21SAndroid Build Coastguard Worker 85*3c875a21SAndroid Build Coastguard Worker There is one other context where this function cannot succeed: CI. Tests 86*3c875a21SAndroid Build Coastguard Worker run in CI do not have a source tree to find, so calling this function in 87*3c875a21SAndroid Build Coastguard Worker that context will fail. 88*3c875a21SAndroid Build Coastguard Worker """ 89*3c875a21SAndroid Build Coastguard Worker android_top = Path(os.environ.get("ANDROID_BUILD_TOP", os.getcwd())) 90*3c875a21SAndroid Build Coastguard Worker top = android_top / 'external' 91*3c875a21SAndroid Build Coastguard Worker 92*3c875a21SAndroid Build Coastguard Worker if not top.exists(): 93*3c875a21SAndroid Build Coastguard Worker raise RuntimeError( 94*3c875a21SAndroid Build Coastguard Worker f"{top} does not exist. This program must be run from the " 95*3c875a21SAndroid Build Coastguard Worker f"root of an Android tree (CWD is {os.getcwd()})." 96*3c875a21SAndroid Build Coastguard Worker ) 97*3c875a21SAndroid Build Coastguard Worker return top 98*3c875a21SAndroid Build Coastguard Worker 99*3c875a21SAndroid Build Coastguard Worker 100*3c875a21SAndroid Build Coastguard Workerdef get_absolute_project_path(proj_path: Path) -> Path: 101*3c875a21SAndroid Build Coastguard Worker """Gets absolute path of a project. 102*3c875a21SAndroid Build Coastguard Worker 103*3c875a21SAndroid Build Coastguard Worker Path resolution starts from external/. 104*3c875a21SAndroid Build Coastguard Worker """ 105*3c875a21SAndroid Build Coastguard Worker if proj_path.is_absolute(): 106*3c875a21SAndroid Build Coastguard Worker return proj_path 107*3c875a21SAndroid Build Coastguard Worker return external_path() / proj_path 108*3c875a21SAndroid Build Coastguard Worker 109*3c875a21SAndroid Build Coastguard Worker 110*3c875a21SAndroid Build Coastguard Workerdef resolve_command_line_paths(paths: list[str]) -> list[Path]: 111*3c875a21SAndroid Build Coastguard Worker """Resolves project paths provided by the command line. 112*3c875a21SAndroid Build Coastguard Worker 113*3c875a21SAndroid Build Coastguard Worker Both relative and absolute paths are resolved to fully qualified paths 114*3c875a21SAndroid Build Coastguard Worker and returned. If any path does not exist relative to the CWD, a message 115*3c875a21SAndroid Build Coastguard Worker will be printed and that path will be pruned from the list. 116*3c875a21SAndroid Build Coastguard Worker """ 117*3c875a21SAndroid Build Coastguard Worker resolved: list[Path] = [] 118*3c875a21SAndroid Build Coastguard Worker for path_str in paths: 119*3c875a21SAndroid Build Coastguard Worker path = Path(path_str) 120*3c875a21SAndroid Build Coastguard Worker if not path.exists(): 121*3c875a21SAndroid Build Coastguard Worker print(f"Provided path {path} ({path.resolve()}) does not exist. Skipping.") 122*3c875a21SAndroid Build Coastguard Worker else: 123*3c875a21SAndroid Build Coastguard Worker resolved.append(path.resolve()) 124*3c875a21SAndroid Build Coastguard Worker return resolved 125*3c875a21SAndroid Build Coastguard Worker 126*3c875a21SAndroid Build Coastguard Worker 127*3c875a21SAndroid Build Coastguard Workerdef get_metadata_path(proj_path: Path) -> Path: 128*3c875a21SAndroid Build Coastguard Worker """Gets the absolute path of METADATA for a project.""" 129*3c875a21SAndroid Build Coastguard Worker return get_absolute_project_path(proj_path) / METADATA_FILENAME 130*3c875a21SAndroid Build Coastguard Worker 131*3c875a21SAndroid Build Coastguard Worker 132*3c875a21SAndroid Build Coastguard Workerdef get_relative_project_path(proj_path: Path) -> Path: 133*3c875a21SAndroid Build Coastguard Worker """Gets the relative path of a project starting from external/.""" 134*3c875a21SAndroid Build Coastguard Worker return get_absolute_project_path(proj_path).relative_to(external_path()) 135*3c875a21SAndroid Build Coastguard Worker 136*3c875a21SAndroid Build Coastguard Worker 137*3c875a21SAndroid Build Coastguard Workerdef canonicalize_project_path(proj_path: Path) -> Path: 138*3c875a21SAndroid Build Coastguard Worker """Returns the canonical representation of the project path. 139*3c875a21SAndroid Build Coastguard Worker 140*3c875a21SAndroid Build Coastguard Worker For paths that are in the same tree as external_updater (the common 141*3c875a21SAndroid Build Coastguard Worker case), the canonical path is the path of the project relative to //external. 142*3c875a21SAndroid Build Coastguard Worker 143*3c875a21SAndroid Build Coastguard Worker For paths that are in a different tree (an uncommon case used for 144*3c875a21SAndroid Build Coastguard Worker updating projects in other builds such as the NDK), the canonical path is 145*3c875a21SAndroid Build Coastguard Worker the absolute path. 146*3c875a21SAndroid Build Coastguard Worker """ 147*3c875a21SAndroid Build Coastguard Worker try: 148*3c875a21SAndroid Build Coastguard Worker return get_relative_project_path(proj_path) 149*3c875a21SAndroid Build Coastguard Worker except ValueError as ex: 150*3c875a21SAndroid Build Coastguard Worker # A less common use case, but the path might be to a non-local tree, 151*3c875a21SAndroid Build Coastguard Worker # in which case the path will not be relative to our tree. This 152*3c875a21SAndroid Build Coastguard Worker # happens when using external_updater in another project like the NDK 153*3c875a21SAndroid Build Coastguard Worker # or rr. 154*3c875a21SAndroid Build Coastguard Worker if proj_path.is_absolute(): 155*3c875a21SAndroid Build Coastguard Worker return proj_path 156*3c875a21SAndroid Build Coastguard Worker 157*3c875a21SAndroid Build Coastguard Worker # Not relative to //external, and not an absolute path. This case 158*3c875a21SAndroid Build Coastguard Worker # hasn't existed before, so it has no canonical form. 159*3c875a21SAndroid Build Coastguard Worker raise ValueError( 160*3c875a21SAndroid Build Coastguard Worker f"{proj_path} must be either an absolute path or relative to {external_path()}" 161*3c875a21SAndroid Build Coastguard Worker ) from ex 162*3c875a21SAndroid Build Coastguard Worker 163*3c875a21SAndroid Build Coastguard Worker 164*3c875a21SAndroid Build Coastguard Workerdef read_metadata(proj_path: Path) -> metadata_pb2.MetaData: 165*3c875a21SAndroid Build Coastguard Worker """Reads and parses METADATA file for a project. 166*3c875a21SAndroid Build Coastguard Worker 167*3c875a21SAndroid Build Coastguard Worker Args: 168*3c875a21SAndroid Build Coastguard Worker proj_path: Path to the project. 169*3c875a21SAndroid Build Coastguard Worker 170*3c875a21SAndroid Build Coastguard Worker Returns: 171*3c875a21SAndroid Build Coastguard Worker Parsed MetaData proto. 172*3c875a21SAndroid Build Coastguard Worker 173*3c875a21SAndroid Build Coastguard Worker Raises: 174*3c875a21SAndroid Build Coastguard Worker text_format.ParseError: Occurred when the METADATA file is invalid. 175*3c875a21SAndroid Build Coastguard Worker FileNotFoundError: Occurred when METADATA file is not found. 176*3c875a21SAndroid Build Coastguard Worker """ 177*3c875a21SAndroid Build Coastguard Worker 178*3c875a21SAndroid Build Coastguard Worker with get_metadata_path(proj_path).open('r') as metadata_file: 179*3c875a21SAndroid Build Coastguard Worker metadata = metadata_file.read() 180*3c875a21SAndroid Build Coastguard Worker return text_format.Parse(metadata, metadata_pb2.MetaData()) 181*3c875a21SAndroid Build Coastguard Worker 182*3c875a21SAndroid Build Coastguard Worker 183*3c875a21SAndroid Build Coastguard Workerdef convert_url_to_identifier(metadata: metadata_pb2.MetaData) -> metadata_pb2.MetaData: 184*3c875a21SAndroid Build Coastguard Worker """Converts the old style METADATA to the new style""" 185*3c875a21SAndroid Build Coastguard Worker for url in metadata.third_party.url: 186*3c875a21SAndroid Build Coastguard Worker if url.type == metadata_pb2.URL.HOMEPAGE: 187*3c875a21SAndroid Build Coastguard Worker metadata.third_party.homepage = url.value 188*3c875a21SAndroid Build Coastguard Worker else: 189*3c875a21SAndroid Build Coastguard Worker identifier = metadata_pb2.Identifier() 190*3c875a21SAndroid Build Coastguard Worker identifier.type = IdentifierType[metadata_pb2.URL.Type.Name(url.type)].value 191*3c875a21SAndroid Build Coastguard Worker identifier.value = url.value 192*3c875a21SAndroid Build Coastguard Worker identifier.version = metadata.third_party.version 193*3c875a21SAndroid Build Coastguard Worker metadata.third_party.ClearField("version") 194*3c875a21SAndroid Build Coastguard Worker metadata.third_party.identifier.append(identifier) 195*3c875a21SAndroid Build Coastguard Worker metadata.third_party.ClearField("url") 196*3c875a21SAndroid Build Coastguard Worker return metadata 197*3c875a21SAndroid Build Coastguard Worker 198*3c875a21SAndroid Build Coastguard Worker 199*3c875a21SAndroid Build Coastguard Workerdef write_metadata(proj_path: Path, metadata: metadata_pb2.MetaData, keep_date: bool) -> None: 200*3c875a21SAndroid Build Coastguard Worker """Writes updated METADATA file for a project. 201*3c875a21SAndroid Build Coastguard Worker 202*3c875a21SAndroid Build Coastguard Worker This function updates last_upgrade_date in metadata and write to the project 203*3c875a21SAndroid Build Coastguard Worker directory. 204*3c875a21SAndroid Build Coastguard Worker 205*3c875a21SAndroid Build Coastguard Worker Args: 206*3c875a21SAndroid Build Coastguard Worker proj_path: Path to the project. 207*3c875a21SAndroid Build Coastguard Worker metadata: The MetaData proto to write. 208*3c875a21SAndroid Build Coastguard Worker keep_date: Do not change date. 209*3c875a21SAndroid Build Coastguard Worker """ 210*3c875a21SAndroid Build Coastguard Worker 211*3c875a21SAndroid Build Coastguard Worker if not keep_date: 212*3c875a21SAndroid Build Coastguard Worker date = metadata.third_party.last_upgrade_date 213*3c875a21SAndroid Build Coastguard Worker now = datetime.datetime.now() 214*3c875a21SAndroid Build Coastguard Worker date.year = now.year 215*3c875a21SAndroid Build Coastguard Worker date.month = now.month 216*3c875a21SAndroid Build Coastguard Worker date.day = now.day 217*3c875a21SAndroid Build Coastguard Worker try: 218*3c875a21SAndroid Build Coastguard Worker rel_proj_path = str(get_relative_project_path(proj_path)) 219*3c875a21SAndroid Build Coastguard Worker except ValueError: 220*3c875a21SAndroid Build Coastguard Worker # Absolute paths to other trees will not be relative to our tree. 221*3c875a21SAndroid Build Coastguard Worker # There are no portable instructions for upgrading that project, 222*3c875a21SAndroid Build Coastguard Worker # since the path will differ between machines (or checkouts). 223*3c875a21SAndroid Build Coastguard Worker rel_proj_path = "<absolute path to project>" 224*3c875a21SAndroid Build Coastguard Worker usage_hint = textwrap.dedent(f"""\ 225*3c875a21SAndroid Build Coastguard Worker # This project was upgraded with external_updater. 226*3c875a21SAndroid Build Coastguard Worker # Usage: tools/external_updater/updater.sh update external/{rel_proj_path} 227*3c875a21SAndroid Build Coastguard Worker # For more info, check https://cs.android.com/android/platform/superproject/main/+/main:tools/external_updater/README.md 228*3c875a21SAndroid Build Coastguard Worker 229*3c875a21SAndroid Build Coastguard Worker """) 230*3c875a21SAndroid Build Coastguard Worker text_metadata = usage_hint + text_format.MessageToString(metadata) 231*3c875a21SAndroid Build Coastguard Worker with get_metadata_path(proj_path).open('w') as metadata_file: 232*3c875a21SAndroid Build Coastguard Worker if metadata.third_party.license_type == metadata_pb2.LicenseType.BY_EXCEPTION_ONLY: 233*3c875a21SAndroid Build Coastguard Worker metadata_file.write(textwrap.dedent("""\ 234*3c875a21SAndroid Build Coastguard Worker # THIS PACKAGE HAS SPECIAL LICENSING CONDITIONS. PLEASE 235*3c875a21SAndroid Build Coastguard Worker # CONSULT THE OWNERS AND [email protected] BEFORE 236*3c875a21SAndroid Build Coastguard Worker # DEPENDING ON IT IN YOUR PROJECT. 237*3c875a21SAndroid Build Coastguard Worker 238*3c875a21SAndroid Build Coastguard Worker """)) 239*3c875a21SAndroid Build Coastguard Worker metadata_file.write(text_metadata) 240