Bug 884587 - Part 1: Perform file removal with purge manifests; r=glandium
This commit is contained in:
@@ -9,6 +9,9 @@ import logging
|
||||
import os
|
||||
import types
|
||||
|
||||
from mozpack.copier import FilePurger
|
||||
from mozpack.manifests import PurgeManifest
|
||||
|
||||
from .base import BuildBackend
|
||||
from ..frontend.data import (
|
||||
ConfigFileSubstitution,
|
||||
@@ -127,6 +130,15 @@ class RecursiveMakeBackend(BuildBackend):
|
||||
self.backend_input_files.add(os.path.join(self.environment.topobjdir,
|
||||
'config', 'autoconf.mk'))
|
||||
|
||||
self._purge_manifests = dict(
|
||||
dist_bin=PurgeManifest(relpath='dist/bin'),
|
||||
dist_include=PurgeManifest(relpath='dist/include'),
|
||||
dist_private=PurgeManifest(relpath='dist/private'),
|
||||
dist_public=PurgeManifest(relpath='dist/public'),
|
||||
dist_sdk=PurgeManifest(relpath='dist/sdk'),
|
||||
tests=PurgeManifest(relpath='_tests'),
|
||||
)
|
||||
|
||||
def _update_from_avoid_write(self, result):
|
||||
existed, updated = result
|
||||
|
||||
@@ -252,6 +264,8 @@ class RecursiveMakeBackend(BuildBackend):
|
||||
self._update_from_avoid_write(mastermanifest.close())
|
||||
self.summary.managed_count += 1
|
||||
|
||||
self._write_purge_manifests()
|
||||
|
||||
def _process_directory_traversal(self, obj, backend_file):
|
||||
"""Process a data.DirectoryTraversal instance."""
|
||||
fh = backend_file.fh
|
||||
@@ -323,3 +337,27 @@ class RecursiveMakeBackend(BuildBackend):
|
||||
if obj.relativedir != '':
|
||||
manifest = '%s/%s' % (obj.relativedir, manifest)
|
||||
self.xpcshell_manifests.append(manifest)
|
||||
|
||||
def _write_purge_manifests(self):
|
||||
# We write out a "manifest" file for each directory that is to be
|
||||
# purged.
|
||||
#
|
||||
# Ideally we have as few manifests as possible - ideally only 1. This
|
||||
# will likely require all build metadata to be in emitted objects.
|
||||
# We're not quite there yet, so we maintain multiple manifests.
|
||||
man_dir = os.path.join(self.environment.topobjdir, '_build_manifests',
|
||||
'purge')
|
||||
|
||||
# We have a purger for the manifests themselves to ensure we don't over
|
||||
# purge if we delete a purge manifest.
|
||||
purger = FilePurger()
|
||||
|
||||
for k, manifest in self._purge_manifests.items():
|
||||
purger.add(k)
|
||||
full = os.path.join(man_dir, k)
|
||||
|
||||
fh = FileAvoidWrite(os.path.join(man_dir, k))
|
||||
manifest.write_fileobj(fh)
|
||||
self._update_from_avoid_write(fh.close())
|
||||
|
||||
purger.purge(man_dir)
|
||||
|
||||
@@ -89,15 +89,17 @@ class BackendTester(unittest.TestCase):
|
||||
config['substs'].append(('top_srcdir', srcdir))
|
||||
return ConfigEnvironment(srcdir, objdir, **config)
|
||||
|
||||
def _emit(self, name):
|
||||
env = self._get_environment(name)
|
||||
def _emit(self, name, env=None):
|
||||
if not env:
|
||||
env = self._get_environment(name)
|
||||
|
||||
reader = BuildReader(env)
|
||||
emitter = TreeMetadataEmitter(env)
|
||||
|
||||
return env, emitter.emit(reader.read_topsrcdir())
|
||||
|
||||
def _consume(self, name, cls):
|
||||
env, objs = self._emit(name)
|
||||
def _consume(self, name, cls, env=None):
|
||||
env, objs = self._emit(name, env=env)
|
||||
backend = cls(env)
|
||||
backend.consume(objs)
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ from __future__ import unicode_literals
|
||||
import os
|
||||
import time
|
||||
|
||||
from mozpack.manifests import PurgeManifest
|
||||
from mozunit import main
|
||||
|
||||
from mozbuild.backend.configenvironment import ConfigEnvironment
|
||||
@@ -269,5 +270,42 @@ class TestRecursiveMakeBackend(BackendTester):
|
||||
'; THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT MODIFY BY HAND.',
|
||||
''] + ['[include:%s/xpcshell.ini]' % x for x in expected])
|
||||
|
||||
def test_purge_manifests_written(self):
|
||||
env = self._consume('stub0', RecursiveMakeBackend)
|
||||
|
||||
purge_dir = os.path.join(env.topobjdir, '_build_manifests', 'purge')
|
||||
self.assertTrue(os.path.exists(purge_dir))
|
||||
|
||||
expected = [
|
||||
'dist_bin',
|
||||
'dist_include',
|
||||
'dist_private',
|
||||
'dist_public',
|
||||
'dist_sdk',
|
||||
'tests',
|
||||
]
|
||||
|
||||
for e in expected:
|
||||
full = os.path.join(purge_dir, e)
|
||||
self.assertTrue(os.path.exists(full))
|
||||
|
||||
m = PurgeManifest.from_path(os.path.join(purge_dir, 'dist_bin'))
|
||||
self.assertEqual(m.relpath, 'dist/bin')
|
||||
|
||||
def test_old_purge_manifest_deleted(self):
|
||||
# Simulate a purge manifest from a previous backend version. Ensure it
|
||||
# is deleted.
|
||||
env = self._get_environment('stub0')
|
||||
purge_dir = os.path.join(env.topobjdir, '_build_manifests', 'purge')
|
||||
manifest_path = os.path.join(purge_dir, 'old_manifest')
|
||||
os.makedirs(purge_dir)
|
||||
m = PurgeManifest()
|
||||
m.write_file(manifest_path)
|
||||
|
||||
self.assertTrue(os.path.exists(manifest_path))
|
||||
self._consume('stub0', RecursiveMakeBackend, env)
|
||||
self.assertFalse(os.path.exists(manifest_path))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
89
python/mozbuild/mozpack/manifests.py
Normal file
89
python/mozbuild/mozpack/manifests.py
Normal file
@@ -0,0 +1,89 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from .copier import FilePurger
|
||||
import mozpack.path as mozpath
|
||||
|
||||
|
||||
class UnreadablePurgeManifest(Exception):
|
||||
"""Error for failure when reading content of a serialized PurgeManifest."""
|
||||
|
||||
|
||||
class PurgeManifest(object):
|
||||
"""Describes actions to be used with a copier.FilePurger instance.
|
||||
|
||||
This class facilitates serialization and deserialization of data used
|
||||
to construct a copier.FilePurger and to perform a purge operation.
|
||||
|
||||
The manifest contains a set of entries (paths that are accounted for and
|
||||
shouldn't be purged) and a relative path. The relative path is optional and
|
||||
can be used to e.g. have several manifest files in a directory be
|
||||
dynamically applied to subdirectories under a common base directory.
|
||||
|
||||
Don't be confused by the name of this class: entries are files that are
|
||||
*not* purged.
|
||||
"""
|
||||
def __init__(self, relpath=''):
|
||||
self.relpath = relpath
|
||||
self.entries = set()
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, PurgeManifest):
|
||||
return False
|
||||
|
||||
return other.relpath == self.relpath and other.entries == self.entries
|
||||
|
||||
@staticmethod
|
||||
def from_path(path):
|
||||
with open(path, 'rt') as fh:
|
||||
return PurgeManifest.from_fileobj(fh)
|
||||
|
||||
@staticmethod
|
||||
def from_fileobj(fh):
|
||||
m = PurgeManifest()
|
||||
|
||||
version = fh.readline().rstrip()
|
||||
if version != '1':
|
||||
raise UnreadablePurgeManifest('Unknown manifest version: ' %
|
||||
version)
|
||||
|
||||
m.relpath = fh.readline().rstrip()
|
||||
|
||||
for entry in fh:
|
||||
m.entries.add(entry.rstrip())
|
||||
|
||||
return m
|
||||
|
||||
def add(self, path):
|
||||
return self.entries.add(path)
|
||||
|
||||
def write_file(self, path):
|
||||
with open(path, 'wt') as fh:
|
||||
return self.write_fileobj(fh)
|
||||
|
||||
def write_fileobj(self, fh):
|
||||
fh.write('1\n')
|
||||
fh.write('%s\n' % self.relpath)
|
||||
|
||||
# We write sorted so written output is consistent.
|
||||
for entry in sorted(self.entries):
|
||||
fh.write('%s\n' % entry)
|
||||
|
||||
def get_purger(self, prepend_relpath=False):
|
||||
"""Obtain a FilePurger instance from this manifest.
|
||||
|
||||
If :prepend_relpath is truish, the relative path in the manifest will
|
||||
be prepended to paths added to the FilePurger. Otherwise, the raw paths
|
||||
will be used.
|
||||
"""
|
||||
p = FilePurger()
|
||||
for entry in self.entries:
|
||||
if prepend_relpath:
|
||||
entry = mozpath.join(self.relpath, entry)
|
||||
|
||||
p.add(entry)
|
||||
|
||||
return p
|
||||
48
python/mozbuild/mozpack/test/test_manifests.py
Normal file
48
python/mozbuild/mozpack/test/test_manifests.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import mozunit
|
||||
|
||||
from mozpack.manifests import (
|
||||
PurgeManifest,
|
||||
UnreadablePurgeManifest,
|
||||
)
|
||||
from mozpack.test.test_files import TestWithTmpDir
|
||||
|
||||
|
||||
class TestPurgeManifest(TestWithTmpDir):
|
||||
def test_construct(self):
|
||||
m = PurgeManifest()
|
||||
self.assertEqual(m.relpath, '')
|
||||
self.assertEqual(len(m.entries), 0)
|
||||
|
||||
def test_serialization(self):
|
||||
m = PurgeManifest(relpath='rel')
|
||||
m.add('foo')
|
||||
m.add('bar')
|
||||
p = self.tmppath('m')
|
||||
m.write_file(p)
|
||||
|
||||
self.assertTrue(os.path.exists(p))
|
||||
|
||||
m2 = PurgeManifest.from_path(p)
|
||||
self.assertEqual(m.relpath, m2.relpath)
|
||||
self.assertEqual(m.entries, m2.entries)
|
||||
self.assertEqual(m, m2)
|
||||
|
||||
def test_unknown_version(self):
|
||||
p = self.tmppath('bad')
|
||||
|
||||
with open(p, 'wt') as fh:
|
||||
fh.write('2\n')
|
||||
fh.write('not relevant')
|
||||
|
||||
with self.assertRaises(UnreadablePurgeManifest):
|
||||
PurgeManifest.from_path(p)
|
||||
|
||||
Reference in New Issue
Block a user