Merge ~cjwatson/launchpad:remove-sourcedeps into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 61619cac1ea8f9823ab2677ac4b5b346427bb8b9
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:remove-sourcedeps
Merge into: launchpad:master
Prerequisite: ~cjwatson/launchpad:brz-loom-3.0.0
Diff against target: 1054 lines (+24/-124)
12 files modified
.pre-commit-config.yaml (+0/-4)
Makefile (+7/-8)
dev/null (+0/-3)
doc/explanation/navigating.rst (+0/-5)
doc/explanation/running-details.rst (+0/-1)
lib/lp/scripts/utilities/test.py (+1/-1)
setup.cfg (+1/-1)
utilities/link-external-sourcecode (+4/-27)
utilities/rocketfuel-get (+3/-58)
utilities/rocketfuel-setup (+0/-5)
utilities/snakefood/Makefile (+1/-1)
utilities/update-sourcecode (+7/-10)
Reviewer Review Type Date Requested Status
Jürgen Gmach Approve
Review via email: mp+428729@code.launchpad.net

Commit message

Remove the obsolete sourcedeps mechanism

Description of the change

All our Python dependencies are now either in the virtualenv or symlinked into the virtualenv from system-level packages.

`utilities/update-sourcecode` still exists as a no-op script for the time being, until we've ensured that no deployment machinery runs it any more.

To post a comment you must log in.
Revision history for this message
Jürgen Gmach (jugmac00) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
2index ddb984f..d1b37f1 100644
3--- a/.pre-commit-config.yaml
4+++ b/.pre-commit-config.yaml
5@@ -31,9 +31,7 @@ repos:
6 exclude: |
7 (?x)^(
8 lib/contrib/.*
9- |lib/devscripts/.*
10 |utilities/community-contributions\.py
11- |utilities/update-sourcecode
12 )$
13 - repo: https://github.com/psf/black
14 rev: 22.3.0
15@@ -42,9 +40,7 @@ repos:
16 exclude: |
17 (?x)^(
18 lib/contrib/.*
19- |lib/devscripts/.*
20 |utilities/community-contributions\.py
21- |utilities/update-sourcecode
22 )$
23 - repo: https://github.com/PyCQA/isort
24 rev: 5.9.2
25diff --git a/Makefile b/Makefile
26index af03cce..b2e2be3 100644
27--- a/Makefile
28+++ b/Makefile
29@@ -190,8 +190,8 @@ inplace: build logs clean_logs codehosting-dir
30 .PHONY: build
31 build: compile apidoc jsbuild css_combine
32
33-# Bootstrap download-cache and sourcecode. Useful for CI jobs that want to
34-# set these up from scratch.
35+# Bootstrap download-cache. Useful for CI jobs that want to set this up
36+# from scratch.
37 .PHONY: bootstrap
38 bootstrap:
39 if [ -d download-cache/.git ]; then \
40@@ -199,14 +199,13 @@ bootstrap:
41 else \
42 git clone --depth=1 $(DEPENDENCY_REPO) download-cache; \
43 fi
44- utilities/update-sourcecode
45
46-# LP_SOURCEDEPS_PATH should point to the sourcecode directory, but we
47-# want the parent directory where the download-cache and env directories
48-# are. We re-use the variable that is using for the rocketfuel-get script.
49+# LP_PROJECT_ROOT/LP_SOURCEDEPS_DIR points to the parent directory where the
50+# download-cache and env directories are. We reuse the variables that are
51+# used for the rocketfuel-get script.
52 download-cache:
53-ifdef LP_SOURCEDEPS_PATH
54- utilities/link-external-sourcecode $(LP_SOURCEDEPS_PATH)/..
55+ifneq (,$(LP_PROJECT_ROOT)$(LP_SOURCEDEPS_DIR))
56+ utilities/link-external-sourcecode $(LP_PROJECT_ROOT)/$(LP_SOURCEDEPS_DIR)
57 else
58 @echo "Missing ./download-cache."
59 @echo "Developers: please run utilities/link-external-sourcecode."
60diff --git a/doc/explanation/navigating.rst b/doc/explanation/navigating.rst
61index 277d452..b4fd90f 100644
62--- a/doc/explanation/navigating.rst
63+++ b/doc/explanation/navigating.rst
64@@ -58,10 +58,5 @@ of the ones that come up from time to time.
65 ``brzplugins/``
66 Breezy plugins used in running Launchpad.
67
68-``sourcecode/``
69- A directory into which we symlink branches of some of Launchpad's
70- dependencies that haven't yet been turned into proper Python
71- dependencies.
72-
73 ``zcml/``
74 Various configuration files for the Zope services.
75diff --git a/doc/explanation/running-details.rst b/doc/explanation/running-details.rst
76index aad600b..09c62db 100644
77--- a/doc/explanation/running-details.rst
78+++ b/doc/explanation/running-details.rst
79@@ -52,6 +52,5 @@ Get the code:
80
81 $ git clone https://git.launchpad.net/launchpad
82 $ cd launchpad
83- $ utilities/update-sourcecode
84 $ git clone --depth=1 https://git.launchpad.net/lp-source-dependencies download-cache
85 $ make
86diff --git a/lib/devscripts/__init__.py b/lib/devscripts/__init__.py
87deleted file mode 100644
88index 748fe27..0000000
89--- a/lib/devscripts/__init__.py
90+++ /dev/null
91@@ -1,10 +0,0 @@
92-# Copyright 2009 Canonical Ltd. This software is licensed under the
93-# GNU Affero General Public License version 3 (see the file LICENSE).
94-
95-"""Scripts that are used in developing Launchpad."""
96-
97-import os
98-
99-
100-def get_launchpad_root():
101- return os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
102diff --git a/lib/devscripts/sourcecode.py b/lib/devscripts/sourcecode.py
103deleted file mode 100644
104index 7293245..0000000
105--- a/lib/devscripts/sourcecode.py
106+++ /dev/null
107@@ -1,425 +0,0 @@
108-# Copyright 2009 Canonical Ltd. This software is licensed under the
109-# GNU Affero General Public License version 3 (see the file LICENSE).
110-
111-"""Tools for maintaining the Launchpad source code."""
112-
113-from __future__ import absolute_import, print_function
114-
115-__all__ = [
116- 'interpret_config',
117- 'parse_config_file',
118- 'plan_update',
119-]
120-
121-import errno
122-import json
123-import optparse
124-import os
125-import shutil
126-import sys
127-
128-try:
129- from breezy import ui
130- from breezy.branch import Branch
131- from breezy.errors import (
132- BzrError,
133- IncompatibleRepositories,
134- NotBranchError,
135- )
136- from breezy.plugin import load_plugins
137- from breezy.revisionspec import RevisionSpec
138- from breezy.trace import enable_default_logging, report_exception
139- from breezy.upgrade import upgrade
140- from breezy.workingtree import WorkingTree
141-except ImportError:
142- from bzrlib import ui
143- from bzrlib.branch import Branch
144- from bzrlib.errors import (
145- BzrError,
146- IncompatibleRepositories,
147- NotBranchError,
148- )
149- from bzrlib.plugin import load_plugins
150- from bzrlib.revisionspec import RevisionSpec
151- from bzrlib.trace import enable_default_logging, report_exception
152- from bzrlib.upgrade import upgrade
153- from bzrlib.workingtree import WorkingTree
154-
155-from devscripts import get_launchpad_root
156-
157-
158-def parse_config_file(file_handle):
159- """Parse the source code config file 'file_handle'.
160-
161- :param file_handle: A file-like object containing sourcecode
162- configuration.
163- :return: A sequence of lines of either '[key, value]' or
164- '[key, value, optional]'.
165- """
166- for line in file_handle:
167- if line == '\n' or line.startswith('#'):
168- continue
169- yield line.split()
170-
171-
172-def interpret_config_entry(entry, use_http=False):
173- """Interpret a single parsed line from the config file."""
174- branch_name = entry[0]
175- components = entry[1].split(';revno=')
176- branch_url = components[0]
177- if use_http:
178- branch_url = branch_url.replace('lp:', 'http://bazaar.launchpad.net/')
179- if len(components) == 1:
180- revision = None
181- else:
182- assert len(components) == 2, 'Bad branch URL: ' + entry[1]
183- revision = components[1] or None
184- if len(entry) > 2:
185- assert len(entry) == 3 and entry[2].lower() == 'optional', (
186- 'Bad configuration line: should be space delimited values of '
187- 'sourcecode directory name, branch URL [, "optional"]\n' +
188- ' '.join(entry))
189- optional = True
190- else:
191- optional = False
192- return branch_name, branch_url, revision, optional
193-
194-
195-def load_cache(cache_filename):
196- try:
197- cache_file = open(cache_filename, 'r')
198- except IOError as e:
199- if e.errno == errno.ENOENT:
200- return {}
201- else:
202- raise
203- with cache_file:
204- return json.load(cache_file)
205-
206-
207-def interpret_config(config_entries, public_only, use_http=False):
208- """Interpret a configuration stream, as parsed by 'parse_config_file'.
209-
210- :param configuration: A sequence of parsed configuration entries.
211- :param public_only: If true, ignore private/optional branches.
212- :param use_http: If True, force all branch URLs to use http://
213- :return: A dict mapping the names of the sourcecode dependencies to a
214- 2-tuple of their branches and whether or not they are optional.
215- """
216- config = {}
217- for entry in config_entries:
218- branch_name, branch_url, revision, optional = interpret_config_entry(
219- entry, use_http)
220- if not optional or not public_only:
221- config[branch_name] = (branch_url, revision, optional)
222- return config
223-
224-
225-def _subset_dict(d, keys):
226- """Return a dict that's a subset of 'd', based on the keys in 'keys'."""
227- return {key: d[key] for key in keys}
228-
229-
230-def plan_update(existing_branches, configuration):
231- """Plan the update to existing branches based on 'configuration'.
232-
233- :param existing_branches: A sequence of branches that already exist.
234- :param configuration: A dictionary of sourcecode configuration, such as is
235- returned by `interpret_config`.
236- :return: (new_branches, update_branches, removed_branches), where
237- 'new_branches' are the branches in the configuration that don't exist
238- yet, 'update_branches' are the branches in the configuration that do
239- exist, and 'removed_branches' are the branches that exist locally, but
240- not in the configuration. 'new_branches' and 'update_branches' are
241- dicts of the same form as 'configuration', 'removed_branches' is a
242- set of the same form as 'existing_branches'.
243- """
244- existing_branches = set(existing_branches)
245- config_branches = set(configuration.keys())
246- new_branches = config_branches - existing_branches
247- removed_branches = existing_branches - config_branches
248- update_branches = config_branches.intersection(existing_branches)
249- return (
250- _subset_dict(configuration, new_branches),
251- _subset_dict(configuration, update_branches),
252- removed_branches)
253-
254-
255-def find_branches(directory):
256- """List the directory names in 'directory' that are branches."""
257- branches = []
258- for name in os.listdir(directory):
259- if name in ('.', '..'):
260- continue
261- try:
262- Branch.open(os.path.join(directory, name))
263- branches.append(name)
264- except NotBranchError:
265- pass
266- return branches
267-
268-
269-def get_revision_id(revision, from_branch, tip=False):
270- """Return revision id for a revision number and a branch.
271-
272- If the revision is empty, the revision_id will be None.
273-
274- If ``tip`` is True, the revision value will be ignored.
275- """
276- if not tip and revision:
277- spec = RevisionSpec.from_string(revision)
278- return spec.as_revision_id(from_branch)
279- # else return None
280-
281-
282-def _format_revision_name(revision, tip=False):
283- """Formatting helper to return human-readable identifier for revision.
284-
285- If ``tip`` is True, the revision value will be ignored.
286- """
287- if not tip and revision:
288- return 'revision %s' % (revision,)
289- else:
290- return 'tip'
291-
292-
293-def get_controldir(branch):
294- try:
295- # Breezy
296- return branch.controldir
297- except AttributeError:
298- # Bazaar
299- return branch.bzrdir
300-
301-
302-def get_branches(sourcecode_directory, new_branches,
303- possible_transports=None, tip=False, quiet=False):
304- """Get the new branches into sourcecode."""
305- for project, (branch_url, revision, optional) in new_branches.items():
306- destination = os.path.join(sourcecode_directory, project)
307- try:
308- remote_branch = Branch.open(
309- branch_url, possible_transports=possible_transports)
310- except BzrError:
311- if optional:
312- report_exception(sys.exc_info(), sys.stderr)
313- continue
314- else:
315- raise
316- possible_transports.append(
317- get_controldir(remote_branch).root_transport)
318- if not quiet:
319- print('Getting %s from %s at %s' %
320- (project, branch_url, _format_revision_name(revision, tip)))
321- # If the 'optional' flag is set, then it's a branch that shares
322- # history with Launchpad, so we should share repositories. Otherwise,
323- # we should avoid sharing repositories to avoid format
324- # incompatibilities.
325- force_new_repo = not optional
326- revision_id = get_revision_id(revision, remote_branch, tip)
327- get_controldir(remote_branch).sprout(
328- destination, revision_id=revision_id, create_tree_if_local=True,
329- source_branch=remote_branch, force_new_repo=force_new_repo,
330- possible_transports=possible_transports)
331-
332-
333-def find_stale(updated, cache, sourcecode_directory, quiet):
334- """Find branches whose revision info doesn't match the cache."""
335- new_updated = dict(updated)
336- for project, (branch_url, revision, optional) in updated.items():
337- cache_revision_info = cache.get(project)
338- if cache_revision_info is None:
339- continue
340- cache_revno = cache_revision_info[0]
341- cache_revision_id = cache_revision_info[1].encode('ASCII')
342- if cache_revno != int(revision):
343- continue
344- destination = os.path.join(sourcecode_directory, project)
345- try:
346- branch = Branch.open(destination)
347- except BzrError:
348- continue
349- last_revno, last_revision_id = branch.last_revision_info()
350- if last_revno != cache_revno or last_revision_id != cache_revision_id:
351- continue
352- if not quiet:
353- print('%s is already up to date.' % project)
354- del new_updated[project]
355- return new_updated
356-
357-
358-def update_cache(cache, cache_filename, changed, sourcecode_directory, quiet):
359- """Update the cache with the changed branches."""
360- old_cache = dict(cache)
361- for project, (branch_url, revision, optional) in changed.items():
362- destination = os.path.join(sourcecode_directory, project)
363- branch = Branch.open(destination)
364- last_revno, last_revision_id = branch.last_revision_info()
365- if not isinstance(last_revision_id, str): # Python 3
366- last_revision_id = last_revision_id.decode('ASCII')
367- cache[project] = [last_revno, last_revision_id]
368- if cache == old_cache:
369- return
370- with open(cache_filename, 'w') as cache_file:
371- # XXX cjwatson 2020-01-21: Stop explicitly specifying separators
372- # once we require Python >= 3.4 (where this is the default).
373- json.dump(
374- cache, cache_file, indent=4, separators=(',', ': '),
375- sort_keys=True)
376- if not quiet:
377- print('Cache updated. Please commit "%s".' % cache_filename)
378-
379-
380-def update_branches(sourcecode_directory, update_branches,
381- possible_transports=None, tip=False, quiet=False):
382- """Update the existing branches in sourcecode."""
383- if possible_transports is None:
384- possible_transports = []
385- # XXX: JonathanLange 2009-11-09: Rather than updating one branch after
386- # another, we could instead try to get them in parallel.
387- for project, (branch_url, revision, optional) in update_branches.items():
388- # Update project from branch_url.
389- destination = os.path.join(sourcecode_directory, project)
390- if not quiet:
391- print('Updating %s to %s' %
392- (project, _format_revision_name(revision, tip)))
393- local_tree = WorkingTree.open(destination)
394- try:
395- remote_branch = Branch.open(
396- branch_url, possible_transports=possible_transports)
397- except BzrError:
398- if optional:
399- report_exception(sys.exc_info(), sys.stderr)
400- continue
401- else:
402- raise
403- possible_transports.append(
404- get_controldir(remote_branch).root_transport)
405- revision_id = get_revision_id(revision, remote_branch, tip)
406- try:
407- result = local_tree.pull(
408- remote_branch, stop_revision=revision_id, overwrite=True,
409- possible_transports=possible_transports)
410- except IncompatibleRepositories:
411- # XXX JRV 20100407: Ideally get_controldir(remote_branch)._format
412- # should be passed into upgrade() to ensure the format is the same
413- # locally and remotely. Unfortunately smart server branches
414- # have their _format set to RemoteFormat rather than an actual
415- # format instance.
416- upgrade(destination)
417- # Upgraded, repoen working tree
418- local_tree = WorkingTree.open(destination)
419- result = local_tree.pull(
420- remote_branch, stop_revision=revision_id, overwrite=True,
421- possible_transports=possible_transports)
422- if result.old_revid == result.new_revid:
423- if not quiet:
424- print(' (No change)')
425- else:
426- if result.old_revno < result.new_revno:
427- change = 'Updated'
428- else:
429- change = 'Reverted'
430- if not quiet:
431- print(' (%s from %s to %s)' % (
432- change, result.old_revno, result.new_revno))
433-
434-
435-def remove_branches(sourcecode_directory, removed_branches, quiet=False):
436- """Remove sourcecode that's no longer there."""
437- for project in removed_branches:
438- destination = os.path.join(sourcecode_directory, project)
439- if not quiet:
440- print('Removing %s' % project)
441- try:
442- shutil.rmtree(destination)
443- except OSError:
444- os.unlink(destination)
445-
446-
447-def update_sourcecode(sourcecode_directory, config_filename, cache_filename,
448- public_only, tip, dry_run, quiet=False, use_http=False):
449- """Update the sourcecode."""
450- config_file = open(config_filename)
451- config = interpret_config(
452- parse_config_file(config_file), public_only, use_http)
453- config_file.close()
454- cache = load_cache(cache_filename)
455- if not os.path.exists(sourcecode_directory):
456- os.makedirs(sourcecode_directory)
457- branches = find_branches(sourcecode_directory)
458- new, updated, removed = plan_update(branches, config)
459- possible_transports = []
460- if dry_run:
461- print('Branches to fetch:', new.keys())
462- print('Branches to update:', updated.keys())
463- print('Branches to remove:', list(removed))
464- else:
465- get_branches(
466- sourcecode_directory, new, possible_transports, tip, quiet)
467- updated = find_stale(updated, cache, sourcecode_directory, quiet)
468- update_branches(
469- sourcecode_directory, updated, possible_transports, tip, quiet)
470- changed = dict(updated)
471- changed.update(new)
472- update_cache(
473- cache, cache_filename, changed, sourcecode_directory, quiet)
474- remove_branches(sourcecode_directory, removed, quiet)
475-
476-
477-# XXX: JonathanLange 2009-09-11: By default, the script will operate on the
478-# current checkout. Most people only have symlinks to sourcecode in their
479-# checkouts. This is fine for updating, but breaks for removing (you can't
480-# shutil.rmtree a symlink) and breaks for adding, since it adds the new branch
481-# to the checkout, rather than to the shared sourcecode area. Ideally, the
482-# script would see that the sourcecode directory is full of symlinks and then
483-# follow these symlinks to find the shared source directory. If the symlinks
484-# differ from each other (because of developers fiddling with things), we can
485-# take a survey of all of them, and choose the most popular.
486-
487-
488-def main(args):
489- parser = optparse.OptionParser("usage: %prog [options] [root [conffile]]")
490- parser.add_option(
491- '--public-only', action='store_true',
492- help='Only fetch/update the public sourcecode branches.')
493- parser.add_option(
494- '--tip', action='store_true',
495- help='Ignore revision constraints for all branches and pull tip')
496- parser.add_option(
497- '--dry-run', action='store_true',
498- help='Do nothing, but report what would have been done.')
499- parser.add_option(
500- '--quiet', action='store_true',
501- help="Don't print informational messages.")
502- parser.add_option(
503- '--use-http', action='store_true',
504- help="Force bzr to use http to get the sourcecode branches "
505- "rather than using bzr+ssh.")
506- options, args = parser.parse_args(args)
507- root = get_launchpad_root()
508- if len(args) > 1:
509- sourcecode_directory = args[1]
510- else:
511- sourcecode_directory = os.path.join(root, 'sourcecode')
512- if len(args) > 2:
513- config_filename = args[2]
514- else:
515- config_filename = os.path.join(root, 'utilities', 'sourcedeps.conf')
516- cache_filename = os.path.join(
517- root, 'utilities', 'sourcedeps.cache')
518- if len(args) > 3:
519- parser.error("Too many arguments.")
520- if not options.quiet:
521- print('Sourcecode: %s' % (sourcecode_directory,))
522- print('Config: %s' % (config_filename,))
523- enable_default_logging()
524- # Tell bzr to use the terminal (if any) to show progress bars
525- ui.ui_factory = ui.make_ui_for_terminal(
526- sys.stdin, sys.stdout, sys.stderr)
527- load_plugins()
528- update_sourcecode(
529- sourcecode_directory, config_filename, cache_filename,
530- options.public_only, options.tip, options.dry_run, options.quiet,
531- options.use_http)
532- return 0
533diff --git a/lib/devscripts/tests/__init__.py b/lib/devscripts/tests/__init__.py
534deleted file mode 100644
535index 62d7904..0000000
536--- a/lib/devscripts/tests/__init__.py
537+++ /dev/null
538@@ -1,4 +0,0 @@
539-# Copyright 2009 Canonical Ltd. This software is licensed under the
540-# GNU Affero General Public License version 3 (see the file LICENSE).
541-
542-"""Tests for devscripts."""
543diff --git a/lib/devscripts/tests/test_sourcecode.py b/lib/devscripts/tests/test_sourcecode.py
544deleted file mode 100644
545index e4c122c..0000000
546--- a/lib/devscripts/tests/test_sourcecode.py
547+++ /dev/null
548@@ -1,226 +0,0 @@
549-# Copyright 2009 Canonical Ltd. This software is licensed under the
550-# GNU Affero General Public License version 3 (see the file LICENSE).
551-
552-"""Module docstring goes here."""
553-
554-from __future__ import absolute_import, print_function
555-
556-import io
557-import os
558-import shutil
559-import tempfile
560-import unittest
561-
562-try:
563- from breezy.bzr.bzrdir import BzrDir
564- from breezy.tests import TestCase
565- from breezy.transport import get_transport
566-except ImportError:
567- from bzrlib.bzrdir import BzrDir
568- from bzrlib.tests import TestCase
569- from bzrlib.transport import get_transport
570-
571-from devscripts import get_launchpad_root
572-from devscripts.sourcecode import (
573- find_branches,
574- interpret_config,
575- parse_config_file,
576- plan_update,
577-)
578-
579-
580-class TestParseConfigFile(unittest.TestCase):
581- """Tests for the config file parser."""
582-
583- def makeFile(self, contents):
584- return io.StringIO(contents)
585-
586- def test_empty(self):
587- # Parsing an empty config file returns an empty sequence.
588- empty_file = self.makeFile("")
589- self.assertEqual([], list(parse_config_file(empty_file)))
590-
591- def test_single_value(self):
592- # Parsing a file containing a single key=value pair returns a sequence
593- # containing the (key, value) as a list.
594- config_file = self.makeFile("key value")
595- self.assertEqual(
596- [['key', 'value']], list(parse_config_file(config_file)))
597-
598- def test_comment_ignored(self):
599- # If a line begins with a '#', then its a comment.
600- comment_only = self.makeFile('# foo')
601- self.assertEqual([], list(parse_config_file(comment_only)))
602-
603- def test_optional_value(self):
604- # Lines in the config file can have a third optional entry.
605- config_file = self.makeFile('key value optional')
606- self.assertEqual(
607- [['key', 'value', 'optional']],
608- list(parse_config_file(config_file)))
609-
610- def test_whitespace_stripped(self):
611- # Any whitespace around any of the tokens in the config file are
612- # stripped out.
613- config_file = self.makeFile(' key value optional ')
614- self.assertEqual(
615- [['key', 'value', 'optional']],
616- list(parse_config_file(config_file)))
617-
618-
619-class TestInterpretConfiguration(unittest.TestCase):
620- """Tests for the configuration interpreter."""
621-
622- def test_empty(self):
623- # An empty configuration stream means no configuration.
624- config = interpret_config([], False)
625- self.assertEqual({}, config)
626-
627- def test_key_value(self):
628- # A (key, value) pair without a third optional value is returned in
629- # the configuration as a dictionary entry under 'key' with '(value,
630- # None, False)' as its value.
631- config = interpret_config([['key', 'value']], False)
632- self.assertEqual({'key': ('value', None, False)}, config)
633-
634- def test_key_value_public_only(self):
635- # A (key, value) pair without a third optional value is returned in
636- # the configuration as a dictionary entry under 'key' with '(value,
637- # None, False)' as its value when public_only is true.
638- config = interpret_config([['key', 'value']], True)
639- self.assertEqual({'key': ('value', None, False)}, config)
640-
641- def test_key_value_optional(self):
642- # A (key, value, optional) entry is returned in the configuration as a
643- # dictionary entry under 'key' with '(value, True)' as its value.
644- config = interpret_config([['key', 'value', 'optional']], False)
645- self.assertEqual({'key': ('value', None, True)}, config)
646-
647- def test_key_value_optional_public_only(self):
648- # A (key, value, optional) entry is not returned in the configuration
649- # when public_only is true.
650- config = interpret_config([['key', 'value', 'optional']], True)
651- self.assertEqual({}, config)
652-
653- def test_key_value_revision(self):
654- # A (key, value) pair without a third optional value when the
655- # value has a suffix of ``;revno=[REVISION]`` is returned in the
656- # configuration as a dictionary entry under 'key' with '(value,
657- # None, False)' as its value.
658- config = interpret_config([['key', 'value;revno=45']], False)
659- self.assertEqual({'key': ('value', '45', False)}, config)
660-
661- def test_key_value_revision_with_multiple_revnos_raises_error(self):
662- # A (key, value) pair without a third optional value when the
663- # value has multiple suffixes of ``;revno=[REVISION]`` raises an
664- # error.
665- self.assertRaises(
666- AssertionError,
667- interpret_config, [['key', 'value;revno=45;revno=47']], False)
668-
669- def test_too_many_values(self):
670- # A line with too many values raises an error.
671- self.assertRaises(
672- AssertionError,
673- interpret_config, [['key', 'value', 'optional', 'extra']], False)
674-
675- def test_bad_optional_value(self):
676- # A third value that is not the "optional" string raises an error.
677- self.assertRaises(
678- AssertionError,
679- interpret_config, [['key', 'value', 'extra']], False)
680-
681- def test_use_http(self):
682- # If use_http=True is passed to interpret_config, all lp: branch
683- # URLs will be transformed into http:// URLs.
684- config = interpret_config(
685- [['key', 'lp:~sabdfl/foo/trunk']], False, use_http=True)
686- expected_url = 'http://bazaar.launchpad.net/~sabdfl/foo/trunk'
687- self.assertEqual(expected_url, config['key'][0])
688-
689-
690-class TestPlanUpdate(unittest.TestCase):
691- """Tests for how to plan the update."""
692-
693- def test_trivial(self):
694- # In the trivial case, there are no existing branches and no
695- # configured branches, so there are no branches to add, none to
696- # update, and none to remove.
697- new, existing, removed = plan_update([], {})
698- self.assertEqual({}, new)
699- self.assertEqual({}, existing)
700- self.assertEqual(set(), removed)
701-
702- def test_all_new(self):
703- # If there are no existing branches, then the all of the configured
704- # branches are new, none are existing and none have been removed.
705- new, existing, removed = plan_update([], {'a': ('b', False)})
706- self.assertEqual({'a': ('b', False)}, new)
707- self.assertEqual({}, existing)
708- self.assertEqual(set(), removed)
709-
710- def test_all_old(self):
711- # If there configuration is now empty, but there are existing
712- # branches, then that means all the branches have been removed from
713- # the configuration, none are new and none are updated.
714- new, existing, removed = plan_update(['a', 'b', 'c'], {})
715- self.assertEqual({}, new)
716- self.assertEqual({}, existing)
717- self.assertEqual({'a', 'b', 'c'}, removed)
718-
719- def test_all_same(self):
720- # If the set of existing branches is the same as the set of
721- # non-existing branches, then they all need to be updated.
722- config = {'a': ('b', False), 'c': ('d', True)}
723- new, existing, removed = plan_update(config.keys(), config)
724- self.assertEqual({}, new)
725- self.assertEqual(config, existing)
726- self.assertEqual(set(), removed)
727-
728- def test_smoke_the_default_config(self):
729- # Make sure we can parse, interpret and plan based on the default
730- # config file.
731- root = get_launchpad_root()
732- config_filename = os.path.join(root, 'utilities', 'sourcedeps.conf')
733- config_file = open(config_filename)
734- config = interpret_config(parse_config_file(config_file), False)
735- config_file.close()
736- plan_update([], config)
737-
738-
739-class TestFindBranches(TestCase):
740- """Tests the way that we find branches."""
741-
742- def setUp(self):
743- TestCase.setUp(self)
744- self.disable_directory_isolation()
745-
746- def makeBranch(self, path):
747- transport = get_transport(path)
748- transport.ensure_base()
749- BzrDir.create_branch_convenience(
750- transport.base, possible_transports=[transport])
751-
752- def makeDirectory(self):
753- directory = tempfile.mkdtemp()
754- self.addCleanup(shutil.rmtree, directory)
755- return directory
756-
757- def test_empty_directory_has_no_branches(self):
758- # An empty directory has no branches.
759- empty = self.makeDirectory()
760- self.assertEqual([], list(find_branches(empty)))
761-
762- def test_directory_with_branches(self):
763- # find_branches finds branches in the directory.
764- directory = self.makeDirectory()
765- self.makeBranch('%s/a' % directory)
766- self.assertEqual(['a'], list(find_branches(directory)))
767-
768- def test_ignores_files(self):
769- # find_branches ignores any files in the directory.
770- directory = self.makeDirectory()
771- some_file = open('%s/a' % directory, 'w')
772- some_file.write('hello\n')
773- some_file.close()
774- self.assertEqual([], list(find_branches(directory)))
775diff --git a/lib/lp/scripts/utilities/test.py b/lib/lp/scripts/utilities/test.py
776index f0d5f7f..bb852f0 100755
777--- a/lib/lp/scripts/utilities/test.py
778+++ b/lib/lp/scripts/utilities/test.py
779@@ -179,7 +179,7 @@ defaults = {
780 # Find tests in the tests and ftests directories
781 "tests_pattern": "^f?tests$",
782 "test_path": [os.path.join(config.root, "lib")],
783- "package": ["canonical", "lp", "devscripts", "launchpad_loggerhead"],
784+ "package": ["canonical", "lp", "launchpad_loggerhead"],
785 "layer": ["!(YUIAppServerLayer)"],
786 "require_unique_ids": True,
787 }
788diff --git a/setup.cfg b/setup.cfg
789index e1a8c8f..367bb34 100644
790--- a/setup.cfg
791+++ b/setup.cfg
792@@ -212,7 +212,7 @@ ignore =
793
794 [isort]
795 # database/* have some implicit relative imports.
796-known_first_party = canonical,lp,launchpad_loggerhead,devscripts,fti,replication,preflight,security,upgrade,dbcontroller
797+known_first_party = canonical,lp,launchpad_loggerhead,fti,replication,preflight,security,upgrade,dbcontroller
798 known_pythonpath = _pythonpath
799 line_length = 79
800 sections = FUTURE,PYTHONPATH,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
801diff --git a/utilities/link-external-sourcecode b/utilities/link-external-sourcecode
802index bc2a58b..06af63f 100755
803--- a/utilities/link-external-sourcecode
804+++ b/utilities/link-external-sourcecode
805@@ -1,11 +1,11 @@
806 #!/usr/bin/python3
807 #
808-# Copyright 2009-2017 Canonical Ltd. This software is licensed under the GNU
809+# Copyright 2009-2023 Canonical Ltd. This software is licensed under the GNU
810 # Affero General Public License version 3 (see the file LICENSE).
811
812 import optparse
813 import subprocess
814-from os import curdir, listdir, makedirs, symlink, unlink
815+from os import curdir, symlink, unlink
816 from os.path import abspath, basename, exists, islink, join, realpath, relpath
817 from sys import stderr, stdout
818
819@@ -21,18 +21,6 @@ def get_main_worktree(branch_dir):
820 return None
821
822
823-def gen_missing_files(source, destination):
824- """Generate info on every file in source not in destination.
825-
826- Yields `(source, destination)` tuples.
827- """
828- for name in listdir(source):
829- destination_file = join(destination, name)
830- if not exists(destination_file):
831- source_file = join(source, name)
832- yield source_file, destination_file,
833-
834-
835 def link(source, destination):
836 """Symlink source to destination.
837
838@@ -55,8 +43,8 @@ if __name__ == "__main__":
839 parser = optparse.OptionParser(
840 usage="%prog [options] [parent]",
841 description=(
842- "Add a symlink in <target>/sourcecode for each corresponding "
843- "file in <parent>/sourcecode."
844+ "Add a symlink from <target>/download-cache to "
845+ "<parent>/download-cache."
846 ),
847 epilog=(
848 "Most of the time this does the right thing if run "
849@@ -119,17 +107,6 @@ if __name__ == "__main__":
850 if options.parent is None:
851 parser.error("Parent tree not specified.")
852
853- if not exists(join(options.target, "sourcecode")):
854- makedirs(join(options.target, "sourcecode"))
855-
856- missing_files = gen_missing_files(
857- abspath(join(options.parent, "sourcecode")),
858- abspath(join(options.target, "sourcecode")),
859- )
860-
861- for source, destination in missing_files:
862- link(source, destination)
863-
864 for folder_name in ("download-cache",):
865 source = abspath(join(options.parent, folder_name))
866 destination = abspath(join(options.target, folder_name))
867diff --git a/utilities/rocketfuel-get b/utilities/rocketfuel-get
868index 678ccb6..2c51408 100755
869--- a/utilities/rocketfuel-get
870+++ b/utilities/rocketfuel-get
871@@ -1,24 +1,13 @@
872 #! /bin/bash
873 #
874-# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
875+# Copyright 2009-2022 Canonical Ltd. This software is licensed under the
876 # GNU Affero General Public License version 3 (see the file LICENSE).
877 #
878-# Update your copy of master and the necessary source dependencies, and make
879-# sure all source dependencies are properly linked in to all the branches you
880-# are working on.
881+# Update your copy of master and the necessary source dependencies.
882
883 # Stop if there's an error, and treat unset variables as errors.
884 set -eu
885
886-# A rough measure of how much stuff we can do in parallel.
887-CPU_COUNT="$(grep -Ec '^processor\b' /proc/cpuinfo)"
888-
889-# Helper function to run a child process, indenting stdout to aid
890-# readability.
891-run-child() {
892- "$@" | sed -e "s/^/ /"
893-}
894-
895 # Load local settings.
896 if [ -e "$HOME/.rocketfuel-env.sh" ]
897 then
898@@ -39,7 +28,7 @@ git -C "$LP_TRUNK_PATH" pull
899 FINAL_REV=$(git -C "$LP_TRUNK_PATH" rev-parse HEAD)
900
901 # Make sure our directories are around.
902-mkdir -p "$LP_SOURCEDEPS_PATH" "$YUI_PATH"
903+mkdir -p "$(dirname "$LP_DOWNLOAD_CACHE_PATH")" "$YUI_PATH"
904
905 # Get/update the download cache.
906 if [ -d "$LP_DOWNLOAD_CACHE_PATH" ]
907@@ -49,50 +38,6 @@ else
908 git clone --depth=1 lp:lp-source-dependencies "$LP_DOWNLOAD_CACHE_PATH"
909 fi
910
911-# Add or update sourcepackages.
912-sourcedeps_conf="$(dirname "$0")/sourcedeps.conf"
913-if [ ! -e "$sourcedeps_conf" ]
914-then
915- # Use the global deps which are stable.
916- echo "Could not find $sourcedeps_conf" >&2
917- sourcedeps_conf="$LP_TRUNK_PATH/utilities/sourcedeps.conf"
918-fi
919-
920-echo "Updating sourcecode dependencies in rocketfuel:"
921-run-child \
922- "$LP_TRUNK_PATH/utilities/update-sourcecode" \
923- "$LP_SOURCEDEPS_PATH" "$sourcedeps_conf"
924-
925-# Update the current trees in the repo.
926-echo "Updating sourcecode dependencies in current local branches:"
927-
928-# Find directories among local branches containing "sourcecode" directories.
929-# Prints each as a null-terminated record (since Unix filenames may contain
930-# newlines).
931-find_branches_to_relink() {
932- find "$LP_PROJECT_ROOT" \
933- -mindepth 2 -maxdepth 2 -type d -name sourcecode -printf '%h\0'
934-}
935-
936-# Some setups may have lp-sourcedeps mixed in with the local branches. Echo
937-# stdin to stdout, with these filenames filtered out. Filenames must be
938-# null-terminated on input, and remain null-terminated on output.
939-filter_branches_to_relink() {
940- grep -vz '/lp-sourcedeps$'
941-}
942-
943-# Re-link the sourcecode directories for local branches. Takes the branch
944-# paths from stdin, as null-terminated records.
945-relink_branches() {
946- run-child xargs --no-run-if-empty \
947- --max-procs="${CPU_COUNT}" --max-args=1 --null \
948- "$LP_TRUNK_PATH/utilities/link-external-sourcecode" \
949- --parent "$LP_PROJECT_ROOT/$LP_SOURCEDEPS_DIR" --target
950-}
951-
952-# Actually do it:
953-find_branches_to_relink | filter_branches_to_relink | relink_branches
954-
955
956 # Build launchpad if there were changes.
957 if [ "$FINAL_REV" != "$INITIAL_REV" ]
958diff --git a/utilities/rocketfuel-setup b/utilities/rocketfuel-setup
959index e03e8be..e4e1237 100755
960--- a/utilities/rocketfuel-setup
961+++ b/utilities/rocketfuel-setup
962@@ -190,7 +190,6 @@ of the following steps:
963 $ git clone lp:launchpad
964 $ cd launchpad
965 $ git clone --depth=1 lp:lp-source-dependencies download-cache
966-$ utilities/update-sourcecode
967 $ utilities/launchpad-database-setup
968 $ make schema
969 $ sudo make install
970@@ -220,10 +219,6 @@ LP_TRUNK_NAME=\${LP_TRUNK_NAME:=launchpad}
971 LP_TRUNK_PATH=\$LP_PROJECT_ROOT/\$LP_TRUNK_NAME
972
973 LP_SOURCEDEPS_DIR=\${LP_SOURCEDEPS_DIR:=lp-sourcedeps}
974-LP_SOURCEDEPS_PATH=\$LP_PROJECT_ROOT/\$LP_SOURCEDEPS_DIR/sourcecode
975-
976-# Force tilde expansion
977-LP_SOURCEDEPS_PATH=\$(eval echo \${LP_SOURCEDEPS_PATH})
978 " > "$HOME/.rocketfuel-env.sh"
979 fi
980
981diff --git a/utilities/snakefood/Makefile b/utilities/snakefood/Makefile
982index f3bbec7..06d92cc 100644
983--- a/utilities/snakefood/Makefile
984+++ b/utilities/snakefood/Makefile
985@@ -5,7 +5,7 @@ default: lp-clustered.svg
986 # Generate import dependency graph
987 lp.sfood:
988 sfood -i -u -I $(LIB_DIR)/sqlobject -I $(LIB_DIR)/schoolbell \
989- -I $(LIB_DIR)/devscripts -I $(LIB_DIR)/contrib \
990+ -I $(LIB_DIR)/contrib \
991 -I $(LIB_DIR)/canonical/not-used $(LIB_DIR)/canonical \
992 $(LIB_DIR)/lp 2>/dev/null | grep -v contrib/ \
993 | grep -v sqlobject | egrep -v 'BeautifulSoup|bs4' | grep -v psycopg \
994diff --git a/utilities/sourcedeps.cache b/utilities/sourcedeps.cache
995deleted file mode 100644
996index 9e26dfe..0000000
997--- a/utilities/sourcedeps.cache
998+++ /dev/null
999@@ -1 +0,0 @@
1000-{}
1001\ No newline at end of file
1002diff --git a/utilities/sourcedeps.conf b/utilities/sourcedeps.conf
1003deleted file mode 100644
1004index e37f376..0000000
1005--- a/utilities/sourcedeps.conf
1006+++ /dev/null
1007@@ -1,8 +0,0 @@
1008-# If you're adding items to this file you _must_ use the full path to
1009-# the branch after lp: rather than just trusting that bzr/lp will expand
1010-# the series for you. If you don't, update-sourcecode will break when
1011-# running parallelised tests inside lxc containers.
1012-
1013-#########################################################
1014-#### DEPRECATED. NO NEW ITEMS. NO NO NO NO NO NONONONONO
1015-#########################################################
1016diff --git a/utilities/sourcedeps.filter b/utilities/sourcedeps.filter
1017deleted file mode 100644
1018index d81ca05..0000000
1019--- a/utilities/sourcedeps.filter
1020+++ /dev/null
1021@@ -1,3 +0,0 @@
1022-P *.o
1023-P *.pyc
1024-P *.so
1025diff --git a/utilities/update-sourcecode b/utilities/update-sourcecode
1026index 1f2189a..8ea9e06 100755
1027--- a/utilities/update-sourcecode
1028+++ b/utilities/update-sourcecode
1029@@ -1,18 +1,15 @@
1030-#!/usr/bin/python2 -u
1031+#!/usr/bin/python3
1032 #
1033 # Copyright 2009 Canonical Ltd. This software is licensed under the
1034 # GNU Affero General Public License version 3 (see the file LICENSE).
1035
1036-"""Update the sourcecode managed in sourcecode/."""
1037+"""Update the sourcecode managed in sourcecode/.
1038
1039-import os
1040-import sys
1041-
1042-sys.path.insert(
1043- 0, os.path.join(os.path.dirname(os.path.dirname(__file__)), "lib")
1044-)
1045+This script is now a no-op, and will be removed once no deployment machinery
1046+uses it any more.
1047+"""
1048
1049-from devscripts import sourcecode # noqa: E402
1050+import sys
1051
1052 if __name__ == "__main__":
1053- sys.exit(sourcecode.main(sys.argv))
1054+ sys.exit(0)

Subscribers

People subscribed via source and target branches

to status/vote changes: