blob: 2921fd0701d9b6e1dcfa520ce59c3a5ea40f567e [file] [log] [blame]
#
# Copyright (C) 2019 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""A helper script for validateRefactor.sh. Should generally not be used directly.
Can be used directly if validateRefactor.sh has already created the out-old & out-new dirs.
In such a case, it can be run to compare those directories without regenerating them.
This is generally only useful when updating baselines or iterating on this script itself.
Takes baseline names as CLI arguments, which may be passed through from validateRefactor.sh.
Typical usage example:
python validateRefactorHelper.py agpKmp
"""
import itertools
import os
import shutil
import subprocess
import sys
# noto-emoji-compat `bundleinside`s an externally-built with-timestamps jar.
# classes.jar is compared using `diffuse` instead of unzipping and diffing class files.
bannedJars = ["-x", "noto-emoji-compat-java.jar", "-x", "classes.jar"]
# java and json aren"t for unzipping, but the poor exclude-everything-but-jars regex doesn't
# exclude them. Same for exclude-non-klib and .kt/.knm
areNotZips = ["-x", r"**\.java", "-x", r"**\.json", "-x", r"**\.kt", "-x", r"**\.knm"]
# keeps making my regexes fall over :(
hasNoExtension = ["-x", "manifest", "-x", "module"]
doNotUnzip = bannedJars + areNotZips + hasNoExtension
def diff(excludes):
return popenAndReturn(["diff", "-r", "../../out-old/dist/", "../../out-new/dist/"] + excludes)
def popenAndReturn(args):
return subprocess.Popen(args, stdout=subprocess.PIPE).stdout.read().decode("utf-8").split("\n")
# Finds and unzips all files with old/new diff that _do not_ match the argument regex.
def findFilesMatchingWithDiffAndUnzip(regexThatMatchesEverythingElse):
# Exclude all things that are *not* the desired zip type
# (because diff doesn"t have an --include, only --exclude).
zipsWithDiffs = diff(["-q", "-x", regexThatMatchesEverythingElse] + doNotUnzip)
# Take only changed files, not new/deleted ones (the diff there is obvious)
zipsWithDiffs = filter(lambda s: s.startswith("Files"), zipsWithDiffs)
zipsWithDiffs = map(lambda s: s.split()[1:4:2], zipsWithDiffs)
zipsWithDiffs = list(itertools.chain.from_iterable(zipsWithDiffs)) # flatten
# And unzip them
for filename in zipsWithDiffs:
print("unzipping " + filename)
# if os.path.exists(filename+".unzipped/"): os.rmdir(filename+".unzipped/")
shutil.rmtree(filename+".unzipped/")
subprocess.Popen(["unzip", "-qq", "-o", filename, "-d", filename+".unzipped/"])
diffusePath = "../../prebuilts/build-tools/diffuse-0.3.0/bin/diffuse"
def compareWithDiffuse(listOfJars):
for jarPath in list(filter(None, listOfJars)):
print("jarpath: " + jarPath)
newJarPath = jarPath.replace("out-old", "out-new")
print(popenAndReturn([diffusePath, "diff", "--jar", jarPath, newJarPath]))
# We might care to know whether .sha1 or .md5 files have changed, but changes in those files will
# always be accompanied by more meaningful changes in other files, so we don"t need to show changes
# in .sha1 or .md5 files, or in .module files showing the hashes of other files, or config names.
excludedHashes = ["-x", "*.md5*", "-x", "*.sha**", "-I", " \"md5\".*", \
"-I", " \"sha.*", "-I", " \"size\".*", "-I", " \"name\".*"]
# Don"t care about maven-metadata files because they have timestamps in them.
excludedFiles = ["-x", "*maven-metadata.xml**", "-x", r"**\.knm"] # temporarily ignore knms
# Also, ignore files that we already unzipped
excludedZips = ["-x", "*.zip", "-x", "*.jar", "-x", "*.aar", "-x", "*.apk", "-x", "*.klib"]
# These are baselined changes that we understand and know are no-ops in refactors
# "Unskippable" changes are multi-line and can't be skipped in `diff`, so post-process
baselinedChangesForAgpKmp = [
# these are new attributes being added
""" "org.gradle.libraryelements": "aar",""",
""" "org.gradle.jvm.environment": "android",""",
""" "org.gradle.jvm.environment": "non-jvm",""",
""" "org.gradle.jvm.environment": "standard-jvm",""",
# this attribute swap occurs alongside the above new attributes added.
# https://chat.google.com/room/AAAAW8qmCIs/4phaNn_gsrc
""" "org.jetbrains.kotlin.platform.type": "androidJvm\"""",
""" "org.jetbrains.kotlin.platform.type": "jvm\"""",
# name-only change; nothing resolves based on names
""" "name": "releaseApiElements-published",""",
""" "name": "androidApiElements-published",""",
""" <pre>actual typealias""", # open bug in dackka b/339221337
# we are switching from our KMP sourcejars solution to the upstream one
""" "org.gradle.docstype": "fake-sources",""",
""" "org.gradle.docstype": "sources",""",
]
unskippableBaselinedChangesForAgpKmp = [
"""
< },
< "excludes": [
< {
< "group": "org.jetbrains.kotlin",
< "module": "kotlin-stdlib-common"
< },
< {
< "group": "org.jetbrains.kotlin",
< "module": "kotlin-test-common"
< },
< {
< "group": "org.jetbrains.kotlin",
< "module": "kotlin-test-annotations-common"
< }
< ]
---
> }
""",
"""
< <exclusions>
< <exclusion>
< <groupId>org.jetbrains.kotlin</groupId>
< <artifactId>kotlin-stdlib-common</artifactId>
< </exclusion>
< <exclusion>
< <groupId>org.jetbrains.kotlin</groupId>
< <artifactId>kotlin-test-common</artifactId>
< </exclusion>
< <exclusion>
< <groupId>org.jetbrains.kotlin</groupId>
< <artifactId>kotlin-test-annotations-common</artifactId>
< </exclusion>
< </exclusions>
"""
]
baselinedChanges = []
unskippableBaselinedChanges = []
arguments = sys.argv[1:]
if "agpKmp" in arguments:
arguments.remove("agpKmp")
print("IGNORING DIFF FOR agpKmp")
baselinedChanges += baselinedChangesForAgpKmp
unskippableBaselinedChanges += unskippableBaselinedChangesForAgpKmp
if arguments:
print("invalid argument(s) for validateRefactorHelper: " + ", ".join(arguments))
print("currently recognized arguments: agpKmp")
exit()
# interleave "-I" to tell diffutils to 'I'gnore the baselined lines
baselinedChanges = list(itertools.chain.from_iterable(zip(["-I"]*99, baselinedChanges)))
# post-process the diff output to remove multi-line changes that can't be excluded in `diff` itself
def filterOutUnskippableBaselinedChanges(inputString):
result = inputString
for toRemove in unskippableBaselinedChanges:
i = result.find(toRemove)
while (i != -1):
j = result.rfind("\n", 0, i-2) # also find and remove previous line e.g. 82,96c70
result = result[:j+1] + result[i+len(toRemove):]
i = result.find(toRemove)
#remove all "diff -r ..." header lines that no longer have content due to baselining
result = result.split("\n")
nRemoved = 0
for i in range(len(result)): # check for consecutive `diff -r` lines: the first has no content
if not result[i-nRemoved].startswith("diff -r "): continue
if not result[i+1-nRemoved].startswith("diff -r "): continue
del result[i-nRemoved]
nRemoved+=1
if not result[-1]: del result[-1] # remove possible ending blank line
if result[-1].startswith("diff -r "): del result[-1] # terminal `diff -r` line: has no content
return "\n".join(result)
# print(baselinedChanges)
# Find all zip files with a diff, e.g. the tip-of-tree-repository file, and maybe the docs zip
# findFilesMatchingWithDiffAndUnzip(r"**\.[^z][a-z]*")
# Find all aar and apk files with a diff. The proper regex would be `.*\..*[^akpr]+.*`, but it
# doesn"t work in difftools exclude's very limited regex syntax.
findFilesMatchingWithDiffAndUnzip(r"**\.[^a][a-z]*")
# Find all jars and klibs and unzip them (comes after because they could be inside aars/apks).
findFilesMatchingWithDiffAndUnzip(r"**\.[^j][a-z]*")
findFilesMatchingWithDiffAndUnzip(r"**\.[^k][a-z]*")
# now find all diffs in classes.jars
classesJarsWithDiffs = popenAndReturn(["find", "../../out-old/dist/", "-name", "classes.jar"])
print("classes.jar s: " + str(classesJarsWithDiffs))
compareWithDiffuse(classesJarsWithDiffs)
# Now find all diffs in non-zipped files
finalExcludes = excludedHashes + excludedFiles + excludedZips + baselinedChanges
finalDiff = "\n".join(diff(finalExcludes))
finalDiff = filterOutUnskippableBaselinedChanges(finalDiff)
print(finalDiff)