deliverpack.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. #!/usr/bin/env python3
  2. # Copyright (C) 2018 RDA Technologies Limited and/or its affiliates("RDA").
  3. # All rights reserved.
  4. #
  5. # This software is supplied "AS IS" without any warranties.
  6. # RDA assumes no responsibility or liability for the use of the software,
  7. # conveys no license or title under any patent, copyright, or mask work
  8. # right to the product. RDA reserves the right to make changes in the
  9. # software without notification. RDA also make no representation or
  10. # warranty that such application will be suitable for the specified use
  11. # without further testing or modification.
  12. import argparse
  13. import sys
  14. import io
  15. import os
  16. import tarfile
  17. #quectel update
  18. import zipfile
  19. import glob
  20. from pathlib import Path
  21. import json
  22. import shutil
  23. import copy
  24. import xml.etree.ElementTree as ET
  25. DESCRIPTION = """
  26. Create deliver pack from XML description.
  27. """
  28. """
  29. devliver.xml format
  30. For each XML file, there are:
  31. * source base directory: all sources are related to
  32. * destination base directory: all destination are related to
  33. Usually, both of them are None. Which means that source are related to
  34. current directory, and destination are related to the root of output.
  35. All sources should be related to source base directory, and destination
  36. can be related to root of output by set root="y".
  37. The root element tag of XML should be 'deliver'.
  38. When the element supports 'inclusive' and 'exclusive', the condition is:
  39. * Neither 'inclusive' nor 'exclusive' appear, the element should be taken;
  40. * When there are no valid 'inclusive', it won't be taken;
  41. * When there are valid 'exclusive', it won't be taken;
  42. The element types:
  43. * Devliver a file.
  44. <file src="src" dst="dst" dstdir="dir" root="y" optional="y"
  45. inclusive="tag,tag" exclusive="tag,tag"/>
  46. Unless optional="y", the source file must exist.
  47. The destination order:
  48. * When "dst" exist, it will be used. Otherwise,
  49. * When "dstdir" exist, the destination file will be under "dstdir", and
  50. the file name is the same as source. Otherwise,
  51. * The directory and file name is the same as source.
  52. * Deliver glob of files.
  53. <globfile src="*.h" dstdir="dst" root="y"
  54. inclusive="tag,tag" exclusive="tag,tag"/>
  55. It is not an error when glob of file is empty. Also, the matched
  56. directory will be ignored.
  57. When "dstdir" doesn't exist, the destination directory will be the same
  58. as source.
  59. * Deliver a directory.
  60. <recursive src="src" dst="dst" root="y" optional="y"
  61. exclude="name,name" inclusive="tag,tag" exclusive="tag,tag"/>
  62. When "dst" doesn't exist, the destination directory will be the same
  63. as source.
  64. * Handle sub-directory (not implemented now)
  65. <subdir dir="dir" dst="dst" xml="xml" root="y" optional="y"
  66. inclusive="tag,tag" exclusive="tag,tag"/>
  67. By default, both source base directory and destination directory will be
  68. changed to "dir" sub-directory. When "dst" exist, the destination
  69. directory will be changed to "dst" sub-directory.
  70. The default XML in sub-directory is "deliver.xml".
  71. * Include another xml. The working directory won't be changed.
  72. <include xml="xml" optional="y"
  73. inclusive="tag,tag" exclusive="tag,tag"/>
  74. * Group with the same conditions.
  75. <group inclusive="tag,tag" exclusive="tag,tag">
  76. <file .../>
  77. <globfile .../>
  78. <recursive .../>
  79. <subdir .../>
  80. <include .../>
  81. <group .../>
  82. </group>
  83. """
  84. class Deliver(object):
  85. def __init__(self, itags, etags, kwords):
  86. self.src_base = None
  87. self.dst_base = None
  88. self.itags = itags
  89. self.etags = etags
  90. self.kwords = kwords
  91. self.flist = {}
  92. def loadxml(self, fname):
  93. with open(fname, 'r') as fh:
  94. fdata = fh.read()
  95. root = ET.fromstring(fdata.format(**self.kwords))
  96. if root.tag != 'deliver':
  97. raise Exception('invalid deliver xml')
  98. for elem in list(root):
  99. self._load_elem(elem)
  100. def _load_elem(self, elem):
  101. if not self._check_tags(elem):
  102. return
  103. if elem.tag == 'file':
  104. self._load_file(elem)
  105. elif elem.tag == 'globfile':
  106. self._load_globfile(elem)
  107. elif elem.tag == 'recursive':
  108. self._load_recursive(elem)
  109. elif elem.tag == 'subdir':
  110. self._load_subdir(elem)
  111. elif elem.tag == 'include':
  112. self._load_include(elem)
  113. elif elem.tag == 'group':
  114. self._load_group(elem)
  115. # elif elem.tag == 'copydir':
  116. # self._load_copydir(elem)
  117. else:
  118. raise Exception('invalid deliver xml tag' + elem.tag)
  119. def _add_file(self, src, dst):
  120. if dst in self.flist and src != self.flist[dst]:
  121. raise Exception('conflict ' + dst)
  122. self.flist[dst] = src
  123. def _check_tags(self, elem):
  124. itags = []
  125. etags = []
  126. if elem.get('inclusive'):
  127. itags = elem.get('inclusive').split(',')
  128. if elem.get('exclusive'):
  129. etags = elem.get('exclusive').split(',')
  130. if not itags and not etags:
  131. return True
  132. checked = False
  133. if self.itags:
  134. for tag in itags:
  135. if tag in self.itags:
  136. checked = True
  137. break
  138. if not checked:
  139. return False
  140. if self.etags:
  141. for tag in etags:
  142. if tag in self.etags:
  143. return False
  144. return True
  145. def _load_group(self, elem):
  146. for subelem in list(elem):
  147. self._load_elem(subelem)
  148. def _build_dst(self, dst, dstroot):
  149. if dstroot or not self.dst_base:
  150. return dst
  151. return self.dst_base + '/' + dst
  152. def _build_src(self, src):
  153. if not self.src_base:
  154. return src
  155. return self.src_base + '/' + src
  156. def _load_file(self, elem):
  157. optional = elem.get('optional', 'n') == 'y'
  158. dstroot = elem.get('root', 'n') == 'y'
  159. src = elem.get('src')
  160. if src is None:
  161. raise Exception('src is missed in file element')
  162. src = os.path.normpath(src.replace('\\', '/'))
  163. if elem.get('dst'):
  164. dst = self._build_dst(elem.get('dst'), dstroot)
  165. elif elem.get('dstdir'):
  166. dst = self._build_dst(elem.get('dstdir'),
  167. dstroot) + '/' + Path(src).name
  168. else:
  169. dst = self._build_dst(src, False)
  170. src = self._build_src(src)
  171. if not optional and not os.path.exists(src):
  172. raise Exception(src + ' doesn\'t exist')
  173. self._add_file(src, dst)
  174. def _load_globfile(self, elem):
  175. src = elem.get('src')
  176. if src is None:
  177. raise Exception('src is missed in globfile element')
  178. src = os.path.normpath(src.replace('\\', '/'))
  179. dstroot = elem.get('root', 'n') == 'y'
  180. if elem.get('dstdir'):
  181. dstdir = self._build_dst(elem.get('dstdir'), dstroot)
  182. else:
  183. dstdir = self._build_dst(str(Path(src).parent), False)
  184. src = self._build_src(src)
  185. srcdir = str(Path(src).parent)
  186. for fname in glob.glob(src):
  187. name = Path(fname).name
  188. srcfname = srcdir + '/' + name
  189. dstfname = dstdir + '/' + name
  190. if not os.path.isfile(srcfname):
  191. continue
  192. self._add_file(srcfname, dstfname)
  193. def _load_recursive(self, elem):
  194. excludes = []
  195. if elem.get('exclude'):
  196. excludes = elem.get('exclude').split(',')
  197. src = elem.get('src')
  198. if src is None:
  199. raise Exception('src is missed in recursive element')
  200. src = os.path.normpath(src.replace('\\', '/'))
  201. dstroot = elem.get('root', 'n') == 'y'
  202. if elem.get('dst'):
  203. dst = self._build_dst(elem.get('dst'), dstroot)
  204. else:
  205. dst = self._build_dst(src, False)
  206. src = self._build_src(src)
  207. self._load_recursive_dir(src, dst, excludes)
  208. def _load_recursive_dir(self, src, dst, excludes):
  209. for child in os.listdir(src):
  210. if excludes and child in excludes:
  211. continue
  212. srcname = src + '/' + child
  213. dstname = dst + '/' + child
  214. if os.path.isfile(srcname):
  215. self._add_file(srcname, dstname)
  216. elif os.path.isdir(srcname):
  217. self._load_recursive_dir(srcname, dstname, excludes)
  218. def _load_subdir(self, elem):
  219. pass
  220. def _load_include(self, elem):
  221. src = elem.get("xml")
  222. if src is None:
  223. raise Exception('xml is missed in include element')
  224. fname = self._build_src(src)
  225. optional = elem.get('optional', 'n') == 'y'
  226. if not optional and not os.path.exists(fname):
  227. raise Exception(fname + ' doesn\'t exist')
  228. deliver = Deliver(self.itags, self.etags, self.kwords)
  229. deliver.src_base = self.src_base
  230. deliver.dst_base = self.dst_base
  231. deliver.loadxml(fname)
  232. for dst, src in deliver.flist.items():
  233. self._add_file(src, dst)
  234. def _load_copydir(self, elem):
  235. src = elem.get('src')
  236. if src is None:
  237. raise Exception('src is missed in copydir element')
  238. src = os.path.normpath(src.replace('\\', '/'))
  239. dstroot = elem.get('root', 'n') == 'y'
  240. if elem.get('dstdir'):
  241. dstdir = self._build_dst(elem.get('dstdir'), dstroot)
  242. else:
  243. dstdir = self._build_dst(str(Path(src).parent), False)
  244. src = self._build_src(src)
  245. srcdir = str(Path(src).parent)
  246. shutil.copytree(srcdir, dstdir)
  247. def load_args(sub_parsers):
  248. parser = sub_parsers.add_parser('load',
  249. help='load description xml to json')
  250. parser.set_defaults(func=load)
  251. parser.add_argument('--inclusive', dest="inclusive", nargs="*",
  252. help="inclusive tags")
  253. parser.add_argument('--exclusive', dest="exclusive", nargs="*",
  254. help="exclusive tags")
  255. parser.add_argument('--keyword', dest='keyword', nargs='*',
  256. help='keyword used in description')
  257. parser.add_argument('xml', help='description XML file')
  258. parser.add_argument('output', help='output json file')
  259. def load(args):
  260. kwords = {}
  261. if args.keyword:
  262. for elem in args.keyword:
  263. k, v = elem.split('=')
  264. kwords[k] = v
  265. # print(k,v)
  266. deliver = Deliver(args.inclusive, args.exclusive, kwords)
  267. deliver.loadxml(args.xml)
  268. with open(args.output, 'w') as fh:
  269. json.dump(deliver.flist, fh, sort_keys=True, indent=4)
  270. def pack_args(sub_parsers):
  271. parser = sub_parsers.add_parser('pack',
  272. help='pack to tar.gz based on file list json')
  273. parser.set_defaults(func=pack)
  274. parser.add_argument('--output', dest='output', default='pack.tar.gz',
  275. help='output tar.gz file name')
  276. parser.add_argument('json', nargs='+', help='file list json')
  277. def pack_zip_args(sub_parsers):
  278. parser = sub_parsers.add_parser('pack_zip',
  279. help='pack to zip based on file list json')
  280. parser.set_defaults(func=pack_zip)
  281. parser.add_argument('--output', dest='output', default='pack.zip',
  282. help='output zip file name')
  283. parser.add_argument('json', nargs='+', help='file list json')
  284. def pack_zip(args):
  285. flist = {}
  286. for fname in args.json:
  287. with open(fname, 'r') as fh:
  288. fl = json.loads(fh.read())
  289. if not fl:
  290. continue
  291. for dst, src in fl.items():
  292. if dst in flist and src != flist[dst]:
  293. raise Exception('conflict ' + dst)
  294. flist[dst] = src
  295. zipout = zipfile.ZipFile(args.output, 'w', zipfile.ZIP_DEFLATED)
  296. for dst in sorted(flist.keys()):
  297. zipout.write(flist[dst], arcname=dst)
  298. zipout.close()
  299. def pack(args):
  300. flist = {}
  301. for fname in args.json:
  302. with open(fname, 'r') as fh:
  303. fl = json.loads(fh.read())
  304. if not fl:
  305. continue
  306. for dst, src in fl.items():
  307. if dst in flist and src != flist[dst]:
  308. raise Exception('conflict ' + dst)
  309. flist[dst] = src
  310. #
  311. #quectel: collect pkg as folder
  312. #
  313. # sdk_root = args.output
  314. # if not os.path.exists(sdk_root):
  315. # os.makedirs(sdk_root)
  316. # for dst in sorted(flist.keys()):
  317. # dst_file=sdk_root+'/'+dst
  318. # if not os.path.exists(os.path.dirname(dst_file)):
  319. # os.makedirs(os.path.dirname(dst_file))
  320. # shutil.copyfile(flist[dst], dst_file)
  321. tarout = tarfile.open(args.output, 'w:gz')
  322. index = 0
  323. for dst in sorted(flist.keys()):
  324. index = index + 1
  325. if index%1000 == 0 :
  326. print('packing file ...')
  327. tarout.add(flist[dst], arcname=dst)
  328. tarout.close()
  329. def main(argv):
  330. parser = argparse.ArgumentParser(description=DESCRIPTION)
  331. sub_parsers = parser.add_subparsers(help="sub-commands")
  332. load_args(sub_parsers)
  333. #pack_args(sub_parsers)
  334. #quectel update
  335. pack_zip_args(sub_parsers)
  336. args = parser.parse_args(argv)
  337. if args.__contains__('func'):
  338. return args.func(args)
  339. parser.parse_args(['-h'])
  340. return 0
  341. if __name__ == '__main__':
  342. sys.exit(main(sys.argv[1:]))