• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
16import datetime
17from functools import cache
18import os
19from pathlib import Path
20import textwrap
21
22# pylint: disable=import-error
23from google.protobuf import text_format  # type: ignore
24
25# pylint: disable=import-error
26import metadata_pb2  # type: ignore
27
28
29METADATA_FILENAME = 'METADATA'
30
31
32@cache
33def 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
58def get_absolute_project_path(proj_path: Path) -> Path:
59    """Gets absolute path of a project.
60
61    Path resolution starts from external/.
62    """
63    return external_path() / proj_path
64
65
66def get_metadata_path(proj_path: Path) -> Path:
67    """Gets the absolute path of METADATA for a project."""
68    return get_absolute_project_path(proj_path) / METADATA_FILENAME
69
70
71def get_relative_project_path(proj_path: Path) -> Path:
72    """Gets the relative path of a project starting from external/."""
73    return get_absolute_project_path(proj_path).relative_to(external_path())
74
75
76def 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(
97        f"{proj_path} must be either an absolute path or relative to {external_path()}"
98      )
99
100
101def read_metadata(proj_path: Path) -> metadata_pb2.MetaData:
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
115    with get_metadata_path(proj_path).open('r') as metadata_file:
116        metadata = metadata_file.read()
117        return text_format.Parse(metadata, metadata_pb2.MetaData())
118
119
120def write_metadata(proj_path: Path, metadata: metadata_pb2.MetaData, keep_date: bool) -> None:
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.
129      keep_date: Do not change date.
130    """
131
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
138    try:
139        rel_proj_path = str(get_relative_project_path(proj_path))
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>"
145    usage_hint = textwrap.dedent(f"""\
146    # This project was upgraded with external_updater.
147    # Usage: tools/external_updater/updater.sh update {rel_proj_path}
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)
152    with get_metadata_path(proj_path).open('w') as metadata_file:
153        if metadata.third_party.license_type == metadata_pb2.LicenseType.BY_EXCEPTION_ONLY:
154           metadata_file.write(textwrap.dedent("""\
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
159            """))
160        metadata_file.write(text_metadata)
161