PageRenderTime 69ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 1ms

/conary/build/packagepolicy.py

https://bitbucket.org/brcha/conary
Python | 4773 lines | 4315 code | 141 blank | 317 comment | 169 complexity | 4810480e32e4d213727191b6645265c2 MD5 | raw file
Possible License(s): GPL-3.0

Large files files are truncated, but you can click here to view the full file

  1. #
  2. # Copyright (c) rPath, Inc.
  3. #
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation, either version 3 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. #
  17. """
  18. Module used after C{%(destdir)s} has been finalized to create the
  19. initial packaging. Also contains error reporting.
  20. """
  21. import codecs
  22. import imp
  23. import itertools
  24. import os
  25. import re
  26. import site
  27. import sre_constants
  28. import stat
  29. import subprocess
  30. import sys
  31. from conary import files, trove
  32. from conary.build import buildpackage, filter, policy, recipe, tags, use
  33. from conary.build import smartform
  34. from conary.deps import deps
  35. from conary.lib import elf, magic, util, pydeps, fixedglob, graph
  36. from conary.build.action import TARGET_LINUX
  37. from conary.build.action import TARGET_WINDOWS
  38. try:
  39. from xml.etree import ElementTree
  40. except ImportError:
  41. try:
  42. from elementtree import ElementTree
  43. except ImportError:
  44. ElementTree = None
  45. # Helper class
  46. class _DatabaseDepCache(object):
  47. __slots__ = ['db', 'cache']
  48. def __init__(self, db):
  49. self.db = db
  50. self.cache = {}
  51. def getProvides(self, depSetList):
  52. ret = {}
  53. missing = []
  54. for depSet in depSetList:
  55. if depSet in self.cache:
  56. ret[depSet] = self.cache[depSet]
  57. else:
  58. missing.append(depSet)
  59. newresults = self.db.getTrovesWithProvides(missing)
  60. ret.update(newresults)
  61. self.cache.update(newresults)
  62. return ret
  63. class _filterSpec(policy.Policy):
  64. """
  65. Pure virtual base class from which C{ComponentSpec} and C{PackageSpec}
  66. are derived.
  67. """
  68. bucket = policy.PACKAGE_CREATION
  69. processUnmodified = False
  70. supported_targets = (TARGET_LINUX, TARGET_WINDOWS)
  71. def __init__(self, *args, **keywords):
  72. self.extraFilters = []
  73. policy.Policy.__init__(self, *args, **keywords)
  74. def updateArgs(self, *args, **keywords):
  75. """
  76. Call derived classes (C{ComponentSpec} or C{PackageSpec}) as::
  77. ThisClass('<name>', 'filterexp1', 'filterexp2')
  78. where C{filterexp} is either a regular expression or a
  79. tuple of C{(regexp[, setmodes[, unsetmodes]])}
  80. """
  81. if args:
  82. theName = args[0]
  83. for filterexp in args[1:]:
  84. self.extraFilters.append((theName, filterexp))
  85. policy.Policy.updateArgs(self, **keywords)
  86. class _addInfo(policy.Policy):
  87. """
  88. Pure virtual class for policies that add information such as tags,
  89. requirements, and provision, to files.
  90. """
  91. bucket = policy.PACKAGE_CREATION
  92. processUnmodified = False
  93. requires = (
  94. ('PackageSpec', policy.REQUIRED_PRIOR),
  95. )
  96. keywords = {
  97. 'included': {},
  98. 'excluded': {}
  99. }
  100. supported_targets = (TARGET_LINUX, TARGET_WINDOWS)
  101. def updateArgs(self, *args, **keywords):
  102. """
  103. Call as::
  104. C{I{ClassName}(I{info}, I{filterexp})}
  105. or::
  106. C{I{ClassName}(I{info}, exceptions=I{filterexp})}
  107. where C{I{filterexp}} is either a regular expression or a
  108. tuple of C{(regexp[, setmodes[, unsetmodes]])}
  109. """
  110. if args:
  111. args = list(args)
  112. info = args.pop(0)
  113. if args:
  114. if not self.included:
  115. self.included = {}
  116. if info not in self.included:
  117. self.included[info] = []
  118. self.included[info].extend(args)
  119. elif 'exceptions' in keywords:
  120. # not the usual exception handling, this is an exception
  121. if not self.excluded:
  122. self.excluded = {}
  123. if info not in self.excluded:
  124. self.excluded[info] = []
  125. self.excluded[info].append(keywords.pop('exceptions'))
  126. else:
  127. raise TypeError, 'no paths provided'
  128. policy.Policy.updateArgs(self, **keywords)
  129. def doProcess(self, recipe):
  130. # for filters
  131. self.rootdir = self.rootdir % recipe.macros
  132. # instantiate filters
  133. d = {}
  134. for info in self.included:
  135. newinfo = info % recipe.macros
  136. l = []
  137. for item in self.included[info]:
  138. l.append(filter.Filter(item, recipe.macros))
  139. d[newinfo] = l
  140. self.included = d
  141. d = {}
  142. for info in self.excluded:
  143. newinfo = info % recipe.macros
  144. l = []
  145. for item in self.excluded[info]:
  146. l.append(filter.Filter(item, recipe.macros))
  147. d[newinfo] = l
  148. self.excluded = d
  149. policy.Policy.doProcess(self, recipe)
  150. def doFile(self, path):
  151. fullpath = self.recipe.macros.destdir+path
  152. if not util.isregular(fullpath) and not os.path.islink(fullpath):
  153. return
  154. self.runInfo(path)
  155. def runInfo(self, path):
  156. 'pure virtual'
  157. pass
  158. class Config(policy.Policy):
  159. """
  160. NAME
  161. ====
  162. B{C{r.Config()}} - Mark files as configuration files
  163. SYNOPSIS
  164. ========
  165. C{r.Config([I{filterexp}] || [I{exceptions=filterexp}])}
  166. DESCRIPTION
  167. ===========
  168. The C{r.Config} policy marks all files below C{%(sysconfdir)s}
  169. (that is, C{/etc}) and C{%(taghandlerdir)s} (that is,
  170. C{/usr/libexec/conary/tags/}), and any other files explicitly
  171. mentioned, as configuration files.
  172. - To mark files as exceptions, use
  173. C{r.Config(exceptions='I{filterexp}')}.
  174. - To mark explicit inclusions as configuration files, use:
  175. C{r.Config('I{filterexp}')}
  176. A file marked as a Config file cannot also be marked as a
  177. Transient file or an InitialContents file. Conary enforces this
  178. requirement.
  179. EXAMPLES
  180. ========
  181. C{r.Config(exceptions='%(sysconfdir)s/X11/xkb/xkbcomp')}
  182. The file C{/etc/X11/xkb/xkbcomp} is marked as an exception, since it is
  183. not actually a configuration file even though it is within the C{/etc}
  184. (C{%(sysconfdir)s}) directory hierarchy and would be marked as a
  185. configuration file by default.
  186. C{r.Config('%(mmdir)s/Mailman/mm_cfg.py')}
  187. Marks the file C{%(mmdir)s/Mailman/mm_cfg.py} as a configuration file;
  188. it would not be automatically marked as a configuration file otherwise.
  189. """
  190. bucket = policy.PACKAGE_CREATION
  191. processUnmodified = True
  192. requires = (
  193. # for :config component, ComponentSpec must run after Config
  194. # Otherwise, this policy would follow PackageSpec and just set isConfig
  195. # on each config file
  196. ('ComponentSpec', policy.REQUIRED_SUBSEQUENT),
  197. )
  198. invariantinclusions = [ '%(sysconfdir)s/', '%(taghandlerdir)s/']
  199. invariantexceptions = [ '%(userinfodir)s/', '%(groupinfodir)s' ]
  200. def doFile(self, filename):
  201. m = self.recipe.magic[filename]
  202. if m and m.name == "ELF":
  203. # an ELF file cannot be a config file, some programs put
  204. # ELF files under /etc (X, for example), and tag handlers
  205. # can be ELF or shell scripts; we just want tag handlers
  206. # to be config files if they are shell scripts.
  207. # Just in case it was not intentional, warn...
  208. if self.macros.sysconfdir in filename:
  209. self.info('ELF file %s found in config directory', filename)
  210. return
  211. fullpath = self.macros.destdir + filename
  212. if os.path.isfile(fullpath) and util.isregular(fullpath):
  213. if self._fileIsBinary(filename, fullpath):
  214. self.error("binary file '%s' is marked as config" % \
  215. filename)
  216. self._markConfig(filename, fullpath)
  217. def _fileIsBinary(self, path, fn, maxsize=None, decodeFailIsError=True):
  218. limit = os.stat(fn)[stat.ST_SIZE]
  219. if maxsize is not None and limit > maxsize:
  220. self.warn('%s: file size %d longer than max %d',
  221. path, limit, maxsize)
  222. return True
  223. # we'll consider file to be binary file if we don't find any
  224. # good reason to mark it as text, or if we find a good reason
  225. # to mark it as binary
  226. foundFF = False
  227. foundNL = False
  228. f = open(fn, 'r')
  229. try:
  230. while f.tell() < limit:
  231. buf = f.read(65536)
  232. if chr(0) in buf:
  233. self.warn('%s: file contains NULL byte', path)
  234. return True
  235. if '\xff\xff' in buf:
  236. self.warn('%s: file contains 0xFFFF sequence', path)
  237. return True
  238. if '\xff' in buf:
  239. foundFF = True
  240. if '\n' in buf:
  241. foundNL = True
  242. finally:
  243. f.close()
  244. if foundFF and not foundNL:
  245. self.error('%s: found 0xFF without newline', path)
  246. utf8 = codecs.open(fn, 'r', 'utf-8')
  247. win1252 = codecs.open(fn, 'r', 'windows-1252')
  248. try:
  249. try:
  250. while utf8.tell() < limit:
  251. utf8.read(65536)
  252. except UnicodeDecodeError, e:
  253. # Still want to print a warning if it is not unicode;
  254. # Note that Code Page 1252 is considered a legacy
  255. # encoding on Windows
  256. self.warn('%s: %s', path, str(e))
  257. try:
  258. while win1252.tell() < limit:
  259. win1252.read(65536)
  260. except UnicodeDecodeError, e:
  261. self.warn('%s: %s', path, str(e))
  262. return decodeFailIsError
  263. finally:
  264. utf8.close()
  265. win1252.close()
  266. return False
  267. def _addTrailingNewline(self, filename, fullpath):
  268. # FIXME: This exists only for stability; there is no longer
  269. # any need to add trailing newlines to config files. This
  270. # also violates the rule that no files are modified after
  271. # destdir modification has been completed.
  272. self.warn("adding trailing newline to config file '%s'" % \
  273. filename)
  274. mode = os.lstat(fullpath)[stat.ST_MODE]
  275. oldmode = None
  276. if mode & 0600 != 0600:
  277. # need to be able to read and write the file to fix it
  278. oldmode = mode
  279. os.chmod(fullpath, mode|0600)
  280. f = open(fullpath, 'a')
  281. f.seek(0, 2)
  282. f.write('\n')
  283. f.close()
  284. if oldmode is not None:
  285. os.chmod(fullpath, oldmode)
  286. def _markConfig(self, filename, fullpath):
  287. self.info(filename)
  288. f = file(fullpath)
  289. f.seek(0, 2)
  290. if f.tell():
  291. # file has contents
  292. f.seek(-1, 2)
  293. lastchar = f.read(1)
  294. f.close()
  295. if lastchar != '\n':
  296. self._addTrailingNewline(filename, fullpath)
  297. f.close()
  298. self.recipe.ComponentSpec(_config=filename)
  299. class ComponentSpec(_filterSpec):
  300. """
  301. NAME
  302. ====
  303. B{C{r.ComponentSpec()}} - Determines which component each file is in
  304. SYNOPSIS
  305. ========
  306. C{r.ComponentSpec([I{componentname}, I{filterexp}] || [I{packagename}:I{componentname}, I{filterexp}])}
  307. DESCRIPTION
  308. ===========
  309. The C{r.ComponentSpec} policy includes the filter expressions that specify
  310. the default assignment of files to components. The expressions are
  311. considered in the order in which they are evaluated in the recipe, and the
  312. first match wins. After all the recipe-provided expressions are
  313. evaluated, the default expressions are evaluated. If no expression
  314. matches, then the file is assigned to the C{catchall} component.
  315. Note that in the C{I{packagename}:I{componentname}} form, the C{:}
  316. must be literal, it cannot be part of a macro.
  317. KEYWORDS
  318. ========
  319. B{catchall} : Specify the component name which gets all otherwise
  320. unassigned files. Default: C{runtime}
  321. EXAMPLES
  322. ========
  323. C{r.ComponentSpec('manual', '%(contentdir)s/manual/')}
  324. Uses C{r.ComponentSpec} to specify that all files below the
  325. C{%(contentdir)s/manual/} directory are part of the C{:manual} component.
  326. C{r.ComponentSpec('foo:bar', '%(sharedir)s/foo/')}
  327. Uses C{r.ComponentSpec} to specify that all files below the
  328. C{%(sharedir)s/foo/} directory are part of the C{:bar} component
  329. of the C{foo} package, avoiding the need to invoke both the
  330. C{ComponentSpec} and C{PackageSpec} policies.
  331. C{r.ComponentSpec(catchall='data')}
  332. Uses C{r.ComponentSpec} to specify that all files not otherwise specified
  333. go into the C{:data} component instead of the default {:runtime}
  334. component.
  335. """
  336. requires = (
  337. ('Config', policy.REQUIRED_PRIOR),
  338. ('PackageSpec', policy.REQUIRED_SUBSEQUENT),
  339. )
  340. keywords = { 'catchall': 'runtime' }
  341. def __init__(self, *args, **keywords):
  342. """
  343. @keyword catchall: The component name which gets all otherwise
  344. unassigned files. Default: C{runtime}
  345. """
  346. _filterSpec.__init__(self, *args, **keywords)
  347. self.configFilters = []
  348. self.derivedFilters = []
  349. def updateArgs(self, *args, **keywords):
  350. if '_config' in keywords:
  351. configPath=keywords.pop('_config')
  352. self.recipe.PackageSpec(_config=configPath)
  353. if args:
  354. name = args[0]
  355. if ':' in name:
  356. package, name = name.split(':')
  357. args = list(itertools.chain([name], args[1:]))
  358. if package:
  359. # we've got a package as well as a component, pass it on
  360. pkgargs = list(itertools.chain((package,), args[1:]))
  361. self.recipe.PackageSpec(*pkgargs)
  362. _filterSpec.updateArgs(self, *args, **keywords)
  363. def doProcess(self, recipe):
  364. compFilters = []
  365. self.macros = recipe.macros
  366. self.rootdir = self.rootdir % recipe.macros
  367. self.loadFilterDirs()
  368. # The extras need to come before base in order to override decisions
  369. # in the base subfilters; invariants come first for those very few
  370. # specs that absolutely should not be overridden in recipes.
  371. for filteritem in itertools.chain(self.invariantFilters,
  372. self.extraFilters,
  373. self.derivedFilters,
  374. self.configFilters,
  375. self.baseFilters):
  376. if not isinstance(filteritem, (filter.Filter, filter.PathSet)):
  377. name = filteritem[0] % self.macros
  378. assert(name != 'source')
  379. args, kwargs = self.filterExpArgs(filteritem[1:], name=name)
  380. filteritem = filter.Filter(*args, **kwargs)
  381. compFilters.append(filteritem)
  382. # by default, everything that hasn't matched a filter pattern yet
  383. # goes in the catchall component ('runtime' by default)
  384. compFilters.append(filter.Filter('.*', self.macros, name=self.catchall))
  385. # pass these down to PackageSpec for building the package
  386. recipe.PackageSpec(compFilters=compFilters)
  387. def loadFilterDirs(self):
  388. invariantFilterMap = {}
  389. baseFilterMap = {}
  390. self.invariantFilters = []
  391. self.baseFilters = []
  392. # Load all component python files
  393. for componentDir in self.recipe.cfg.componentDirs:
  394. for filterType, map in (('invariant', invariantFilterMap),
  395. ('base', baseFilterMap)):
  396. oneDir = os.sep.join((componentDir, filterType))
  397. if not os.path.isdir(oneDir):
  398. continue
  399. for filename in os.listdir(oneDir):
  400. fullpath = os.sep.join((oneDir, filename))
  401. if (not filename.endswith('.py') or
  402. not util.isregular(fullpath)):
  403. continue
  404. self.loadFilter(filterType, map, filename, fullpath)
  405. # populate the lists with dependency-sorted information
  406. for filterType, map, filterList in (
  407. ('invariant', invariantFilterMap, self.invariantFilters),
  408. ('base', baseFilterMap, self.baseFilters)):
  409. dg = graph.DirectedGraph()
  410. for filterName in map.keys():
  411. dg.addNode(filterName)
  412. filter, follows, precedes = map[filterName]
  413. def warnMissing(missing):
  414. self.error('%s depends on missing %s', filterName, missing)
  415. for prior in follows:
  416. if not prior in map:
  417. warnMissing(prior)
  418. dg.addEdge(prior, filterName)
  419. for subsequent in precedes:
  420. if not subsequent in map:
  421. warnMissing(subsequent)
  422. dg.addEdge(filterName, subsequent)
  423. # test for dependency loops
  424. depLoops = [x for x in dg.getStronglyConnectedComponents()
  425. if len(x) > 1]
  426. if depLoops:
  427. self.error('dependency loop(s) in component filters: %s',
  428. ' '.join(sorted(':'.join(x)
  429. for x in sorted(list(depLoops)))))
  430. return
  431. # Create a stably-sorted list of config filters where
  432. # the filter is not empty. (An empty filter with both
  433. # follows and precedes specified can be used to induce
  434. # ordering between otherwise unrelated components.)
  435. #for name in dg.getTotalOrdering(nodeSort=lambda a, b: cmp(a,b)):
  436. for name in dg.getTotalOrdering():
  437. filters = map[name][0]
  438. if not filters:
  439. continue
  440. componentName = filters[0]
  441. for filterExp in filters[1]:
  442. filterList.append((componentName, filterExp))
  443. def loadFilter(self, filterType, map, filename, fullpath):
  444. # do not load shared libraries
  445. desc = [x for x in imp.get_suffixes() if x[0] == '.py'][0]
  446. f = file(fullpath)
  447. modname = filename[:-3]
  448. m = imp.load_module(modname, f, fullpath, desc)
  449. f.close()
  450. if not 'filters' in m.__dict__:
  451. self.warn('%s missing "filters"; not a valid component'
  452. ' specification file', fullpath)
  453. return
  454. filters = m.__dict__['filters']
  455. if filters and len(filters) > 1 and type(filters[1]) not in (list,
  456. tuple):
  457. self.error('invalid expression in %s: filters specification'
  458. " must be ('name', ('expression', ...))", fullpath)
  459. follows = ()
  460. if 'follows' in m.__dict__:
  461. follows = m.__dict__['follows']
  462. precedes = ()
  463. if 'precedes' in m.__dict__:
  464. precedes = m.__dict__['precedes']
  465. map[modname] = (filters, follows, precedes)
  466. class PackageSpec(_filterSpec):
  467. """
  468. NAME
  469. ====
  470. B{C{r.PackageSpec()}} - Determines which package each file is in
  471. SYNOPSIS
  472. ========
  473. C{r.PackageSpec(I{packagename}, I{filterexp})}
  474. DESCRIPTION
  475. ===========
  476. The C{r.PackageSpec()} policy determines which package each file
  477. is in. (Use C{r.ComponentSpec()} to specify the component without
  478. specifying the package, or to specify C{I{package}:I{component}}
  479. in one invocation.)
  480. EXAMPLES
  481. ========
  482. C{r.PackageSpec('openssh-server', '%(sysconfdir)s/pam.d/sshd')}
  483. Specifies that the file C{%(sysconfdir)s/pam.d/sshd} is in the package
  484. C{openssh-server} rather than the default (which in this case would have
  485. been C{openssh} because this example was provided by C{openssh.recipe}).
  486. """
  487. requires = (
  488. ('ComponentSpec', policy.REQUIRED_PRIOR),
  489. )
  490. keywords = { 'compFilters': None }
  491. def __init__(self, *args, **keywords):
  492. """
  493. @keyword compFilters: reserved for C{ComponentSpec} to pass information
  494. needed by C{PackageSpec}.
  495. """
  496. _filterSpec.__init__(self, *args, **keywords)
  497. self.configFiles = []
  498. self.derivedFilters = []
  499. def updateArgs(self, *args, **keywords):
  500. if '_config' in keywords:
  501. self.configFiles.append(keywords.pop('_config'))
  502. # keep a list of packages filtered for in PackageSpec in the recipe
  503. if args:
  504. newTrove = args[0] % self.recipe.macros
  505. self.recipe.packages[newTrove] = True
  506. _filterSpec.updateArgs(self, *args, **keywords)
  507. def preProcess(self):
  508. self.pkgFilters = []
  509. recipe = self.recipe
  510. self.destdir = recipe.macros.destdir
  511. if self.exceptions:
  512. self.warn('PackageSpec does not honor exceptions')
  513. self.exceptions = None
  514. if self.inclusions:
  515. # would have an effect only with exceptions listed, so no warning...
  516. self.inclusions = None
  517. # userinfo and groupinfo are invariant filters, so they must come first
  518. for infoType in ('user', 'group'):
  519. infoDir = '%%(%sinfodir)s' % infoType % self.macros
  520. realDir = util.joinPaths(self.destdir, infoDir)
  521. if not os.path.isdir(realDir):
  522. continue
  523. for infoPkgName in os.listdir(realDir):
  524. pkgPath = util.joinPaths(infoDir, infoPkgName)
  525. self.pkgFilters.append( \
  526. filter.Filter(pkgPath, self.macros,
  527. name = 'info-%s' % infoPkgName))
  528. # extras need to come before derived so that derived packages
  529. # can change the package to which a file is assigned
  530. for filteritem in itertools.chain(self.extraFilters,
  531. self.derivedFilters):
  532. if not isinstance(filteritem, (filter.Filter, filter.PathSet)):
  533. name = filteritem[0] % self.macros
  534. if not trove.troveNameIsValid(name):
  535. self.error('%s is not a valid package name', name)
  536. args, kwargs = self.filterExpArgs(filteritem[1:], name=name)
  537. self.pkgFilters.append(filter.Filter(*args, **kwargs))
  538. else:
  539. self.pkgFilters.append(filteritem)
  540. # by default, everything that hasn't matched a pattern in the
  541. # main package filter goes in the package named recipe.name
  542. self.pkgFilters.append(filter.Filter('.*', self.macros, name=recipe.name))
  543. # OK, all the filters exist, build an autopackage object that
  544. # knows about them
  545. recipe.autopkg = buildpackage.AutoBuildPackage(
  546. self.pkgFilters, self.compFilters, recipe)
  547. self.autopkg = recipe.autopkg
  548. def do(self):
  549. # Walk capsule contents ignored by doFile
  550. for filePath, _, componentName in self.recipe._iterCapsulePaths():
  551. realPath = self.destdir + filePath
  552. if util.exists(realPath):
  553. # Files that do not exist on the filesystem (devices)
  554. # are handled separately
  555. self.autopkg.addFile(filePath, realPath, componentName)
  556. # Walk normal files
  557. _filterSpec.do(self)
  558. def doFile(self, path):
  559. # all policy classes after this require that the initial tree is built
  560. if not self.recipe._getCapsulePathsForFile(path):
  561. realPath = self.destdir + path
  562. self.autopkg.addFile(path, realPath)
  563. def postProcess(self):
  564. # flag all config files
  565. for confname in self.configFiles:
  566. self.recipe.autopkg.pathMap[confname].flags.isConfig(True)
  567. class InitialContents(policy.Policy):
  568. """
  569. NAME
  570. ====
  571. B{C{r.InitialContents()}} - Mark only explicit inclusions as initial
  572. contents files
  573. SYNOPSIS
  574. ========
  575. C{InitialContents([I{filterexp}])}
  576. DESCRIPTION
  577. ===========
  578. By default, C{r.InitialContents()} does not apply to any files.
  579. It is used to specify all files that Conary needs to mark as
  580. providing only initial contents. When Conary installs or
  581. updates one of these files, it will never replace existing
  582. contents; it uses the provided contents only if the file does
  583. not yet exist at the time Conary is creating it.
  584. A file marked as an InitialContents file cannot also be marked
  585. as a Transient file or a Config file. Conary enforces this
  586. requirement.
  587. EXAMPLES
  588. ========
  589. C{r.InitialContents('%(sysconfdir)s/conary/.*gpg')}
  590. The files C{%(sysconfdir)s/conary/.*gpg} are being marked as initial
  591. contents files. Conary will use those contents when creating the files
  592. the first time, but will never overwrite existing contents in those files.
  593. """
  594. requires = (
  595. ('PackageSpec', policy.REQUIRED_PRIOR),
  596. ('Config', policy.REQUIRED_PRIOR),
  597. )
  598. bucket = policy.PACKAGE_CREATION
  599. processUnmodified = True
  600. invariantexceptions = [ '%(userinfodir)s/', '%(groupinfodir)s' ]
  601. invariantinclusions = ['%(localstatedir)s/run/',
  602. '%(localstatedir)s/log/',
  603. '%(cachedir)s/']
  604. def postInit(self, *args, **kwargs):
  605. self.recipe.Config(exceptions = self.invariantinclusions,
  606. allowUnusedFilters = True)
  607. def updateArgs(self, *args, **keywords):
  608. policy.Policy.updateArgs(self, *args, **keywords)
  609. self.recipe.Config(exceptions=args, allowUnusedFilters = True)
  610. def doFile(self, filename):
  611. fullpath = self.macros.destdir + filename
  612. recipe = self.recipe
  613. if os.path.isfile(fullpath) and util.isregular(fullpath):
  614. self.info(filename)
  615. f = recipe.autopkg.pathMap[filename]
  616. f.flags.isInitialContents(True)
  617. if f.flags.isConfig():
  618. self.error(
  619. '%s is marked as both a configuration file and'
  620. ' an initial contents file', filename)
  621. class Transient(policy.Policy):
  622. """
  623. NAME
  624. ====
  625. B{C{r.Transient()}} - Mark files that have transient contents
  626. SYNOPSIS
  627. ========
  628. C{r.Transient([I{filterexp}])}
  629. DESCRIPTION
  630. ===========
  631. The C{r.Transient()} policy marks files as containing transient
  632. contents. It automatically marks the two most common uses of transient
  633. contents: python and emacs byte-compiled files
  634. (C{.pyc}, C{.pyo}, and C{.elc} files).
  635. Files containing transient contents are almost the opposite of
  636. configuration files: their contents should be overwritten by
  637. the new contents without question at update time, even if the
  638. contents in the filesystem have changed. (Conary raises an
  639. error if file contents have changed in the filesystem for normal
  640. files.)
  641. A file marked as a Transient file cannot also be marked as an
  642. InitialContents file or a Config file. Conary enforces this
  643. requirement.
  644. EXAMPLES
  645. ========
  646. C{r.Transient('%(libdir)s/firefox/extensions/')}
  647. Marks all the files in the directory C{%(libdir)s/firefox/extensions/} as
  648. having transient contents.
  649. """
  650. bucket = policy.PACKAGE_CREATION
  651. filetree = policy.PACKAGE
  652. processUnmodified = True
  653. requires = (
  654. ('PackageSpec', policy.REQUIRED_PRIOR),
  655. ('Config', policy.REQUIRED_PRIOR),
  656. ('InitialContents', policy.REQUIRED_PRIOR),
  657. )
  658. invariantinclusions = [
  659. r'..*\.py(c|o)$',
  660. r'..*\.elc$',
  661. r'%(userinfodir)s/',
  662. r'%(groupinfodir)s'
  663. ]
  664. def doFile(self, filename):
  665. fullpath = self.macros.destdir + filename
  666. if os.path.isfile(fullpath) and util.isregular(fullpath):
  667. recipe = self.recipe
  668. f = recipe.autopkg.pathMap[filename]
  669. f.flags.isTransient(True)
  670. if f.flags.isConfig() or f.flags.isInitialContents():
  671. self.error(
  672. '%s is marked as both a transient file and'
  673. ' a configuration or initial contents file', filename)
  674. class TagDescription(policy.Policy):
  675. """
  676. NAME
  677. ====
  678. B{C{r.TagDescription()}} - Marks tag description files
  679. SYNOPSIS
  680. ========
  681. C{r.TagDescription([I{filterexp}])}
  682. DESCRIPTION
  683. ===========
  684. The C{r.TagDescription} class marks tag description files as
  685. such so that conary handles them correctly. Every file in
  686. C{%(tagdescriptiondir)s/} is marked as a tag description file by default.
  687. No file outside of C{%(tagdescriptiondir)s/} will be considered by this
  688. policy.
  689. EXAMPLES
  690. ========
  691. This policy is not called explicitly.
  692. """
  693. bucket = policy.PACKAGE_CREATION
  694. processUnmodified = False
  695. requires = (
  696. ('PackageSpec', policy.REQUIRED_PRIOR),
  697. )
  698. invariantsubtrees = [ '%(tagdescriptiondir)s/' ]
  699. def doFile(self, path):
  700. if self.recipe._getCapsulePathsForFile(path):
  701. return
  702. fullpath = self.macros.destdir + path
  703. if os.path.isfile(fullpath) and util.isregular(fullpath):
  704. self.info('conary tag file: %s', path)
  705. self.recipe.autopkg.pathMap[path].tags.set("tagdescription")
  706. class TagHandler(policy.Policy):
  707. """
  708. NAME
  709. ====
  710. B{C{r.TagHandler()}} - Mark tag handler files
  711. SYNOPSIS
  712. ========
  713. C{r.TagHandler([I{filterexp}])}
  714. DESCRIPTION
  715. ===========
  716. All files in C{%(taghandlerdir)s/} are marked as a tag
  717. handler files.
  718. EXAMPLES
  719. ========
  720. This policy is not called explicitly.
  721. """
  722. bucket = policy.PACKAGE_CREATION
  723. processUnmodified = False
  724. requires = (
  725. ('PackageSpec', policy.REQUIRED_PRIOR),
  726. )
  727. invariantsubtrees = [ '%(taghandlerdir)s/' ]
  728. def doFile(self, path):
  729. if self.recipe._getCapsulePathsForFile(path):
  730. return
  731. fullpath = self.macros.destdir + path
  732. if os.path.isfile(fullpath) and util.isregular(fullpath):
  733. self.info('conary tag handler: %s', path)
  734. self.recipe.autopkg.pathMap[path].tags.set("taghandler")
  735. class TagSpec(_addInfo):
  736. """
  737. NAME
  738. ====
  739. B{C{r.TagSpec()}} - Apply tags defined by tag descriptions
  740. SYNOPSIS
  741. ========
  742. C{r.TagSpec([I{tagname}, I{filterexp}] || [I{tagname}, I{exceptions=filterexp}])}
  743. DESCRIPTION
  744. ===========
  745. The C{r.TagSpec()} policy automatically applies tags defined by tag
  746. descriptions in both the current system and C{%(destdir)s} to all
  747. files in C{%(destdir)s}.
  748. To apply tags manually (removing a dependency on the tag description
  749. file existing when the packages is cooked), use the syntax:
  750. C{r.TagSpec(I{tagname}, I{filterexp})}.
  751. To set an exception to this policy, use:
  752. C{r.TagSpec(I{tagname}, I{exceptions=filterexp})}.
  753. EXAMPLES
  754. ========
  755. C{r.TagSpec('initscript', '%(initdir)s/')}
  756. Applies the C{initscript} tag to all files in the directory
  757. C{%(initdir)s/}.
  758. """
  759. requires = (
  760. ('PackageSpec', policy.REQUIRED_PRIOR),
  761. )
  762. def doProcess(self, recipe):
  763. self.tagList = []
  764. self.buildReqsComputedForTags = set()
  765. self.suggestBuildRequires = set()
  766. # read the system and %(destdir)s tag databases
  767. for directory in (recipe.macros.destdir+'/etc/conary/tags/',
  768. '/etc/conary/tags/'):
  769. if os.path.isdir(directory):
  770. for filename in os.listdir(directory):
  771. path = util.joinPaths(directory, filename)
  772. self.tagList.append(tags.TagFile(path, recipe.macros, True))
  773. self.fullReqs = self.recipe._getTransitiveBuildRequiresNames()
  774. _addInfo.doProcess(self, recipe)
  775. def markTag(self, name, tag, path, tagFile=None):
  776. # commonly, a tagdescription will nominate a file to be
  777. # tagged, but it will also be set explicitly in the recipe,
  778. # and therefore markTag will be called twice.
  779. if (len(tag.split()) > 1 or
  780. not tag.replace('-', '').replace('_', '').isalnum()):
  781. # handlers for multiple tags require strict tag names:
  782. # no whitespace, only alphanumeric plus - and _ characters
  783. self.error('illegal tag name %s for file %s' %(tag, path))
  784. return
  785. tags = self.recipe.autopkg.pathMap[path].tags
  786. if tag not in tags:
  787. self.info('%s: %s', name, path)
  788. tags.set(tag)
  789. if tagFile and tag not in self.buildReqsComputedForTags:
  790. self.buildReqsComputedForTags.add(tag)
  791. db = self._getDb()
  792. for trove in db.iterTrovesByPath(tagFile.tagFile):
  793. troveName = trove.getName()
  794. if troveName not in self.fullReqs:
  795. # XXX should be error, change after bootstrap
  796. self.warn("%s assigned by %s to file %s, so add '%s'"
  797. ' to buildRequires or call r.TagSpec()'
  798. %(tag, tagFile.tagFile, path, troveName))
  799. self.suggestBuildRequires.add(troveName)
  800. def runInfo(self, path):
  801. if self.recipe._getCapsulePathsForFile(path):
  802. # capsules do not participate in the tag protocol
  803. return
  804. excludedTags = {}
  805. for tag in self.included:
  806. for filt in self.included[tag]:
  807. if filt.match(path):
  808. isExcluded = False
  809. if tag in self.excluded:
  810. for filt in self.excluded[tag]:
  811. if filt.match(path):
  812. s = excludedTags.setdefault(tag, set())
  813. s.add(path)
  814. isExcluded = True
  815. break
  816. if not isExcluded:
  817. self.markTag(tag, tag, path)
  818. for tag in self.tagList:
  819. if tag.match(path):
  820. if tag.name:
  821. name = tag.name
  822. else:
  823. name = tag.tag
  824. isExcluded = False
  825. if tag.tag in self.excluded:
  826. for filt in self.excluded[tag.tag]:
  827. # exception handling is per-tag, so handled specially
  828. if filt.match(path):
  829. s = excludedTags.setdefault(name, set())
  830. s.add(path)
  831. isExcluded = True
  832. break
  833. if not isExcluded:
  834. self.markTag(name, tag.tag, path, tag)
  835. if excludedTags:
  836. for tag in excludedTags:
  837. self.info('ignoring tag match for %s: %s',
  838. tag, ', '.join(sorted(excludedTags[tag])))
  839. def postProcess(self):
  840. if self.suggestBuildRequires:
  841. self.info('possibly add to buildRequires: %s',
  842. str(sorted(list(self.suggestBuildRequires))))
  843. self.recipe.reportMissingBuildRequires(self.suggestBuildRequires)
  844. class Properties(policy.Policy):
  845. """
  846. NAME
  847. ====
  848. B{C{r.Properties()}} - Read property definition files
  849. SYNOPSIS
  850. ========
  851. C{r.Properties(I{exceptions=filterexp} || [I{contents=xml},
  852. I{package=pkg:component}])}
  853. DESCRIPTION
  854. ===========
  855. The C{r.Properties()} policy automatically parses iconfig property
  856. definition files, making the properties available for configuration
  857. management with iconfig.
  858. To add configuration properties manually, use the syntax:
  859. C{r.Properties(I{contents=ipropcontents}, I{package=pkg:component}}
  860. Where contents is the xml string that would normally be stored in the iprop
  861. file and package is the component where to attach the config metadata.
  862. (NOTE: This component must exist)
  863. """
  864. supported_targets = (TARGET_LINUX, TARGET_WINDOWS)
  865. bucket = policy.PACKAGE_CREATION
  866. processUnmodified = True
  867. invariantinclusions = [ r'%(prefix)s/lib/iconfig/properties/.*\.iprop' ]
  868. requires = (
  869. # We need to know what component files have been assigned to
  870. ('PackageSpec', policy.REQUIRED_PRIOR),
  871. )
  872. def __init__(self, *args, **kwargs):
  873. policy.Policy.__init__(self, *args, **kwargs)
  874. self.contents = []
  875. def updateArgs(self, *args, **kwargs):
  876. if 'contents' in kwargs:
  877. contents = kwargs.pop('contents')
  878. pkg = kwargs.pop('package', None)
  879. self.contents.append((pkg, contents))
  880. policy.Policy.updateArgs(self, *args, **kwargs)
  881. def doFile(self, path):
  882. fullpath = self.recipe.macros.destdir + path
  883. if not os.path.isfile(fullpath) or not util.isregular(fullpath):
  884. return
  885. componentMap = self.recipe.autopkg.componentMap
  886. if path not in componentMap:
  887. return
  888. main, comp = componentMap[path].getName().split(':')
  889. xml = open(fullpath).read()
  890. self._parsePropertyData(xml, main, comp)
  891. def postProcess(self):
  892. for pkg, content in self.contents:
  893. pkg = pkg % self.macros
  894. pkgName, compName = pkg.split(':')
  895. self._parsePropertyData(content, pkgName, compName)
  896. def _parsePropertyData(self, xml, pkgName, compName):
  897. xmldata = smartform.SmartFormFieldParser(xml)
  898. self.recipe._addProperty(trove._PROPERTY_TYPE_SMARTFORM,
  899. pkgName, compName, xmldata.name, xml, xmldata.default)
  900. class MakeDevices(policy.Policy):
  901. """
  902. NAME
  903. ====
  904. B{C{r.MakeDevices()}} - Make device nodes
  905. SYNOPSIS
  906. ========
  907. C{MakeDevices([I{path},] [I{type},] [I{major},] [I{minor},] [I{owner},] [I{groups},] [I{mode}])}
  908. DESCRIPTION
  909. ===========
  910. The C{r.MakeDevices()} policy creates device nodes. Conary's
  911. policy of non-root builds requires that these nodes exist only in the
  912. package, and not in the filesystem, as only root may actually create
  913. device nodes.
  914. EXAMPLES
  915. ========
  916. C{r.MakeDevices(I{'/dev/tty', 'c', 5, 0, 'root', 'root', mode=0666, package=':dev'})}
  917. Creates the device node C{/dev/tty}, as type 'c' (character, as opposed to
  918. type 'b', or block) with a major number of '5', minor number of '0',
  919. owner, and group are both the root user, and permissions are 0666.
  920. """
  921. bucket = policy.PACKAGE_CREATION
  922. processUnmodified = True
  923. requires = (
  924. ('PackageSpec', policy.REQUIRED_PRIOR),
  925. ('Ownership', policy.REQUIRED_SUBSEQUENT),
  926. )
  927. def __init__(self, *args, **keywords):
  928. self.devices = []
  929. policy.Policy.__init__(self, *args, **keywords)
  930. def updateArgs(self, *args, **keywords):
  931. """
  932. MakeDevices(path, devtype, major, minor, owner, group, mode=0400)
  933. """
  934. if args:
  935. args = list(args)
  936. l = len(args)
  937. if not ((l > 5) and (l < 9)):
  938. self.recipe.error('MakeDevices: incorrect arguments: %r %r'
  939. %(args, keywords))
  940. mode = keywords.pop('mode', None)
  941. package = keywords.pop('package', None)
  942. if l > 6 and mode is None:
  943. mode = args[6]
  944. if mode is None:
  945. mode = 0400
  946. if l > 7 and package is None:
  947. package = args[7]
  948. self.devices.append(
  949. (args[0:6], {'perms': mode, 'package': package}))
  950. policy.Policy.updateArgs(self, **keywords)
  951. def do(self):
  952. for device, kwargs in self.devices:
  953. r = self.recipe
  954. filename = device[0]
  955. owner = device[4]
  956. group = device[5]
  957. r.Ownership(owner, group, filename)
  958. device[0] = device[0] % r.macros
  959. r.autopkg.addDevice(*device, **kwargs)
  960. class setModes(policy.Policy):
  961. """
  962. Do not call from recipes; this is used internally by C{r.SetModes},
  963. C{r.ParseManifest}, and unpacking derived packages. This policy
  964. modified modes relative to the mode on the file in the filesystem.
  965. It adds setuid/setgid bits not otherwise set/honored on files on the
  966. filesystem, and sets user r/w/x bits if they were altered for the
  967. purposes of accessing the files during packaging. Otherwise,
  968. it honors the bits found on the filesystem. It does not modify
  969. bits in capsules.
  970. """
  971. bucket = policy.PACKAGE_CREATION
  972. processUnmodified = True
  973. requires = (
  974. ('PackageSpec', policy.REQUIRED_PRIOR),
  975. ('WarnWriteable', policy.REQUIRED_SUBSEQUENT),
  976. ('ExcludeDirectories', policy.CONDITIONAL_SUBSEQUENT),
  977. )
  978. def __init__(self, *args, **keywords):
  979. self.sidbits = {}
  980. self.userbits = {}
  981. policy.Policy.__init__(self, *args, **keywords)
  982. def updateArgs(self, *args, **keywords):
  983. """
  984. setModes(path(s), [sidbits=int], [userbits=int])
  985. """
  986. sidbits = keywords.pop('sidbits', None)
  987. userbits = keywords.pop('userbits', None)
  988. for path in args:
  989. if sidbits is not None:
  990. self.sidbits[path] = sidbits
  991. if userbits is not None:
  992. self.userbits[path] = userbits
  993. self.recipe.WarnWriteable(
  994. exceptions=re.escape(path).replace('%', '%%'),
  995. allowUnusedFilters = True)
  996. policy.Policy.updateArgs(self, **keywords)
  997. def doFile(self, path):
  998. # Don't set modes on capsule files
  999. if self.recipe._getCapsulePathsForFile(path):
  1000. return
  1001. # Skip files that aren't part of the package
  1002. if path not in self.recipe.autopkg.pathMap:
  1003. return
  1004. newmode = oldmode = self.recipe.autopkg.pathMap[path].inode.perms()
  1005. if path in self.userbits:
  1006. newmode = (newmode & 077077) | self.userbits[path]
  1007. if path in self.sidbits and self.sidbits[path]:
  1008. newmode |= self.sidbits[path]
  1009. self.info('suid/sgid: %s mode 0%o', path, newmode & 07777)
  1010. if newmode != oldmode:
  1011. self.recipe.autopkg.pathMap[path].inode.perms.set(newmode)
  1012. class LinkType(policy.Policy):
  1013. """
  1014. NAME
  1015. ====
  1016. B{C{r.LinkType()}} - Ensures only regular, non-configuration files are hardlinked
  1017. SYNOPSIS
  1018. ========
  1019. C{r.LinkType([I{filterexp}])}
  1020. DESCRIPTION
  1021. ===========
  1022. The C{r.LinkType()} policy ensures that only regular, non-configuration
  1023. files are hardlinked.
  1024. EXAMPLES
  1025. ========
  1026. This policy is not called explicitly.
  1027. """
  1028. bucket = policy.PACKAGE_CREATION
  1029. processUnmodified = True
  1030. requires = (
  1031. ('Config', policy.REQUIRED_PRIOR),
  1032. ('PackageSpec', policy.REQUIRED_PRIOR),
  1033. )
  1034. def do(self):
  1035. for component in self.recipe.autopkg.getComponents():
  1036. for path in sorted(component.hardlinkMap.keys()):
  1037. if self.recipe.autopkg.pathMap[path].flags.isConfig():
  1038. self.error("Config file %s has illegal hard links", path)
  1039. for path in component.badhardlinks:
  1040. self.error("Special file %s has illegal hard links", path)
  1041. class LinkCount(policy.Policy):
  1042. """
  1043. NAME
  1044. ====
  1045. B{C{r.LinkCount()}} - Restricts hardlinks across directories.
  1046. SYNOPSIS
  1047. ========
  1048. C{LinkCount([I{filterexp}] | [I{exceptions=filterexp}])}
  1049. DESCRIPTION
  1050. ===========
  1051. The C{r.LinkCount()} policy restricts hardlinks across directories.
  1052. It is generally an error to have hardlinks across directories, except when
  1053. the packager knows that there is no reasonable chance that they will be on
  1054. separate filesystems.
  1055. In cases where the packager is certain hardlinks will not cross
  1056. filesystems, a list of regular expressions specifying files
  1057. which are excepted from this rule may be passed to C{r.LinkCount}.
  1058. EXAMPLES
  1059. ========
  1060. C{r.LinkCount(exceptions='/usr/share/zoneinfo/')}
  1061. Uses C{r.LinkCount} to except zoneinfo files, located in
  1062. C{/usr/share/zoneinfo/}, from the policy against cross-directory
  1063. hardlinks.
  1064. """
  1065. bucket = policy.PACKAGE_CREATION
  1066. processUnmodified = False
  1067. requires = (
  1068. ('PackageSpec', policy.REQUIRED_PRIOR),
  1069. )
  1070. def __init__(self, *args, **keywords):
  1071. policy.Policy.__init__(self, *args, **keywords)
  1072. self.excepts = set()
  1073. def updateArgs(self, *args, **keywords):
  1074. allowUnusedFilters = keywords.pop('allowUnusedFilters', False) or \
  1075. self.allowUnusedFilters
  1076. exceptions = keywords.pop('exceptions', None)
  1077. if exceptions:
  1078. if type(exceptions) is str:
  1079. self.excepts.add(exceptions)
  1080. if not allowUnusedFilters:
  1081. self.unusedFilters['exceptions'].add(exceptions)
  1082. elif type(exceptions) in (tuple, list):
  1083. self.excepts.update(exceptions)
  1084. if not allowUnusedFilters:
  1085. self.unusedFilters['exceptions'].update(exceptions)
  1086. # FIXME: we may want to have another keyword argument
  1087. # that passes information down to the buildpackage
  1088. # that causes link groups to be broken for some
  1089. # directories but not others. We need to research
  1090. # first whether this is useful; it may not be.
  1091. def do(self):
  1092. if self.recipe.getType() == recipe.RECIPE_TYPE_CAPSULE:
  1093. return
  1094. filters = [(x, filter.Filter(x, self.macros)) for x in self.excepts]
  1095. for component in self.recipe.autopkg.getComponents():
  1096. for inode in component.linkGroups:
  1097. # ensure all in same directory, except for directories
  1098. # matching regexps that have been passed in
  1099. allPaths = [x for x in component.linkGroups[inode]]
  1100. for path in allPaths[:]:
  1101. for regexp, f in filters:
  1102. if f.match(path):
  1103. self.unusedFilters['exceptions'].discard(regexp)
  1104. allPaths.remove(path)
  1105. dirSet = set(os.path.dirname(x) + '/' for x in allPaths)
  1106. if len(dirSet) > 1:
  1107. self.error('files %s are hard links across directories %s',
  1108. ', '.join(sorted(component.linkGroups[inode])),
  1109. ', '.join(sorted(list(dirSet))))
  1110. self.error('If these directories cannot reasonably be'
  1111. ' on different filesystems, disable this'
  1112. ' warning by calling'
  1113. " r.LinkCount(exceptions=('%s')) or"
  1114. " equivalent"
  1115. % "', '".join(sorted(list(dirSet))))
  1116. class ExcludeDirectories(policy.Policy):
  1117. """
  1118. NAME
  1119. ====
  1120. B{C{r.ExcludeDirectories()}} - Exclude directories from package
  1121. SYNOPSIS
  1122. ========
  1123. C{r.ExcludeDirectories([I{filterexp}] | [I{exceptions=filterexp}])}
  1124. DESCRIPTION
  1125. ===========
  1126. The C{r.ExcludeDirectories} policy causes directories to be
  1127. excluded from the package by default. Use
  1128. C{r.ExcludeDirectories(exceptions=I{filterexp})} to set exceptions to this
  1129. policy, which will cause directories matching the regular expression
  1130. C{filterexp} to be included in the package. Remember that Conary
  1131. packages cannot share files, including directories, so only one
  1132. package installed on a system at any one time can own the same
  1133. directory.
  1134. There are only three reasons to explicitly package a directory: the
  1135. directory needs permissions other than 0755, it needs non-root owner
  1136. or group, or it must exist even if it is empty.
  1137. Therefore, it should generally not be necessary to invoke this policy
  1138. directly. If your directory requires permissions other than 0755, simply
  1139. use C{r.SetMode} to specify the permissions, and the directory will be
  1140. automatically included. Similarly, if you wish to include an empty
  1141. directory with owner or group information, call C{r.Ownership} on that
  1142. empty directory,
  1143. Because C{r.Ownership} can reasonably be called on an entire
  1144. subdirectory tree and indiscriminately applied to files and
  1145. directories alike, non-empty directories with owner or group
  1146. set will be excluded from packaging unless an exception is
  1147. explicitly provided.
  1148. If you call C{r.Ownership} with a filter that applies to an
  1149. empty directory, but you do not want to package that directory,
  1150. you will have to remove the directory with C{r.Remove}.
  1151. Packages do not need to explicitly include directories to ensure
  1152. existence of a target to place a file in. Conary will appropriately
  1153. create the directory, and delete it later if the directory becomes empty.
  1154. EXAMPLES
  1155. ========
  1156. C{r.ExcludeDirectories(exceptions='/tftpboot')}
  1157. Sets the directory C{/tftboot} as an exception to the
  1158. C{r.ExcludeDirectories} policy, so that the C{/tftpboot}
  1159. directory will be included in the package.
  1160. """
  1161. bucket = policy.PACKAGE_CREATION
  1162. processUnmodified = True
  1163. requires = (
  1164. ('PackageSpec', policy.REQUIRED_PRIOR),
  1165. ('Ownership', policy.REQUIRED_PRIOR),
  1166. ('MakeDevices', policy.CONDITIONAL_PRIOR),
  1167. )
  1168. invariantinclusions = [ ('.*', stat.S_IFDIR) ]
  1169. supported_targets

Large files files are truncated, but you can click here to view the full file