Merge lp:~james-w/pkgme-devportal/executable-executables into lp:pkgme-devportal

Proposed by James Westby
Status: Merged
Approved by: James Westby
Approved revision: 126
Merged at revision: 125
Proposed branch: lp:~james-w/pkgme-devportal/executable-executables
Merge into: lp:pkgme-devportal
Diff against target: 251 lines (+99/-26)
3 files modified
devportalbinary/binary.py (+34/-11)
devportalbinary/metadata.py (+11/-10)
devportalbinary/tests/test_binary.py (+54/-5)
To merge this branch: bzr merge lp:~james-w/pkgme-devportal/executable-executables
Reviewer Review Type Date Requested Status
Jonathan Lange Approve
Review via email: mp+124803@code.launchpad.net

Commit message

Make executables executable.

The executable may not be marked exectuable (e.g. if it was delivered
in a .zip), so we also consider any ELF executables as candidate exectuables.

That will make it more likely we find the right thing, but it won't be
enough to actually make that file exectuable. Therefore we override
dh_fixperms to chmod the file at build time so it will be installed correctly.

We also tell dh_fixperms to not adjust anything in /opt so that it doesn't
remove any needed exectuable bits.

Description of the change

Hi,

This tries to make executable handling more robust and lenient.

It should now work for things delivered as zip files, and will also
do a better job at making sure that any executables stay executable.

It also fixes another instance of the package name bug.

The branch is a bit messy, sorry. I couldn't think of any changes to
make the code more sensible though.

Thanks,

James

To post a comment you must log in.
Revision history for this message
Jonathan Lange (jml) wrote :

Looks good. The only thing I'd consider changing is making the "special casing" a bit less special by making it a loop or something similar.

review: Approve
126. By James Westby

Use a loop for the elements requiring package name. Thanks jml.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'devportalbinary/binary.py'
2--- devportalbinary/binary.py 2012-09-11 17:19:18 +0000
3+++ devportalbinary/binary.py 2012-09-18 14:36:18 +0000
4@@ -59,9 +59,14 @@
5 # the overrides template for a dh_shlibdeps
6 OVERRIDE_DH_SHLIBDEPS_TEMPLATE = """
7 override_dh_shlibdeps:
8-\tdh_shlibdeps -l%s
9+\tdh_shlibdeps -l{ld_search_path}
10 """
11
12+OVERRIDE_DH_FIXPERMS_TEMPLATE = """
13+override_dh_fixperms:
14+\tchmod u+x debian/{package_name}/{executable_path}
15+\tdh_fixperms -X/opt/
16+"""
17
18 # the default extra debian/rules targets
19 DEFAULT_EXTRA_RULES_TARGETS = OVERRIDE_DH_STRIP_TEMPLATE
20@@ -160,16 +165,26 @@
21 return None
22
23
24+def is_elf_exectuable(file_type):
25+ """Whether `file_type` indicates an ELF executable."""
26+ if file_type.startswith('ELF'):
27+ if "executable" in file_type.split(",")[0]:
28+ return True
29+ return False
30+
31+
32 def iter_executables(path):
33 """Iterate through all executable files under 'path'.
34
35 Paths yielded will be relative to 'path'. Directories will *not* be
36- yielded. "Executable" is determined by filesystem permissions.
37+ yielded. A file is considered "executable" if it has the executable
38+ bit set, or if it is an ELF executable.
39 """
40 for root, dirs, files in os.walk(path):
41 for filename in files:
42 file_path = os.path.join(root, filename)
43- if os.access(file_path, os.X_OK):
44+ if (os.access(file_path, os.X_OK)
45+ or is_elf_exectuable(get_file_type(file_path))):
46 yield os.path.relpath(file_path, path)
47
48
49@@ -189,7 +204,7 @@
50 OBJDUMP = '/usr/bin/objdump'
51 # Map the objdump "architecture" value to the dpkg architecture,
52 # objdump report i386 as "i386" but amd64 as "i386:x86-64"
53-OBJDUMP_MAPPING = {
54+OBJDUMP_MAPPING = {
55 "i386:x86-64" : "amd64",
56 }
57
58@@ -319,15 +334,21 @@
59 def guess_extra_debian_rules_targets(
60 package_name,
61 path,
62- embedded_libs_finder=guess_embedded_libs_search_paths):
63+ embedded_libs_finder=guess_embedded_libs_search_paths,
64+ executable_path=None):
65 embedded_paths = embedded_libs_finder(path)
66 extra_targets = DEFAULT_EXTRA_RULES_TARGETS
67 if embedded_paths:
68- base_dir = "$(CURDIR)/debian/%(package_name)s/opt/" \
69- "%(package_name)s/" % { 'package_name' : package_name }
70+ base_dir = "$(CURDIR)/debian/{package_name}/opt/" \
71+ "{package_name}/".format(package_name=package_name)
72 ld_search_path = build_ld_library_search_path(
73 base_dir, embedded_paths)
74- extra_targets += OVERRIDE_DH_SHLIBDEPS_TEMPLATE % ld_search_path
75+ extra_targets += OVERRIDE_DH_SHLIBDEPS_TEMPLATE.format(
76+ ld_search_path=ld_search_path)
77+ if executable_path:
78+ extra_targets += OVERRIDE_DH_FIXPERMS_TEMPLATE.format(
79+ executable_path=executable_path,
80+ package_name=package_name)
81 return extra_targets
82
83
84@@ -401,12 +422,14 @@
85 This file was automatically generated.
86 """ % (maintainer_suffix,)
87
88- def get_extra_targets(self):
89- package_name = self.get_package_name()
90- return guess_extra_debian_rules_targets(package_name, self.path)
91+ def get_extra_targets(self, package_name):
92+ executable_path = self.get_executable(package_name)
93+ return guess_extra_debian_rules_targets(package_name, self.path,
94+ executable_path=executable_path)
95
96 @classmethod
97 def want_with_metadata(cls, path, metadata):
98+ # XXX: should it also check that there is an executable?
99 if list(iter_binaries(path)):
100 return {
101 'score': 10,
102
103=== modified file 'devportalbinary/metadata.py'
104--- devportalbinary/metadata.py 2012-09-17 16:05:32 +0000
105+++ devportalbinary/metadata.py 2012-09-18 14:36:18 +0000
106@@ -353,7 +353,7 @@
107 def get_extra_control_binary_fields(self):
108 return None
109
110- def get_extra_targets(self):
111+ def get_extra_targets(self, package_name):
112 """Return any extra debian/rules targets. """
113 return ""
114
115@@ -537,12 +537,16 @@
116 Distribution,
117 ExplicitCopyright,
118 ExtraControlBinaryFields,
119- ExtraTargets,
120 Maintainer,
121 Section,
122 Version,
123 Homepage,
124 ]
125+ PACKAGE_NAME_ELEMENTS = [
126+ ExtraFiles,
127+ ExtraFilesFromPaths,
128+ ExtraTargets,
129+ ]
130 info = {}
131 for element in COMPULSORY_ELEMENTS:
132 info[element] = self._calculate_info_element(element)
133@@ -550,15 +554,12 @@
134 value = self._calculate_info_element(element)
135 if value:
136 info[element] = value
137- # Special-case ExtraFiles and ExtraFilesFromPaths since they need
138- # PackageName.
139+ # Special-case those elements that need the package name
140 package_name = PackageName.clean(info[PackageName])
141- info[ExtraFiles] = self._calculate_info_element(
142- ExtraFiles, package_name)
143- extra_files_from_paths = self._calculate_info_element(
144- ExtraFilesFromPaths, package_name)
145- if extra_files_from_paths:
146- info[ExtraFilesFromPaths] = extra_files_from_paths
147+ for element in PACKAGE_NAME_ELEMENTS:
148+ value = self._calculate_info_element(element, package_name)
149+ if value:
150+ info[element] = value
151 return info
152
153 @classmethod
154
155=== modified file 'devportalbinary/tests/test_binary.py'
156--- devportalbinary/tests/test_binary.py 2012-09-11 17:19:18 +0000
157+++ devportalbinary/tests/test_binary.py 2012-09-18 14:36:18 +0000
158@@ -30,6 +30,7 @@
159 guess_embedded_libs_search_paths,
160 guess_executable,
161 guess_extra_debian_rules_targets,
162+ is_elf_exectuable,
163 iter_binaries,
164 iter_executables,
165 needed_libraries_from_objdump,
166@@ -69,6 +70,16 @@
167 executables = list(iter_executables(path))
168 self.assertEqual(['some-file'], executables)
169
170+ def test_finds_elf_executable(self):
171+ # ELF executables are considered executables.
172+ executable = get_test_data_file_path('hello', 'hello')
173+ with open(executable, 'rb') as f:
174+ content = f.read()
175+ tree = FileTree({'some-file': {PERMISSIONS: 0644, CONTENT: content}})
176+ path = self.useFixture(tree).path
177+ executables = list(iter_executables(path))
178+ self.assertEqual(['some-file'], executables)
179+
180 def test_no_files_at_all(self):
181 # iter_executables finds no executables if there are no files at all.
182 path = self.useFixture(FileTree({})).path
183@@ -383,21 +394,36 @@
184 conf = load_configuration()
185 self.assertEqual(backend.get_architecture(), "i386")
186
187+ def test_get_extra_targets_makes_executable_executable(self):
188+ package_name = 'package'
189+ executable_path = 'a/binary'
190+ path = self.getUniqueString()
191+ extra_targets = guess_extra_debian_rules_targets(package_name, path,
192+ lambda l: [], executable_path=executable_path)
193+ self.assertIn(
194+ "override_dh_fixperms:"
195+ "\n\tchmod u+x debian/{1}/{0}"
196+ "\n\tdh_fixperms -X/opt/".format(executable_path,
197+ package_name),
198+ extra_targets)
199+
200 def test_get_extra_targets_no_bundled_libs(self):
201 package_name = self.getUniqueString()
202 path = self.getUniqueString()
203 self.assertEqual(
204- guess_extra_debian_rules_targets(package_name, path, lambda l: []),
205- DEFAULT_EXTRA_RULES_TARGETS)
206+ DEFAULT_EXTRA_RULES_TARGETS,
207+ guess_extra_debian_rules_targets(package_name, path, lambda l: []))
208
209 def test_get_extra_targets_bundled_libs(self):
210 package_name = "a-pkgname"
211 path = self.getUniqueString()
212 ld_search_path = ["x86/lib"]
213+ expected_ld_search_path = ("$(CURDIR)/debian/{package_name}"
214+ "/opt/{package_name}/{search_path}").format(package_name=package_name,
215+ search_path=ld_search_path[0])
216 self.assertEqual(
217- DEFAULT_EXTRA_RULES_TARGETS+OVERRIDE_DH_SHLIBDEPS_TEMPLATE % (
218- "$(CURDIR)/debian/%s/opt/%s/%s" % (
219- package_name, package_name, ld_search_path[0])),
220+ DEFAULT_EXTRA_RULES_TARGETS+OVERRIDE_DH_SHLIBDEPS_TEMPLATE.format(
221+ ld_search_path=expected_ld_search_path),
222 guess_extra_debian_rules_targets(
223 package_name, path, lambda l: ld_search_path))
224
225@@ -422,3 +448,26 @@
226 This file was automatically generated.
227 """ % (submitter,)
228 self.assertEqual(expected, backend.get_explicit_copyright())
229+
230+
231+class IsElfExecutableTests(TestCase):
232+
233+ def test_not_elf(self):
234+ self.assertEqual(False, is_elf_exectuable('blah'))
235+
236+ def test_not_executable(self):
237+ self.assertEqual(False,
238+ is_elf_exectuable('ELF 32-bit LSB shared object'))
239+
240+ def test_executable(self):
241+ self.assertEqual(True,
242+ is_elf_exectuable('ELF 32-bit LSB executable'))
243+
244+ def test_executable_with_extra_junk(self):
245+ self.assertEqual(True,
246+ is_elf_exectuable('ELF 32-bit LSB executable, blah'))
247+
248+ def test_not_executable_with_extra_junk(self):
249+ self.assertEqual(False,
250+ is_elf_exectuable(
251+ 'ELF 32-bit LSB shared object, blah executable'))

Subscribers

People subscribed via source and target branches