Owen Gray | 877bbd3 | 2024-07-19 10:01:20 -0400 | [diff] [blame] | 1 | # |
| 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 | |
| 18 | Can be used directly if validateRefactor.sh has already created the out-old & out-new dirs. |
| 19 | In such a case, it can be run to compare those directories without regenerating them. |
| 20 | This is generally only useful when updating baselines or iterating on this script itself. |
| 21 | Takes baseline names as CLI arguments, which may be passed through from validateRefactor.sh. |
| 22 | |
| 23 | Typical usage example: |
| 24 | |
| 25 | python validateRefactorHelper.py agpKmp |
| 26 | """ |
| 27 | import itertools |
| 28 | import os |
| 29 | import shutil |
| 30 | import subprocess |
| 31 | import 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. |
| 35 | bannedJars = ["-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 |
| 38 | areNotZips = ["-x", r"**\.java", "-x", r"**\.json", "-x", r"**\.kt", "-x", r"**\.knm"] |
| 39 | # keeps making my regexes fall over :( |
| 40 | hasNoExtension = ["-x", "manifest", "-x", "module"] |
| 41 | doNotUnzip = bannedJars + areNotZips + hasNoExtension |
| 42 | |
| 43 | def diff(excludes): |
| 44 | return popenAndReturn(["diff", "-r", "../../out-old/dist/", "../../out-new/dist/"] + excludes) |
| 45 | |
| 46 | def 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. |
| 50 | def 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 | |
| 65 | diffusePath = "../../prebuilts/build-tools/diffuse-0.3.0/bin/diffuse" |
| 66 | |
| 67 | def 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. |
| 76 | excludedHashes = ["-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. |
| 79 | excludedFiles = ["-x", "*maven-metadata.xml**", "-x", r"**\.knm"] # temporarily ignore knms |
| 80 | # Also, ignore files that we already unzipped |
| 81 | excludedZips = ["-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 |
| 85 | baselinedChangesForAgpKmp = [ |
| 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 | ] |
| 103 | unskippableBaselinedChangesForAgpKmp = [ |
| 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 | |
| 141 | baselinedChanges = [] |
| 142 | unskippableBaselinedChanges = [] |
| 143 | arguments = sys.argv[1:] |
| 144 | if "agpKmp" in arguments: |
| 145 | arguments.remove("agpKmp") |
| 146 | print("IGNORING DIFF FOR agpKmp") |
| 147 | baselinedChanges += baselinedChangesForAgpKmp |
| 148 | unskippableBaselinedChanges += unskippableBaselinedChangesForAgpKmp |
| 149 | if 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 |
| 155 | baselinedChanges = 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 |
| 158 | def 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. |
| 184 | findFilesMatchingWithDiffAndUnzip(r"**\.[^a][a-z]*") |
| 185 | # Find all jars and klibs and unzip them (comes after because they could be inside aars/apks). |
| 186 | findFilesMatchingWithDiffAndUnzip(r"**\.[^j][a-z]*") |
| 187 | findFilesMatchingWithDiffAndUnzip(r"**\.[^k][a-z]*") |
| 188 | # now find all diffs in classes.jars |
| 189 | classesJarsWithDiffs = popenAndReturn(["find", "../../out-old/dist/", "-name", "classes.jar"]) |
| 190 | print("classes.jar s: " + str(classesJarsWithDiffs)) |
| 191 | compareWithDiffuse(classesJarsWithDiffs) |
| 192 | # Now find all diffs in non-zipped files |
| 193 | finalExcludes = excludedHashes + excludedFiles + excludedZips + baselinedChanges |
| 194 | finalDiff = "\n".join(diff(finalExcludes)) |
| 195 | finalDiff = filterOutUnskippableBaselinedChanges(finalDiff) |
| 196 | print(finalDiff) |
| 197 | |