Skip to content

Instantly share code, notes, and snippets.

@thilong
Created March 31, 2020 03:48
Show Gist options
  • Save thilong/60dace8b17e21afed28ab521721ad15d to your computer and use it in GitHub Desktop.
Save thilong/60dace8b17e21afed28ab521721ad15d to your computer and use it in GitHub Desktop.
把aar文件解包并合并的python脚本
#!/usr/local/bin/python3
# -*- coding: utf-8 -*-
import sys, os, shutil, zipfile
import xml.etree.ElementTree as xmlDoc
from enum import Enum
ignoreFiles = ['R.txt', 'proguard.txt']
def log(LEVEL, msg):
print('['+LEVEL+']\t'+msg)
def logFileContent(path):
content = ''
with open(path, 'r') as fp:
content = fp.read()
print('[文件]\t'+path + ' : ')
print(content)
class HMSFileType(Enum):
Unknown = 0
Jar = 1
AndroidManifest = 2
R = 3
Value = 4
Regular = 5
class HMSFile:
fileType = HMSFileType.Unknown #文件类型
name = '' #文件名
path = '' #相对于工作路径的路径
copyTo = '' #拷贝到的目标路径
def __init__(self):
self.fileType = HMSFileType.Unknown
self.name = ''
self.path = ''
self.copyTo = ''
def shouldIgnore(self) -> bool:
return self.name in ignoreFiles
def isCombineable(self) -> bool:
return self.fileType == HMSFileType.AndroidManifest or self.fileType == HMSFileType.Value
def process(self,rootPath) -> bool:
fullTo = os.path.join(rootPath, self.copyTo)
if self.shouldIgnore():
log('忽略', self.path)
return True
if self.fileType == HMSFileType.AndroidManifest and os.path.exists(fullTo):
return self.process_AndroidManifest(rootPath)
if self.fileType == HMSFileType.Value and os.path.exists(fullTo):
return self.process_Values(rootPath)
fullFrom = os.path.join(rootPath, self.path)
fullToFolder = os.path.dirname(fullTo)
if not os.path.exists(fullToFolder):
os.makedirs(fullToFolder)
shutil.copy(fullFrom, fullTo)
return os.path.exists(fullTo)
def process_AndroidManifest(self,rootPath) -> bool:
log('合并', self.path)
fromPath = os.path.join(rootPath, self.path)
toPath = os.path.join(rootPath, self.copyTo)
fromTree = xmlDoc.parse(fromPath)
toTree = xmlDoc.parse(toPath)
toRoot = toTree.getroot()
logFileContent(fromPath)
for node in fromTree.getroot():
if node.tag == 'uses-sdk':
continue
elif node.tag == 'application':
toAppNode = toRoot.find('application')
for appNode in node:
toAppNode.append(appNode)
else:
toRoot.append(node)
xmlStr = str(xmlDoc.tostring(toRoot, encoding='utf-8'), encoding='utf-8')
xmlStr = xmlStr.replace('xmlns:ns0', 'xmlns:android')
xmlStr = xmlStr.replace(' ns0:', ' android:')
with open(toPath, 'w+') as outputXmlFp:
outputXmlFp.write(xmlStr)
return True
def process_Values(self,rootPath) -> bool:
log('合并', self.path)
fromPath = os.path.join(rootPath, self.path)
toPath = os.path.join(rootPath, self.copyTo)
fromTree = xmlDoc.parse(fromPath)
toTree = xmlDoc.parse(toPath)
toRoot = toTree.getroot()
for node in fromTree.getroot():
toRoot.append(node)
xmlStr = str(xmlDoc.tostring(toRoot, encoding='utf-8'), encoding='utf-8')
xmlStr = xmlStr.replace('xmlns:ns0', 'xmlns:android')
xmlStr = xmlStr.replace(' ns0:', ' android:')
with open(toPath, 'w+') as outputXmlFp:
outputXmlFp.write(xmlStr)
return True
class HMSLibraryType(Enum):
UNKNOWN = 0
JAR = 1
AAR = 2
class HMSLibrary:
fileType = None
parentFolder = '' #所在文件夹
name = '' #文件名
nameWithoutExt = '' #无后缀的文件名
extractFolder = '' #解压后的文件夹名
files = [] #解压后需要处理的文件
def __init__(self, path):
self.parentFolder = os.path.dirname(path)
self.name = os.path.basename(path)
self.files = []
self.nameWithoutExt = ''
self.extractFolder = ''
if self.name.lower().endswith('.aar'):
self.fileType = HMSLibraryType.AAR
self.nameWithoutExt = self.name[:-4]
elif self.name.lower().endswith('jar'):
self.fileType = HMSLibraryType.JAR
self.nameWithoutExt = self.name[:-4]
else:
self.fileType = HMSLibraryType.UNKNOWN
def prepareForCopy(self) -> bool:
if self.fileType == HMSLibraryType.JAR:
child = HMSFile()
child.fileType = HMSFileType.Jar
child.name = self.name
child.copyTo = os.path.join('output/libs', self.name)
child.path = self.name
self.files.append(child)
return True
try:
extractPath = os.path.join(self.parentFolder, self.nameWithoutExt)
zf = zipfile.ZipFile(os.path.join(self.parentFolder, self.name))
zf.extractall(path = extractPath)
self.extractFolder = self.nameWithoutExt
except:
return False
self.prepareFiles('')
return True
def prepareFiles(self, inFolder):
folderPath = os.path.join(self.parentFolder, self.extractFolder, inFolder)
for subFileName in os.listdir(folderPath):
subFilePath = os.path.join(folderPath, subFileName)
if os.path.isdir(subFilePath):
self.prepareFiles(os.path.join(inFolder, subFileName))
elif os.path.isfile(subFilePath):
relativePath = os.path.join(inFolder, subFileName)
hfile = HMSFile()
hfile.name = subFileName
hfile.path = os.path.join(self.extractFolder,inFolder, subFileName)
hfile.copyTo = os.path.join('output/resources/', relativePath)
if relativePath == 'classes.jar':
hfile.copyTo = os.path.join('output/libs/', self.extractFolder + '.jar')
hfile.fileType = HMSFileType.Jar
elif relativePath == 'AndroidManifest.xml':
hfile.copyTo = os.path.join('output', 'AndroidManifest.xml')
hfile.fileType = HMSFileType.AndroidManifest
elif '/values' in relativePath:
hfile.fileType = HMSFileType.Value
else:
hfile.fileType = HMSFileType.Regular
self.files.append(hfile)
def cleanup(self):
if len(self.extractFolder) > 0:
shutil.rmtree(os.path.join(self.parentFolder, self.extractFolder), ignore_errors=True)
class HMSRepackApp:
workingRoot = ''
abortOnError = True
shouldCleanup = True
libraries = []
completedFiles = []
def __init__(self, pathToWork, abortOnError = True, shouldCleanup = True):
self.workingRoot = pathToWork
self.abortOnError = abortOnError
self.shouldCleanup = shouldCleanup
outputPath = os.path.join(self.workingRoot, 'output')
if os.path.exists(outputPath):
shutil.rmtree(outputPath, ignore_errors=True)
def prepareFiles(self) -> bool:
for fileName in os.listdir(self.workingRoot):
filePath = os.path.join(self.workingRoot, fileName)
if not os.path.isfile(filePath):
continue
lib = HMSLibrary(filePath)
if lib.fileType != HMSLibraryType.UNKNOWN:
self.libraries.append(lib)
if lib.prepareForCopy() == False and self.abortOnError:
self.cleanup()
return False
return True
def checkHasConflict(self, lfile) -> bool:
if lfile.isCombineable():
return False
for cFile in self.completedFiles:
if cFile.copyTo == lfile.copyTo and not lfile.shouldIgnore():
log('警告','文件拷贝存在冲突: ' + cFile.path + ' 与 ' + lfile.path)
return True
return False
def processFiles(self) -> bool:
for lib in self.libraries:
for lfile in lib.files:
if self.checkHasConflict(lfile):
if self.abortOnError:
return False
if lfile.process(self.workingRoot) == False:
log('错误','文件处理发生错误 ' + lfile.path)
if self.abortOnError:
return False
else:
self.completedFiles.append(lfile)
return True
#解决agcp与agconnect-core的类冲突
def mod_agcp(self):
for fileName in os.listdir(os.path.join(self.workingRoot, 'output/libs')):
if not fileName.startswith('com.huawei.agconnect.agcp'):
continue
oldJarPath = os.path.join(self.workingRoot, 'output/libs', fileName)
newJarPath = os.path.join(self.workingRoot, 'output/libs', fileName[0:-4] + '.mod.jar')
log('修改', oldJarPath + ' -> ' + newJarPath)
zin = zipfile.ZipFile (oldJarPath, 'r')
zout = zipfile.ZipFile (newJarPath, 'w')
for item in zin.infolist():
buffer = zin.read(item.filename)
if not item.filename.startswith('com/huawei/agconnect/config'):
zout.writestr(item, buffer)
zout.close()
zin.close()
os.remove(oldJarPath)
def run(self) -> bool:
ret = True
while True:
if not self.prepareFiles():
ret = False
break
if not self.processFiles():
ret = False
break
break
self.mod_agcp()
if self.shouldCleanup:
self.cleanup()
return ret
def cleanup(self):
for lib in self.libraries:
lib.cleanup()
app = HMSRepackApp('/Users/aidoo/Downloads/HMS_Eclipse', shouldCleanup=True)
app.run()
print('done')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment