license.bbclass 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. #
  2. # Copyright OpenEmbedded Contributors
  3. #
  4. # SPDX-License-Identifier: MIT
  5. #
  6. # Populates LICENSE_DIRECTORY as set in distro config with the license files as set by
  7. # LIC_FILES_CHKSUM.
  8. # TODO:
  9. # - There is a real issue revolving around license naming standards.
  10. LICENSE_DIRECTORY ??= "${DEPLOY_DIR}/licenses"
  11. LICSSTATEDIR = "${WORKDIR}/license-destdir/"
  12. # Create extra package with license texts and add it to RRECOMMENDS:${PN}
  13. LICENSE_CREATE_PACKAGE[type] = "boolean"
  14. LICENSE_CREATE_PACKAGE ??= "0"
  15. LICENSE_PACKAGE_SUFFIX ??= "-lic"
  16. LICENSE_FILES_DIRECTORY ??= "${datadir}/licenses/"
  17. LICENSE_DEPLOY_PATHCOMPONENT = "${SSTATE_PKGARCH}"
  18. LICENSE_DEPLOY_PATHCOMPONENT:class-cross = "native"
  19. LICENSE_DEPLOY_PATHCOMPONENT:class-native = "native"
  20. # Ensure the *value* of SSTATE_PKGARCH is captured as it is used in the output paths
  21. LICENSE_DEPLOY_PATHCOMPONENT[vardepvalue] += "${LICENSE_DEPLOY_PATHCOMPONENT}"
  22. addtask populate_lic after do_patch before do_build
  23. do_populate_lic[dirs] = "${LICSSTATEDIR}/${LICENSE_DEPLOY_PATHCOMPONENT}/${PN}"
  24. do_populate_lic[cleandirs] = "${LICSSTATEDIR}"
  25. python do_populate_lic() {
  26. """
  27. Populate LICENSE_DIRECTORY with licenses.
  28. """
  29. lic_files_paths = find_license_files(d)
  30. # The base directory we wrangle licenses to
  31. destdir = os.path.join(d.getVar('LICSSTATEDIR'), d.getVar('LICENSE_DEPLOY_PATHCOMPONENT'), d.getVar('PN'))
  32. copy_license_files(lic_files_paths, destdir)
  33. info = get_recipe_info(d)
  34. with open(os.path.join(destdir, "recipeinfo"), "w") as f:
  35. for key in sorted(info.keys()):
  36. f.write("%s: %s\n" % (key, info[key]))
  37. oe.qa.exit_if_errors(d)
  38. }
  39. # it would be better to copy them in do_install:append, but find_license_files is python
  40. python perform_packagecopy:prepend () {
  41. enabled = oe.data.typed_value('LICENSE_CREATE_PACKAGE', d)
  42. if d.getVar('CLASSOVERRIDE') == 'class-target' and enabled:
  43. lic_files_paths = find_license_files(d)
  44. # LICENSE_FILES_DIRECTORY starts with '/' so os.path.join cannot be used to join D and LICENSE_FILES_DIRECTORY
  45. destdir = d.getVar('D') + os.path.join(d.getVar('LICENSE_FILES_DIRECTORY'), d.getVar('PN'))
  46. copy_license_files(lic_files_paths, destdir)
  47. add_package_and_files(d)
  48. }
  49. perform_packagecopy[vardeps] += "LICENSE_CREATE_PACKAGE"
  50. def get_recipe_info(d):
  51. info = {}
  52. info["PV"] = d.getVar("PV")
  53. info["PR"] = d.getVar("PR")
  54. info["LICENSE"] = d.getVar("LICENSE")
  55. return info
  56. def add_package_and_files(d):
  57. packages = d.getVar('PACKAGES')
  58. files = d.getVar('LICENSE_FILES_DIRECTORY')
  59. pn = d.getVar('PN')
  60. pn_lic = "%s%s" % (pn, d.getVar('LICENSE_PACKAGE_SUFFIX', False))
  61. if pn_lic in packages.split():
  62. bb.warn("%s package already existed in %s." % (pn_lic, pn))
  63. else:
  64. # first in PACKAGES to be sure that nothing else gets LICENSE_FILES_DIRECTORY
  65. d.setVar('PACKAGES', "%s %s" % (pn_lic, packages))
  66. d.setVar('FILES:' + pn_lic, files)
  67. def copy_license_files(lic_files_paths, destdir):
  68. import shutil
  69. import errno
  70. bb.utils.mkdirhier(destdir)
  71. for (basename, path, beginline, endline) in lic_files_paths:
  72. try:
  73. src = path
  74. dst = os.path.join(destdir, basename)
  75. if os.path.exists(dst):
  76. os.remove(dst)
  77. if os.path.islink(src):
  78. src = os.path.realpath(src)
  79. canlink = os.access(src, os.W_OK) and (os.stat(src).st_dev == os.stat(destdir).st_dev) and beginline is None and endline is None
  80. if canlink:
  81. try:
  82. os.link(src, dst)
  83. except OSError as err:
  84. if err.errno == errno.EXDEV:
  85. # Copy license files if hardlink is not possible even if st_dev is the
  86. # same on source and destination (docker container with device-mapper?)
  87. canlink = False
  88. else:
  89. raise
  90. # Only chown if we did hardlink and we're running under pseudo
  91. if canlink and os.environ.get('PSEUDO_DISABLED') == '0':
  92. os.chown(dst,0,0)
  93. if not canlink:
  94. begin_idx = max(0, int(beginline) - 1) if beginline is not None else None
  95. end_idx = max(0, int(endline)) if endline is not None else None
  96. if begin_idx is None and end_idx is None:
  97. shutil.copyfile(src, dst)
  98. else:
  99. with open(src, 'rb') as src_f:
  100. with open(dst, 'wb') as dst_f:
  101. dst_f.write(b''.join(src_f.readlines()[begin_idx:end_idx]))
  102. except Exception as e:
  103. bb.warn("Could not copy license file %s to %s: %s" % (src, dst, e))
  104. def find_license_files(d):
  105. """
  106. Creates list of files used in LIC_FILES_CHKSUM and generic LICENSE files.
  107. """
  108. import shutil
  109. import oe.license
  110. from collections import defaultdict, OrderedDict
  111. # All the license files for the package
  112. lic_files = d.getVar('LIC_FILES_CHKSUM') or ""
  113. pn = d.getVar('PN')
  114. # The license files are located in S/LIC_FILE_CHECKSUM.
  115. srcdir = d.getVar('S')
  116. # Directory we store the generic licenses as set in the distro configuration
  117. generic_directory = d.getVar('COMMON_LICENSE_DIR')
  118. # List of basename, path tuples
  119. lic_files_paths = []
  120. # hash for keep track generic lics mappings
  121. non_generic_lics = {}
  122. # Entries from LIC_FILES_CHKSUM
  123. lic_chksums = {}
  124. license_source_dirs = []
  125. license_source_dirs.append(generic_directory)
  126. try:
  127. additional_lic_dirs = d.getVar('LICENSE_PATH').split()
  128. for lic_dir in additional_lic_dirs:
  129. license_source_dirs.append(lic_dir)
  130. except:
  131. pass
  132. class FindVisitor(oe.license.LicenseVisitor):
  133. def visit_Str(self, node):
  134. #
  135. # Until I figure out what to do with
  136. # the two modifiers I support (or greater = +
  137. # and "with exceptions" being *
  138. # we'll just strip out the modifier and put
  139. # the base license.
  140. find_licenses(node.s.replace("+", "").replace("*", ""))
  141. self.generic_visit(node)
  142. def visit_Constant(self, node):
  143. find_licenses(node.value.replace("+", "").replace("*", ""))
  144. self.generic_visit(node)
  145. def find_licenses(license_type):
  146. try:
  147. bb.utils.mkdirhier(gen_lic_dest)
  148. except:
  149. pass
  150. spdx_generic = None
  151. license_source = None
  152. # If the generic does not exist we need to check to see if there is an SPDX mapping to it,
  153. # unless NO_GENERIC_LICENSE is set.
  154. for lic_dir in license_source_dirs:
  155. if not os.path.isfile(os.path.join(lic_dir, license_type)):
  156. if d.getVarFlag('SPDXLICENSEMAP', license_type) != None:
  157. # Great, there is an SPDXLICENSEMAP. We can copy!
  158. bb.debug(1, "We need to use a SPDXLICENSEMAP for %s" % (license_type))
  159. spdx_generic = d.getVarFlag('SPDXLICENSEMAP', license_type)
  160. license_source = lic_dir
  161. break
  162. elif os.path.isfile(os.path.join(lic_dir, license_type)):
  163. spdx_generic = license_type
  164. license_source = lic_dir
  165. break
  166. non_generic_lic = d.getVarFlag('NO_GENERIC_LICENSE', license_type)
  167. if spdx_generic and license_source:
  168. # we really should copy to generic_ + spdx_generic, however, that ends up messing the manifest
  169. # audit up. This should be fixed in emit_pkgdata (or, we actually got and fix all the recipes)
  170. lic_files_paths.append(("generic_" + license_type, os.path.join(license_source, spdx_generic),
  171. None, None))
  172. # The user may attempt to use NO_GENERIC_LICENSE for a generic license which doesn't make sense
  173. # and should not be allowed, warn the user in this case.
  174. if d.getVarFlag('NO_GENERIC_LICENSE', license_type):
  175. oe.qa.handle_error("license-no-generic",
  176. "%s: %s is a generic license, please don't use NO_GENERIC_LICENSE for it." % (pn, license_type), d)
  177. elif non_generic_lic and non_generic_lic in lic_chksums:
  178. # if NO_GENERIC_LICENSE is set, we copy the license files from the fetched source
  179. # of the package rather than the license_source_dirs.
  180. lic_files_paths.append(("generic_" + license_type,
  181. os.path.join(srcdir, non_generic_lic), None, None))
  182. non_generic_lics[non_generic_lic] = license_type
  183. else:
  184. # Explicitly avoid the CLOSED license because this isn't generic
  185. if license_type != 'CLOSED':
  186. # And here is where we warn people that their licenses are lousy
  187. oe.qa.handle_error("license-exists",
  188. "%s: No generic license file exists for: %s in any provider" % (pn, license_type), d)
  189. pass
  190. if not generic_directory:
  191. bb.fatal("COMMON_LICENSE_DIR is unset. Please set this in your distro config")
  192. for url in lic_files.split():
  193. try:
  194. (method, host, path, user, pswd, parm) = bb.fetch.decodeurl(url)
  195. if method != "file" or not path:
  196. raise bb.fetch.MalformedUrl()
  197. except bb.fetch.MalformedUrl:
  198. bb.fatal("%s: LIC_FILES_CHKSUM contains an invalid URL: %s" % (d.getVar('PF'), url))
  199. # We want the license filename and path
  200. chksum = parm.get('md5', None)
  201. beginline = parm.get('beginline')
  202. endline = parm.get('endline')
  203. lic_chksums[path] = (chksum, beginline, endline)
  204. v = FindVisitor()
  205. try:
  206. v.visit_string(d.getVar('LICENSE'))
  207. except oe.license.InvalidLicense as exc:
  208. bb.fatal('%s: %s' % (d.getVar('PF'), exc))
  209. except SyntaxError:
  210. oe.qa.handle_error("license-syntax",
  211. "%s: Failed to parse LICENSE: %s" % (d.getVar('PF'), d.getVar('LICENSE')), d)
  212. # Add files from LIC_FILES_CHKSUM to list of license files
  213. lic_chksum_paths = defaultdict(OrderedDict)
  214. for path, data in sorted(lic_chksums.items()):
  215. lic_chksum_paths[os.path.basename(path)][data] = (os.path.join(srcdir, path), data[1], data[2])
  216. for basename, files in lic_chksum_paths.items():
  217. if len(files) == 1:
  218. # Don't copy again a LICENSE already handled as non-generic
  219. if basename in non_generic_lics:
  220. continue
  221. data = list(files.values())[0]
  222. lic_files_paths.append(tuple([basename] + list(data)))
  223. else:
  224. # If there are multiple different license files with identical
  225. # basenames we rename them to <file>.0, <file>.1, ...
  226. for i, data in enumerate(files.values()):
  227. lic_files_paths.append(tuple(["%s.%d" % (basename, i)] + list(data)))
  228. return lic_files_paths
  229. SSTATETASKS += "do_populate_lic"
  230. do_populate_lic[sstate-inputdirs] = "${LICSSTATEDIR}"
  231. do_populate_lic[sstate-outputdirs] = "${LICENSE_DIRECTORY}/"
  232. IMAGE_CLASSES:append = " license_image"
  233. python do_populate_lic_setscene () {
  234. sstate_setscene(d)
  235. }
  236. addtask do_populate_lic_setscene