xref: /aosp_15_r20/libcore/tools/upstream/pkg-status (revision 89a6322812dc8573315e60046e7959c50dad91d4)
1*89a63228SAndroid Build Coastguard Worker#!/usr/bin/python
2*89a63228SAndroid Build Coastguard Worker#
3*89a63228SAndroid Build Coastguard Worker# Copyright (C) 2021 The Android Open Source Project
4*89a63228SAndroid Build Coastguard Worker#
5*89a63228SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
6*89a63228SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
7*89a63228SAndroid Build Coastguard Worker# You may obtain a copy of the License at
8*89a63228SAndroid Build Coastguard Worker#
9*89a63228SAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
10*89a63228SAndroid Build Coastguard Worker#
11*89a63228SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
12*89a63228SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
13*89a63228SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14*89a63228SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
15*89a63228SAndroid Build Coastguard Worker# limitations under the License.
16*89a63228SAndroid Build Coastguard Worker
17*89a63228SAndroid Build Coastguard Worker"""
18*89a63228SAndroid Build Coastguard WorkerReports on merge status of Java files in a package based on four
19*89a63228SAndroid Build Coastguard Workerrepositories:
20*89a63228SAndroid Build Coastguard Worker
21*89a63228SAndroid Build Coastguard Workerbaseline - upstream baseline used for previous Android release
22*89a63228SAndroid Build Coastguard Workerrelease  - files in previous Android release
23*89a63228SAndroid Build Coastguard Workercurrent  - target for merge
24*89a63228SAndroid Build Coastguard Workerupstream - new upstream being merged
25*89a63228SAndroid Build Coastguard Worker
26*89a63228SAndroid Build Coastguard WorkerExample output:
27*89a63228SAndroid Build Coastguard Worker$ tools/upstream/pkg-status java.security.spec
28*89a63228SAndroid Build Coastguard WorkerAlgorithmParameterSpec.java: Unchanged, Done
29*89a63228SAndroid Build Coastguard WorkerDSAGenParameterSpec.java: Added, TO DO
30*89a63228SAndroid Build Coastguard WorkerDSAParameterSpec.java: Unchanged, Done
31*89a63228SAndroid Build Coastguard WorkerDSAPrivateKeySpec.java: Unchanged, Done
32*89a63228SAndroid Build Coastguard WorkerDSAPublicKeySpec.java: Unchanged, Done
33*89a63228SAndroid Build Coastguard WorkerECField.java: Unchanged, Done
34*89a63228SAndroid Build Coastguard WorkerECFieldF2m.java: Unchanged, Done
35*89a63228SAndroid Build Coastguard WorkerECFieldFp.java: Unchanged, Done
36*89a63228SAndroid Build Coastguard WorkerECGenParameterSpec.java: Updated, TO DO
37*89a63228SAndroid Build Coastguard Worker[...]
38*89a63228SAndroid Build Coastguard Worker"""
39*89a63228SAndroid Build Coastguard Worker
40*89a63228SAndroid Build Coastguard Workerimport argparse
41*89a63228SAndroid Build Coastguard Workerimport hashlib
42*89a63228SAndroid Build Coastguard Workerimport os
43*89a63228SAndroid Build Coastguard Workerimport os.path
44*89a63228SAndroid Build Coastguard Workerimport sys
45*89a63228SAndroid Build Coastguard Workerfrom enum import Enum
46*89a63228SAndroid Build Coastguard Workerfrom pathlib import Path
47*89a63228SAndroid Build Coastguard Worker
48*89a63228SAndroid Build Coastguard WorkerRED = '\u001b[31m'
49*89a63228SAndroid Build Coastguard WorkerGREEN = "\u001b[32m"
50*89a63228SAndroid Build Coastguard WorkerYELLOW = "\u001b[33m"
51*89a63228SAndroid Build Coastguard WorkerRESET = "\u001b[0m"
52*89a63228SAndroid Build Coastguard Worker
53*89a63228SAndroid Build Coastguard Worker
54*89a63228SAndroid Build Coastguard Workerdef colourise(colour, string):
55*89a63228SAndroid Build Coastguard Worker    """Wrap a string with an ANSI colour code"""
56*89a63228SAndroid Build Coastguard Worker    return "%s%s%s" % (colour, string, RESET)
57*89a63228SAndroid Build Coastguard Worker
58*89a63228SAndroid Build Coastguard Worker
59*89a63228SAndroid Build Coastguard Workerdef red(string):
60*89a63228SAndroid Build Coastguard Worker    """Wrap a string with a red ANSI colour code"""
61*89a63228SAndroid Build Coastguard Worker    return colourise(RED, string)
62*89a63228SAndroid Build Coastguard Worker
63*89a63228SAndroid Build Coastguard Worker
64*89a63228SAndroid Build Coastguard Workerdef green(string):
65*89a63228SAndroid Build Coastguard Worker    """Wrap a string with a green ANSI colour code"""
66*89a63228SAndroid Build Coastguard Worker    return colourise(GREEN, string)
67*89a63228SAndroid Build Coastguard Worker
68*89a63228SAndroid Build Coastguard Worker
69*89a63228SAndroid Build Coastguard Workerdef yellow(string):
70*89a63228SAndroid Build Coastguard Worker    """Wrap a string with a yellow ANSI colour code"""
71*89a63228SAndroid Build Coastguard Worker    return colourise(YELLOW, string)
72*89a63228SAndroid Build Coastguard Worker
73*89a63228SAndroid Build Coastguard Worker
74*89a63228SAndroid Build Coastguard Workerclass WorkStatus(Enum):
75*89a63228SAndroid Build Coastguard Worker    """Enum for a file's work completion status"""
76*89a63228SAndroid Build Coastguard Worker    UNKNOWN = ('Unknown', red)
77*89a63228SAndroid Build Coastguard Worker    TODO = ('TO DO', yellow)
78*89a63228SAndroid Build Coastguard Worker    DONE = ('Done', green)
79*89a63228SAndroid Build Coastguard Worker    PROBABLY_DONE = ('Probably done', green)
80*89a63228SAndroid Build Coastguard Worker    ERROR = ('Error', red)
81*89a63228SAndroid Build Coastguard Worker
82*89a63228SAndroid Build Coastguard Worker    def colourise(self, string):
83*89a63228SAndroid Build Coastguard Worker        """Colourise a string using the method for this enum value"""
84*89a63228SAndroid Build Coastguard Worker        return self.colourfunc(string)
85*89a63228SAndroid Build Coastguard Worker
86*89a63228SAndroid Build Coastguard Worker    def __init__(self, description, colourfunc):
87*89a63228SAndroid Build Coastguard Worker        self.description = description
88*89a63228SAndroid Build Coastguard Worker        self.colourfunc = colourfunc
89*89a63228SAndroid Build Coastguard Worker
90*89a63228SAndroid Build Coastguard Worker
91*89a63228SAndroid Build Coastguard Workerclass MergeStatus(Enum):
92*89a63228SAndroid Build Coastguard Worker    """Enum for a file's merge status"""
93*89a63228SAndroid Build Coastguard Worker    UNKNOWN = 'Unknown!'
94*89a63228SAndroid Build Coastguard Worker    MISSING = 'Missing'
95*89a63228SAndroid Build Coastguard Worker    ADDED = 'Added'
96*89a63228SAndroid Build Coastguard Worker    DELETED = 'Deleted or moved'
97*89a63228SAndroid Build Coastguard Worker    UNCHANGED = 'Unchanged'
98*89a63228SAndroid Build Coastguard Worker    UPDATED = 'Updated'
99*89a63228SAndroid Build Coastguard Worker
100*89a63228SAndroid Build Coastguard Worker    def __init__(self, description):
101*89a63228SAndroid Build Coastguard Worker        self.description = description
102*89a63228SAndroid Build Coastguard Worker
103*89a63228SAndroid Build Coastguard Worker
104*89a63228SAndroid Build Coastguard Workerclass MergeConfig:
105*89a63228SAndroid Build Coastguard Worker    """
106*89a63228SAndroid Build Coastguard Worker    Configuration for an upstream merge.
107*89a63228SAndroid Build Coastguard Worker
108*89a63228SAndroid Build Coastguard Worker    Encapsulates the paths to each of the required code repositories.
109*89a63228SAndroid Build Coastguard Worker    """
110*89a63228SAndroid Build Coastguard Worker    def __init__(self, baseline, release, current, upstream) -> None:
111*89a63228SAndroid Build Coastguard Worker        self.baseline = baseline
112*89a63228SAndroid Build Coastguard Worker        self.release = release
113*89a63228SAndroid Build Coastguard Worker        self.current = current
114*89a63228SAndroid Build Coastguard Worker        self.upstream = upstream
115*89a63228SAndroid Build Coastguard Worker        try:
116*89a63228SAndroid Build Coastguard Worker            # Root of checked-out Android sources, set by the "lunch" command.
117*89a63228SAndroid Build Coastguard Worker            self.android_build_top = os.environ['ANDROID_BUILD_TOP']
118*89a63228SAndroid Build Coastguard Worker            # Root of repository snapshots.
119*89a63228SAndroid Build Coastguard Worker            self.ojluni_upstreams = os.environ['OJLUNI_UPSTREAMS']
120*89a63228SAndroid Build Coastguard Worker        except KeyError:
121*89a63228SAndroid Build Coastguard Worker            sys.exit('`lunch` and set OJLUNI_UPSTREAMS first.')
122*89a63228SAndroid Build Coastguard Worker
123*89a63228SAndroid Build Coastguard Worker
124*89a63228SAndroid Build Coastguard Worker    def java_dir(self, repo, pkg):
125*89a63228SAndroid Build Coastguard Worker        relpath = pkg.replace('.', '/')
126*89a63228SAndroid Build Coastguard Worker        if repo == self.current:
127*89a63228SAndroid Build Coastguard Worker            return '%s/libcore/%s/src/main/java/%s' % (
128*89a63228SAndroid Build Coastguard Worker                    self.android_build_top, self.current, relpath)
129*89a63228SAndroid Build Coastguard Worker        else:
130*89a63228SAndroid Build Coastguard Worker            return '%s/%s/%s' % (self.ojluni_upstreams, repo, relpath)
131*89a63228SAndroid Build Coastguard Worker
132*89a63228SAndroid Build Coastguard Worker    def baseline_dir(self, pkg):
133*89a63228SAndroid Build Coastguard Worker        return self.java_dir(self.baseline, pkg)
134*89a63228SAndroid Build Coastguard Worker
135*89a63228SAndroid Build Coastguard Worker    def release_dir(self, pkg):
136*89a63228SAndroid Build Coastguard Worker        return self.java_dir(self.release, pkg)
137*89a63228SAndroid Build Coastguard Worker
138*89a63228SAndroid Build Coastguard Worker    def current_dir(self, pkg):
139*89a63228SAndroid Build Coastguard Worker        return self.java_dir(self.current, pkg)
140*89a63228SAndroid Build Coastguard Worker
141*89a63228SAndroid Build Coastguard Worker    def upstream_dir(self, pkg):
142*89a63228SAndroid Build Coastguard Worker        return self.java_dir(self.upstream, pkg)
143*89a63228SAndroid Build Coastguard Worker
144*89a63228SAndroid Build Coastguard Worker
145*89a63228SAndroid Build Coastguard Workerclass JavaPackage:
146*89a63228SAndroid Build Coastguard Worker    """
147*89a63228SAndroid Build Coastguard Worker    Encapsulates information about a single Java package, notably paths
148*89a63228SAndroid Build Coastguard Worker    to it within each repository.
149*89a63228SAndroid Build Coastguard Worker    """
150*89a63228SAndroid Build Coastguard Worker    def __init__(self, config, name) -> None:
151*89a63228SAndroid Build Coastguard Worker        self.name = name
152*89a63228SAndroid Build Coastguard Worker        self.baseline_dir = config.baseline_dir(name)
153*89a63228SAndroid Build Coastguard Worker        self.release_dir = config.release_dir(name)
154*89a63228SAndroid Build Coastguard Worker        self.current_dir = config.current_dir(name)
155*89a63228SAndroid Build Coastguard Worker        self.upstream_dir = config.upstream_dir(name)
156*89a63228SAndroid Build Coastguard Worker
157*89a63228SAndroid Build Coastguard Worker    @staticmethod
158*89a63228SAndroid Build Coastguard Worker    def list_candidate_files(path):
159*89a63228SAndroid Build Coastguard Worker        """Returns a list of all the Java filenames in a directory."""
160*89a63228SAndroid Build Coastguard Worker        return list(filter(
161*89a63228SAndroid Build Coastguard Worker                lambda f: f.endswith('.java') and f != 'package-info.java',
162*89a63228SAndroid Build Coastguard Worker                os.listdir(path)))
163*89a63228SAndroid Build Coastguard Worker
164*89a63228SAndroid Build Coastguard Worker    def all_files(self):
165*89a63228SAndroid Build Coastguard Worker        """Returns the union of all the Java filenames in all repositories."""
166*89a63228SAndroid Build Coastguard Worker        files = set(self.list_candidate_files(self.baseline_dir))
167*89a63228SAndroid Build Coastguard Worker        files.update(self.list_candidate_files(self.release_dir))
168*89a63228SAndroid Build Coastguard Worker        files.update(self.list_candidate_files(self.upstream_dir))
169*89a63228SAndroid Build Coastguard Worker        files.update(self.list_candidate_files(self.current_dir))
170*89a63228SAndroid Build Coastguard Worker        return sorted(list(files))
171*89a63228SAndroid Build Coastguard Worker
172*89a63228SAndroid Build Coastguard Worker    def java_files(self):
173*89a63228SAndroid Build Coastguard Worker        """Returns a list of JavaFiles corresponding to all filenames."""
174*89a63228SAndroid Build Coastguard Worker        return map(lambda f: JavaFile(self, f), self.all_files())
175*89a63228SAndroid Build Coastguard Worker
176*89a63228SAndroid Build Coastguard Worker    def baseline_path(self, filename):
177*89a63228SAndroid Build Coastguard Worker        return Path(self.baseline_dir + '/' + filename)
178*89a63228SAndroid Build Coastguard Worker
179*89a63228SAndroid Build Coastguard Worker    def release_path(self, filename):
180*89a63228SAndroid Build Coastguard Worker        return Path(self.release_dir + '/' + filename)
181*89a63228SAndroid Build Coastguard Worker
182*89a63228SAndroid Build Coastguard Worker    def current_path(self, filename):
183*89a63228SAndroid Build Coastguard Worker        return Path(self.current_dir + '/' + filename)
184*89a63228SAndroid Build Coastguard Worker
185*89a63228SAndroid Build Coastguard Worker    def upstream_path(self, filename):
186*89a63228SAndroid Build Coastguard Worker        return Path(self.upstream_dir + '/' + filename)
187*89a63228SAndroid Build Coastguard Worker
188*89a63228SAndroid Build Coastguard Worker    def report_merge_status(self):
189*89a63228SAndroid Build Coastguard Worker        """Report on the mergse status of this package."""
190*89a63228SAndroid Build Coastguard Worker        for file in self.java_files():
191*89a63228SAndroid Build Coastguard Worker            merge_status, work_status = file.status()
192*89a63228SAndroid Build Coastguard Worker            text = '%s: %s, %s' % \
193*89a63228SAndroid Build Coastguard Worker                   (
194*89a63228SAndroid Build Coastguard Worker                           file.name, merge_status.description,
195*89a63228SAndroid Build Coastguard Worker                           work_status.description)
196*89a63228SAndroid Build Coastguard Worker            print(work_status.colourise(text))
197*89a63228SAndroid Build Coastguard Worker            if work_status == WorkStatus.ERROR:
198*89a63228SAndroid Build Coastguard Worker                print(file.baseline_sum, file.baseline)
199*89a63228SAndroid Build Coastguard Worker                print(file.release_sum, file.release)
200*89a63228SAndroid Build Coastguard Worker                print(file.current_sum, file.current)
201*89a63228SAndroid Build Coastguard Worker                print(file.upstream_sum, file.upstream)
202*89a63228SAndroid Build Coastguard Worker
203*89a63228SAndroid Build Coastguard Worker
204*89a63228SAndroid Build Coastguard Workerclass JavaFile:
205*89a63228SAndroid Build Coastguard Worker    """
206*89a63228SAndroid Build Coastguard Worker    Encapsulates information about a single Java file in a package across
207*89a63228SAndroid Build Coastguard Worker    all of the repositories involved in a merge.
208*89a63228SAndroid Build Coastguard Worker    """
209*89a63228SAndroid Build Coastguard Worker    def __init__(self, package, name):
210*89a63228SAndroid Build Coastguard Worker        self.package = package
211*89a63228SAndroid Build Coastguard Worker        self.name = name
212*89a63228SAndroid Build Coastguard Worker        # Paths for this file in each repository
213*89a63228SAndroid Build Coastguard Worker        self.baseline = package.baseline_path(name)
214*89a63228SAndroid Build Coastguard Worker        self.release = package.release_path(name)
215*89a63228SAndroid Build Coastguard Worker        self.upstream = package.upstream_path(name)
216*89a63228SAndroid Build Coastguard Worker        self.current = package.current_path(name)
217*89a63228SAndroid Build Coastguard Worker        # Checksums for this file in each repository, or None if absent
218*89a63228SAndroid Build Coastguard Worker        self.baseline_sum = self.checksum(self.baseline)
219*89a63228SAndroid Build Coastguard Worker        self.release_sum = self.checksum(self.release)
220*89a63228SAndroid Build Coastguard Worker        self.upstream_sum = self.checksum(self.upstream)
221*89a63228SAndroid Build Coastguard Worker        self.current_sum = self.checksum(self.current)
222*89a63228SAndroid Build Coastguard Worker        # List of methods for determining file's merge status.
223*89a63228SAndroid Build Coastguard Worker        # Order matters - see merge_status() for details
224*89a63228SAndroid Build Coastguard Worker        self.merge_status_methods = [
225*89a63228SAndroid Build Coastguard Worker                (self.check_for_missing, MergeStatus.MISSING),
226*89a63228SAndroid Build Coastguard Worker                (self.check_for_unchanged, MergeStatus.UNCHANGED),
227*89a63228SAndroid Build Coastguard Worker                (self.check_for_added_upstream, MergeStatus.ADDED),
228*89a63228SAndroid Build Coastguard Worker                (self.check_for_removed_upstream, MergeStatus.DELETED),
229*89a63228SAndroid Build Coastguard Worker                (self.check_for_changed_upstream, MergeStatus.UPDATED),
230*89a63228SAndroid Build Coastguard Worker        ]
231*89a63228SAndroid Build Coastguard Worker        # Map of methods from merge status to determine work status
232*89a63228SAndroid Build Coastguard Worker        self.work_status_methods = {
233*89a63228SAndroid Build Coastguard Worker                MergeStatus.MISSING: self.calculate_missing_work_status,
234*89a63228SAndroid Build Coastguard Worker                MergeStatus.UNCHANGED: self.calculate_unchanged_work_status,
235*89a63228SAndroid Build Coastguard Worker                MergeStatus.ADDED: self.calculate_added_work_status,
236*89a63228SAndroid Build Coastguard Worker                MergeStatus.DELETED: self.calculate_deleted_work_status,
237*89a63228SAndroid Build Coastguard Worker                MergeStatus.UPDATED: self.calculate_updated_work_status,
238*89a63228SAndroid Build Coastguard Worker        }
239*89a63228SAndroid Build Coastguard Worker
240*89a63228SAndroid Build Coastguard Worker    def is_android_changed(self):
241*89a63228SAndroid Build Coastguard Worker        """
242*89a63228SAndroid Build Coastguard Worker        Returns true if the file was changed between the baseline and Android
243*89a63228SAndroid Build Coastguard Worker        release.
244*89a63228SAndroid Build Coastguard Worker        """
245*89a63228SAndroid Build Coastguard Worker        return self.is_in_release() and self.baseline_sum != self.release_sum
246*89a63228SAndroid Build Coastguard Worker
247*89a63228SAndroid Build Coastguard Worker    def is_android_unchanged(self):
248*89a63228SAndroid Build Coastguard Worker        """
249*89a63228SAndroid Build Coastguard Worker        Returns true if the file is in the Android release and is unchanged.
250*89a63228SAndroid Build Coastguard Worker        """
251*89a63228SAndroid Build Coastguard Worker        return self.is_in_release() and self.baseline_sum == self.release_sum
252*89a63228SAndroid Build Coastguard Worker
253*89a63228SAndroid Build Coastguard Worker    def check_for_changed_upstream(self):
254*89a63228SAndroid Build Coastguard Worker        """Returns true if the file is changed upstream since the baseline."""
255*89a63228SAndroid Build Coastguard Worker        return self.baseline_sum != self.upstream_sum
256*89a63228SAndroid Build Coastguard Worker
257*89a63228SAndroid Build Coastguard Worker    def is_in_baseline(self):
258*89a63228SAndroid Build Coastguard Worker        return self.baseline_sum is not None
259*89a63228SAndroid Build Coastguard Worker
260*89a63228SAndroid Build Coastguard Worker    def is_in_release(self):
261*89a63228SAndroid Build Coastguard Worker        """Returns true if the file is present in the baseline and release."""
262*89a63228SAndroid Build Coastguard Worker        return self.is_in_baseline() and self.release_sum is not None
263*89a63228SAndroid Build Coastguard Worker
264*89a63228SAndroid Build Coastguard Worker    def is_in_current(self):
265*89a63228SAndroid Build Coastguard Worker        """Returns true if the file is in current, release and baseline."""
266*89a63228SAndroid Build Coastguard Worker        return self.is_in_release() and self.current_sum is not None
267*89a63228SAndroid Build Coastguard Worker
268*89a63228SAndroid Build Coastguard Worker    def is_in_upstream(self):
269*89a63228SAndroid Build Coastguard Worker        return self.upstream_sum is not None
270*89a63228SAndroid Build Coastguard Worker
271*89a63228SAndroid Build Coastguard Worker    def check_for_missing(self):
272*89a63228SAndroid Build Coastguard Worker        """
273*89a63228SAndroid Build Coastguard Worker        Returns true if the file is expected to be in current, but isn't.
274*89a63228SAndroid Build Coastguard Worker        """
275*89a63228SAndroid Build Coastguard Worker        return self.is_in_release() and self.is_in_upstream() \
276*89a63228SAndroid Build Coastguard Worker               and not self.is_in_current()
277*89a63228SAndroid Build Coastguard Worker
278*89a63228SAndroid Build Coastguard Worker    def removed_in_release(self):
279*89a63228SAndroid Build Coastguard Worker        """Returns true if the file was removed by Android in the release."""
280*89a63228SAndroid Build Coastguard Worker        return self.is_in_baseline() and not self.is_in_release()
281*89a63228SAndroid Build Coastguard Worker
282*89a63228SAndroid Build Coastguard Worker    def check_for_removed_upstream(self):
283*89a63228SAndroid Build Coastguard Worker        """Returns true if the file was removed upstream since the baseline."""
284*89a63228SAndroid Build Coastguard Worker        return self.is_in_baseline() and not self.is_in_upstream()
285*89a63228SAndroid Build Coastguard Worker
286*89a63228SAndroid Build Coastguard Worker    def check_for_added_upstream(self):
287*89a63228SAndroid Build Coastguard Worker        """Returns true if the file was added upstream since the baseline."""
288*89a63228SAndroid Build Coastguard Worker        return self.is_in_upstream() and not self.is_in_baseline()
289*89a63228SAndroid Build Coastguard Worker
290*89a63228SAndroid Build Coastguard Worker    def check_for_unchanged(self):
291*89a63228SAndroid Build Coastguard Worker        """Returns true if the file is unchanged upstream since the baseline."""
292*89a63228SAndroid Build Coastguard Worker        return not self.check_for_changed_upstream()
293*89a63228SAndroid Build Coastguard Worker
294*89a63228SAndroid Build Coastguard Worker    def merge_status(self):
295*89a63228SAndroid Build Coastguard Worker        """
296*89a63228SAndroid Build Coastguard Worker        Returns the merge status for this file, or UNKNOWN.
297*89a63228SAndroid Build Coastguard Worker        Tries each merge_status_method in turn, and if one returns true
298*89a63228SAndroid Build Coastguard Worker        then this method returns the associated merge status.
299*89a63228SAndroid Build Coastguard Worker        """
300*89a63228SAndroid Build Coastguard Worker        for (method, status) in self.merge_status_methods:
301*89a63228SAndroid Build Coastguard Worker            if method():
302*89a63228SAndroid Build Coastguard Worker                return status
303*89a63228SAndroid Build Coastguard Worker        return MergeStatus.UNKNOWN
304*89a63228SAndroid Build Coastguard Worker
305*89a63228SAndroid Build Coastguard Worker    def work_status(self):
306*89a63228SAndroid Build Coastguard Worker        """
307*89a63228SAndroid Build Coastguard Worker        Returns the work status for this file.
308*89a63228SAndroid Build Coastguard Worker        Looks up a status method based on the merge statis and uses that to
309*89a63228SAndroid Build Coastguard Worker        determine the work status.
310*89a63228SAndroid Build Coastguard Worker        """
311*89a63228SAndroid Build Coastguard Worker        status = self.merge_status()
312*89a63228SAndroid Build Coastguard Worker        if status in self.work_status_methods:
313*89a63228SAndroid Build Coastguard Worker            return self.work_status_methods[status]()
314*89a63228SAndroid Build Coastguard Worker        return WorkStatus.ERROR
315*89a63228SAndroid Build Coastguard Worker
316*89a63228SAndroid Build Coastguard Worker    @staticmethod
317*89a63228SAndroid Build Coastguard Worker    def calculate_missing_work_status():
318*89a63228SAndroid Build Coastguard Worker        """Missing files are always an error."""
319*89a63228SAndroid Build Coastguard Worker        return WorkStatus.ERROR
320*89a63228SAndroid Build Coastguard Worker
321*89a63228SAndroid Build Coastguard Worker    def calculate_unchanged_work_status(self):
322*89a63228SAndroid Build Coastguard Worker        """
323*89a63228SAndroid Build Coastguard Worker        File is unchanged upstream, so should be unchanged between release and
324*89a63228SAndroid Build Coastguard Worker        current.
325*89a63228SAndroid Build Coastguard Worker        """
326*89a63228SAndroid Build Coastguard Worker        if self.current_sum == self.release_sum:
327*89a63228SAndroid Build Coastguard Worker            return WorkStatus.DONE
328*89a63228SAndroid Build Coastguard Worker        return WorkStatus.UNKNOWN
329*89a63228SAndroid Build Coastguard Worker
330*89a63228SAndroid Build Coastguard Worker    def calculate_added_work_status(self):
331*89a63228SAndroid Build Coastguard Worker        """File was added upstream so needs to be added to current."""
332*89a63228SAndroid Build Coastguard Worker        if self.current_sum is None:
333*89a63228SAndroid Build Coastguard Worker            return WorkStatus.TODO
334*89a63228SAndroid Build Coastguard Worker        if self.current_sum == self.upstream_sum:
335*89a63228SAndroid Build Coastguard Worker            return WorkStatus.DONE
336*89a63228SAndroid Build Coastguard Worker        #     XXX check for change markers if android changed
337*89a63228SAndroid Build Coastguard Worker        return WorkStatus.UNKNOWN
338*89a63228SAndroid Build Coastguard Worker
339*89a63228SAndroid Build Coastguard Worker    def calculate_deleted_work_status(self):
340*89a63228SAndroid Build Coastguard Worker        """File was removed upstream so needs to be removed from current."""
341*89a63228SAndroid Build Coastguard Worker        if self.is_in_current():
342*89a63228SAndroid Build Coastguard Worker            return WorkStatus.TODO
343*89a63228SAndroid Build Coastguard Worker        return WorkStatus.DONE
344*89a63228SAndroid Build Coastguard Worker
345*89a63228SAndroid Build Coastguard Worker    def calculate_updated_work_status(self):
346*89a63228SAndroid Build Coastguard Worker        """File was updated upstream."""
347*89a63228SAndroid Build Coastguard Worker        if self.current_sum == self.upstream_sum:
348*89a63228SAndroid Build Coastguard Worker            if self.is_android_unchanged():
349*89a63228SAndroid Build Coastguard Worker                return WorkStatus.DONE
350*89a63228SAndroid Build Coastguard Worker            # Looks like Android changes are missing in current
351*89a63228SAndroid Build Coastguard Worker            return WorkStatus.ERROR
352*89a63228SAndroid Build Coastguard Worker        if self.is_android_unchanged():
353*89a63228SAndroid Build Coastguard Worker            return WorkStatus.TODO
354*89a63228SAndroid Build Coastguard Worker        # If we get here there are upstream and Android changes that need
355*89a63228SAndroid Build Coastguard Worker        # to be merged,  If possible use the file copyright date as a
356*89a63228SAndroid Build Coastguard Worker        # heuristic to determine if upstream has been merged into current
357*89a63228SAndroid Build Coastguard Worker        release_copyright = self.get_copyright(self.release)
358*89a63228SAndroid Build Coastguard Worker        current_copyright = self.get_copyright(self.current)
359*89a63228SAndroid Build Coastguard Worker        upstream_copyright = self.get_copyright(self.upstream)
360*89a63228SAndroid Build Coastguard Worker        if release_copyright == upstream_copyright:
361*89a63228SAndroid Build Coastguard Worker            # Upstream copyright same as last release, so can't infer anything
362*89a63228SAndroid Build Coastguard Worker            return WorkStatus.UNKNOWN
363*89a63228SAndroid Build Coastguard Worker        if current_copyright == upstream_copyright:
364*89a63228SAndroid Build Coastguard Worker            return WorkStatus.PROBABLY_DONE
365*89a63228SAndroid Build Coastguard Worker        if current_copyright == release_copyright:
366*89a63228SAndroid Build Coastguard Worker            return WorkStatus.TODO
367*89a63228SAndroid Build Coastguard Worker        # Give up
368*89a63228SAndroid Build Coastguard Worker        return WorkStatus.UNKNOWN
369*89a63228SAndroid Build Coastguard Worker
370*89a63228SAndroid Build Coastguard Worker    def status(self):
371*89a63228SAndroid Build Coastguard Worker        return self.merge_status(), self.work_status()
372*89a63228SAndroid Build Coastguard Worker
373*89a63228SAndroid Build Coastguard Worker    @staticmethod
374*89a63228SAndroid Build Coastguard Worker    def checksum(path):
375*89a63228SAndroid Build Coastguard Worker        """Returns a checksum string for a file, SHA256 as a hex string."""
376*89a63228SAndroid Build Coastguard Worker        try:
377*89a63228SAndroid Build Coastguard Worker            with open(path, 'rb') as file:
378*89a63228SAndroid Build Coastguard Worker                bytes = file.read()
379*89a63228SAndroid Build Coastguard Worker                return hashlib.sha256(bytes).hexdigest()
380*89a63228SAndroid Build Coastguard Worker        except:
381*89a63228SAndroid Build Coastguard Worker            return None
382*89a63228SAndroid Build Coastguard Worker
383*89a63228SAndroid Build Coastguard Worker    @staticmethod
384*89a63228SAndroid Build Coastguard Worker    def get_copyright(file):
385*89a63228SAndroid Build Coastguard Worker        """Returns the upstream copyright line for a file."""
386*89a63228SAndroid Build Coastguard Worker        try:
387*89a63228SAndroid Build Coastguard Worker            with open(file, 'r') as file:
388*89a63228SAndroid Build Coastguard Worker                for count in range(5):
389*89a63228SAndroid Build Coastguard Worker                    line = file.readline()
390*89a63228SAndroid Build Coastguard Worker                    if line.startswith(
391*89a63228SAndroid Build Coastguard Worker                            ' * Copyright') and 'Android' not in line:
392*89a63228SAndroid Build Coastguard Worker                        return line
393*89a63228SAndroid Build Coastguard Worker                return None
394*89a63228SAndroid Build Coastguard Worker        except:
395*89a63228SAndroid Build Coastguard Worker            return None
396*89a63228SAndroid Build Coastguard Worker
397*89a63228SAndroid Build Coastguard Worker
398*89a63228SAndroid Build Coastguard Workerdef main():
399*89a63228SAndroid Build Coastguard Worker    parser = argparse.ArgumentParser(
400*89a63228SAndroid Build Coastguard Worker            description='Report on merge status of Java packages',
401*89a63228SAndroid Build Coastguard Worker            formatter_class=argparse.ArgumentDefaultsHelpFormatter)
402*89a63228SAndroid Build Coastguard Worker
403*89a63228SAndroid Build Coastguard Worker    # TODO(prb): Add help for available repositories
404*89a63228SAndroid Build Coastguard Worker    parser.add_argument('-b', '--baseline', default='expected',
405*89a63228SAndroid Build Coastguard Worker                        help='Baseline repo')
406*89a63228SAndroid Build Coastguard Worker    parser.add_argument('-r', '--release', default='sc-release',
407*89a63228SAndroid Build Coastguard Worker                        help='Last released repo')
408*89a63228SAndroid Build Coastguard Worker    parser.add_argument('-u', '--upstream', default='11+28',
409*89a63228SAndroid Build Coastguard Worker                        help='Upstream repo.')
410*89a63228SAndroid Build Coastguard Worker    parser.add_argument('-c', '--current', default='ojluni',
411*89a63228SAndroid Build Coastguard Worker                        help='Current repo.')
412*89a63228SAndroid Build Coastguard Worker    parser.add_argument('pkgs', nargs="+",
413*89a63228SAndroid Build Coastguard Worker                        help='Packages to report on')
414*89a63228SAndroid Build Coastguard Worker
415*89a63228SAndroid Build Coastguard Worker    args = parser.parse_args()
416*89a63228SAndroid Build Coastguard Worker    config = MergeConfig(args.baseline, args.release, args.current,
417*89a63228SAndroid Build Coastguard Worker                         args.upstream)
418*89a63228SAndroid Build Coastguard Worker
419*89a63228SAndroid Build Coastguard Worker    for pkg_name in args.pkgs:
420*89a63228SAndroid Build Coastguard Worker        try:
421*89a63228SAndroid Build Coastguard Worker            package = JavaPackage(config, pkg_name)
422*89a63228SAndroid Build Coastguard Worker            package.report_merge_status()
423*89a63228SAndroid Build Coastguard Worker        except Exception as e:
424*89a63228SAndroid Build Coastguard Worker            print(red("ERROR: Unable to process package " + pkg_name + e))
425*89a63228SAndroid Build Coastguard Worker
426*89a63228SAndroid Build Coastguard Worker
427*89a63228SAndroid Build Coastguard Workerif __name__ == "__main__":
428*89a63228SAndroid Build Coastguard Worker    main()
429