blob: dcdf3ae8de81acdfa1c68100fc6c756678582b85 [file] [log] [blame]
Owen Gray877bbd32024-07-19 10:01:20 -04001#
2# Copyright (C) 2019 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16"""A helper script for validateRefactor.sh. Should generally not be used directly.
17
18Can be used directly if validateRefactor.sh has already created the out-old & out-new dirs.
19In such a case, it can be run to compare those directories without regenerating them.
20This is generally only useful when updating baselines or iterating on this script itself.
21Takes baseline names as CLI arguments, which may be passed through from validateRefactor.sh.
22
23Typical usage example:
24
25 python validateRefactorHelper.py agpKmp
26"""
27import itertools
28import os
29import shutil
30import subprocess
31import sys
32
33# noto-emoji-compat `bundleinside`s an externally-built with-timestamps jar.
34# classes.jar is compared using `diffuse` instead of unzipping and diffing class files.
35bannedJars = ["-x", "noto-emoji-compat-java.jar", "-x", "classes.jar"]
36# java and json aren"t for unzipping, but the poor exclude-everything-but-jars regex doesn't
37# exclude them. Same for exclude-non-klib and .kt/.knm
38areNotZips = ["-x", r"**\.java", "-x", r"**\.json", "-x", r"**\.kt", "-x", r"**\.knm"]
39# keeps making my regexes fall over :(
40hasNoExtension = ["-x", "manifest", "-x", "module"]
41doNotUnzip = bannedJars + areNotZips + hasNoExtension
42
43def diff(excludes):
44 return popenAndReturn(["diff", "-r", "../../out-old/dist/", "../../out-new/dist/"] + excludes)
45
46def popenAndReturn(args):
47 return subprocess.Popen(args, stdout=subprocess.PIPE).stdout.read().decode("utf-8").split("\n")
48
49# Finds and unzips all files with old/new diff that _do not_ match the argument regex.
50def findFilesMatchingWithDiffAndUnzip(regexThatMatchesEverythingElse):
51 # Exclude all things that are *not* the desired zip type
52 # (because diff doesn"t have an --include, only --exclude).
53 zipsWithDiffs = diff(["-q", "-x", regexThatMatchesEverythingElse] + doNotUnzip)
54 # Take only changed files, not new/deleted ones (the diff there is obvious)
55 zipsWithDiffs = filter(lambda s: s.startswith("Files"), zipsWithDiffs)
56 zipsWithDiffs = map(lambda s: s.split()[1:4:2], zipsWithDiffs)
57 zipsWithDiffs = list(itertools.chain.from_iterable(zipsWithDiffs)) # flatten
58 # And unzip them
59 for filename in zipsWithDiffs:
60 print("unzipping " + filename)
61 # if os.path.exists(filename+".unzipped/"): os.rmdir(filename+".unzipped/")
62 shutil.rmtree(filename+".unzipped/")
63 subprocess.Popen(["unzip", "-qq", "-o", filename, "-d", filename+".unzipped/"])
64
65diffusePath = "../../prebuilts/build-tools/diffuse-0.3.0/bin/diffuse"
66
67def compareWithDiffuse(listOfJars):
68 for jarPath in list(filter(None, listOfJars)):
69 print("jarpath: " + jarPath)
70 newJarPath = jarPath.replace("out-old", "out-new")
71 print(popenAndReturn([diffusePath, "diff", "--jar", jarPath, newJarPath]))
72
73# We might care to know whether .sha1 or .md5 files have changed, but changes in those files will
74# always be accompanied by more meaningful changes in other files, so we don"t need to show changes
75# in .sha1 or .md5 files, or in .module files showing the hashes of other files, or config names.
76excludedHashes = ["-x", "*.md5*", "-x", "*.sha**", "-I", " \"md5\".*", \
77 "-I", " \"sha.*", "-I", " \"size\".*", "-I", " \"name\".*"]
78# Don"t care about maven-metadata files because they have timestamps in them.
79excludedFiles = ["-x", "*maven-metadata.xml**", "-x", r"**\.knm"] # temporarily ignore knms
80# Also, ignore files that we already unzipped
81excludedZips = ["-x", "*.zip", "-x", "*.jar", "-x", "*.aar", "-x", "*.apk", "-x", "*.klib"]
82
83# These are baselined changes that we understand and know are no-ops in refactors
84# "Unskippable" changes are multi-line and can't be skipped in `diff`, so post-process
85baselinedChangesForAgpKmp = [
86 # these are new attributes being added
87 """ "org.gradle.libraryelements": "aar",""",
88 """ "org.gradle.jvm.environment": "android",""",
89 """ "org.gradle.jvm.environment": "non-jvm",""",
90 """ "org.gradle.jvm.environment": "standard-jvm",""",
91 # this attribute swap occurs alongside the above new attributes added.
92 # https://chat.google.com/room/AAAAW8qmCIs/4phaNn_gsrc
93 """ "org.jetbrains.kotlin.platform.type": "androidJvm\"""",
94 """ "org.jetbrains.kotlin.platform.type": "jvm\"""",
95 # name-only change; nothing resolves based on names
96 """ "name": "releaseApiElements-published",""",
97 """ "name": "androidApiElements-published",""",
98 """ <pre>actual typealias""", # open bug in dackka b/339221337
99 # we are switching from our KMP sourcejars solution to the upstream one
100 """ "org.gradle.docstype": "fake-sources",""",
101 """ "org.gradle.docstype": "sources",""",
102]
103unskippableBaselinedChangesForAgpKmp = [
104 """
105< },
106< "excludes": [
107< {
108< "group": "org.jetbrains.kotlin",
109< "module": "kotlin-stdlib-common"
110< },
111< {
112< "group": "org.jetbrains.kotlin",
113< "module": "kotlin-test-common"
114< },
115< {
116< "group": "org.jetbrains.kotlin",
117< "module": "kotlin-test-annotations-common"
118< }
119< ]
120---
121> }
122""",
123"""
124< <exclusions>
125< <exclusion>
126< <groupId>org.jetbrains.kotlin</groupId>
127< <artifactId>kotlin-stdlib-common</artifactId>
128< </exclusion>
129< <exclusion>
130< <groupId>org.jetbrains.kotlin</groupId>
131< <artifactId>kotlin-test-common</artifactId>
132< </exclusion>
133< <exclusion>
134< <groupId>org.jetbrains.kotlin</groupId>
135< <artifactId>kotlin-test-annotations-common</artifactId>
136< </exclusion>
137< </exclusions>
138"""
139]
140
141baselinedChanges = []
142unskippableBaselinedChanges = []
143arguments = sys.argv[1:]
144if "agpKmp" in arguments:
145 arguments.remove("agpKmp")
146 print("IGNORING DIFF FOR agpKmp")
147 baselinedChanges += baselinedChangesForAgpKmp
148 unskippableBaselinedChanges += unskippableBaselinedChangesForAgpKmp
149if arguments:
150 print("invalid argument(s) for validateRefactorHelper: " + ", ".join(arguments))
151 print("currently recognized arguments: agpKmp")
152 exit()
153
154# interleave "-I" to tell diffutils to 'I'gnore the baselined lines
155baselinedChanges = list(itertools.chain.from_iterable(zip(["-I"]*99, baselinedChanges)))
156
157# post-process the diff output to remove multi-line changes that can't be excluded in `diff` itself
158def filterOutUnskippableBaselinedChanges(inputString):
159 result = inputString
160 for toRemove in unskippableBaselinedChanges:
161 i = result.find(toRemove)
162 while (i != -1):
163 j = result.rfind("\n", 0, i-2) # also find and remove previous line e.g. 82,96c70
164 result = result[:j+1] + result[i+len(toRemove):]
165 i = result.find(toRemove)
166 #remove all "diff -r ..." header lines that no longer have content due to baselining
167 result = result.split("\n")
168 nRemoved = 0
169 for i in range(len(result)): # check for consecutive `diff -r` lines: the first has no content
170 if not result[i-nRemoved].startswith("diff -r "): continue
171 if not result[i+1-nRemoved].startswith("diff -r "): continue
172 del result[i]
173 nRemoved+=1
174 if not result[-1]: del result[-1] # remove possible ending blank line
175 if result[-1].startswith("diff -r "): del result[-1] # terminal `diff -r` line: has no content
176 return "\n".join(result)
177
178# print(baselinedChanges)
179
180# Find all zip files with a diff, e.g. the tip-of-tree-repository file, and maybe the docs zip
181# findFilesMatchingWithDiffAndUnzip(r"**\.[^z][a-z]*")
182# Find all aar and apk files with a diff. The proper regex would be `.*\..*[^akpr]+.*`, but it
183# doesn"t work in difftools exclude's very limited regex syntax.
184findFilesMatchingWithDiffAndUnzip(r"**\.[^a][a-z]*")
185# Find all jars and klibs and unzip them (comes after because they could be inside aars/apks).
186findFilesMatchingWithDiffAndUnzip(r"**\.[^j][a-z]*")
187findFilesMatchingWithDiffAndUnzip(r"**\.[^k][a-z]*")
188# now find all diffs in classes.jars
189classesJarsWithDiffs = popenAndReturn(["find", "../../out-old/dist/", "-name", "classes.jar"])
190print("classes.jar s: " + str(classesJarsWithDiffs))
191compareWithDiffuse(classesJarsWithDiffs)
192# Now find all diffs in non-zipped files
193finalExcludes = excludedHashes + excludedFiles + excludedZips + baselinedChanges
194finalDiff = "\n".join(diff(finalExcludes))
195finalDiff = filterOutUnskippableBaselinedChanges(finalDiff)
196print(finalDiff)
197