xref: /aosp_15_r20/tools/external_updater/fileutils.py (revision 3c875a214f382db1236d28570d1304ce57138f32)
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