Alter prefix for pkg-config to get correct values #620

Merged
merged 7 commits into from Jul 1, 2016
@@ -289,6 +289,21 @@ def get_library_paths(root, arch_triplet):
return [p for p in paths if os.path.exists(p)]
+def get_pkg_config_paths(root, arch_triplet):
+ paths = [
+ os.path.join(root, 'lib', 'pkgconfig'),
+ os.path.join(root, 'lib', arch_triplet, 'pkgconfig'),
+ os.path.join(root, 'usr', 'lib', 'pkgconfig'),
+ os.path.join(root, 'usr', 'lib', arch_triplet, 'pkgconfig'),
+ os.path.join(root, 'usr', 'share', 'pkgconfig'),
+ os.path.join(root, 'usr', 'local', 'lib', 'pkgconfig'),
+ os.path.join(root, 'usr', 'local', 'lib', arch_triplet, 'pkgconfig'),
+ os.path.join(root, 'usr', 'local', 'share', 'pkgconfig'),
+ ]
+
+ return [p for p in paths if os.path.exists(p)]
+
+
def combine_paths(paths, prepend, separator):
paths = ['{}{}'.format(prepend, p) for p in paths]
return separator.join(paths)
@@ -82,7 +82,6 @@ def __init__(self, plugin_name, part_name, properties,
self.snapdir = project_options.snap_dir
parts_dir = project_options.parts_dir
- self.bindir = os.path.join(parts_dir, part_name, 'bin')
self.ubuntudir = os.path.join(parts_dir, part_name, 'ubuntu')
self.statedir = os.path.join(parts_dir, part_name, 'state')
@@ -348,8 +347,16 @@ def stage(self, force=False):
self.notify_part_progress('Staging')
self._organize()
snap_files, snap_dirs = self.migratable_fileset_for('stage')
+
+ def fixup_func(file_path):
+ if os.path.islink(file_path):
+ return
+ if not file_path.endswith('.pc'):
+ return
+ repo.fix_pkg_config(self.stagedir, file_path, self.code.installdir)
+
_migrate_files(snap_files, snap_dirs, self.code.installdir,
- self.stagedir)
+ self.stagedir, fixup_func=fixup_func)
# TODO once `snappy try` is in place we will need to copy
# dependencies here too
@@ -644,7 +651,7 @@ def _migratable_filesets(fileset, srcdir):
def _migrate_files(snap_files, snap_dirs, srcdir, dstdir, missing_ok=False,
- follow_symlinks=False):
+ follow_symlinks=False, fixup_func=lambda *args: None):
for directory in snap_dirs:
os.makedirs(os.path.join(dstdir, directory), exist_ok=True)
@@ -664,6 +671,7 @@ def _migrate_files(snap_files, snap_dirs, srcdir, dstdir, missing_ok=False,
os.remove(dst)
common.link_or_copy(src, dst, follow_symlinks=follow_symlinks)
+ fixup_func(dst)
def _clean_migrated_files(snap_files, snap_dirs, directory):
@@ -14,15 +14,16 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import fileinput
import glob
import itertools
import logging
import os
import platform
import re
-import string
import shutil
import stat
+import string
import subprocess
import urllib
import urllib.request
@@ -180,7 +181,7 @@ def unpack(self, rootdir):
except subprocess.CalledProcessError:
raise UnpackError(pkg)
- _fix_symlinks(rootdir)
+ _fix_artifacts(rootdir)
_fix_xml_tools(rootdir)
_fix_shebangs(rootdir)
@@ -284,7 +285,28 @@ def _setup_apt_cache(rootdir, sources, project_options):
return apt_cache, progress
-def _fix_symlinks(debdir):
+def fix_pkg_config(root, pkg_config_file, prefix_trim=None):
+ """Opens a pkg_config_file and prefixes the prefix with root."""
+ pattern_trim = None
+ if prefix_trim:
+ pattern_trim = re.compile(
+ '^prefix={}(?P<prefix>.*)'.format(prefix_trim))
+ pattern = re.compile('^prefix=(?P<prefix>.*)')
+
+ with fileinput.input(pkg_config_file, inplace=True) as input_file:
+ for line in input_file:
+ match = pattern.search(line)
+ if prefix_trim:
+ match_trim = pattern_trim.search(line)
+ if prefix_trim and match_trim:
+ print('prefix={}{}'.format(root, match_trim.group('prefix')))
+ elif match:
+ print('prefix={}{}'.format(root, match.group('prefix')))
+ else:
+ print(line, end='')
+
+
+def _fix_artifacts(debdir):
'''
Sometimes debs will contain absolute symlinks (e.g. if the relative
path would go all the way to root, they just do absolute). We can't
@@ -299,18 +321,13 @@ def _fix_symlinks(debdir):
for entry in itertools.chain(files, dirs):
path = os.path.join(root, entry)
if os.path.islink(path) and os.path.isabs(os.readlink(path)):
- target = os.path.join(debdir, os.readlink(path)[1:])
- if _skip_link(os.readlink(path)):
- logger.debug('Skipping {}'.format(target))
- continue
- if not os.path.exists(target):
- if not _try_copy_local(path, target):
- continue
- os.remove(path)
- os.symlink(os.path.relpath(target, root), path)
+ _fix_symlink(path, debdir, root)
elif os.path.exists(path):
_fix_filemode(path)
+ if path.endswith('.pc') and not os.path.islink(path):
+ fix_pkg_config(debdir, path)
@didrocks

didrocks Jul 1, 2016

Contributor

Shouldn't you call fixup_func() rather here? (need to match the number of parameters) and move it to common as you are duplicating the checks for pc files + symlinks?

@sergiusens

sergiusens Jul 1, 2016

Collaborator

This is the only comment I have not addressed as I really want to get rid of common

+
def _fix_xml_tools(root):
xml2_config_path = os.path.join(root, 'usr', 'bin', 'xml2-config')
@@ -326,6 +343,17 @@ def _fix_xml_tools(root):
format(root), xslt_config_path])
+def _fix_symlink(path, debdir, root):
+ target = os.path.join(debdir, os.readlink(path)[1:])
+ if _skip_link(os.readlink(path)):
+ logger.debug('Skipping {}'.format(target))
+ return
+ if not os.path.exists(target) and not _try_copy_local(path, target):
+ return
+ os.remove(path)
+ os.symlink(os.path.relpath(target, root), path)
+
+
def _fix_filemode(path):
mode = stat.S_IMODE(os.stat(path, follow_symlinks=False).st_mode)
if mode & 0o4000 or mode & 0o2000:
@@ -278,9 +278,6 @@ def build_env_for_part(self, part, root_part=True):
if root_part:
# this has to come before any {}/usr/bin
- env += _create_pkg_config_override(
- part.bindir, part.installdir, stagedir,
- self._project_options.arch_triplet)
env += part.env(part.installdir)
env += _runtime_env(part.installdir,
self._project_options.arch_triplet)
@@ -378,6 +375,10 @@ def _build_env(root, arch_triplet):
if paths:
env.append(common.format_path_variable(
'LDFLAGS', paths, prepend='-L', separator=' '))
+ paths = common.get_pkg_config_paths(root, arch_triplet)
+ if paths:
+ env.append(common.format_path_variable(
+ 'PKG_CONFIG_PATH', paths, prepend='', separator=':'))
return env
@@ -389,83 +390,6 @@ def _build_env_for_stage(stagedir, arch_triplet):
return env
-_PKG_CONFIG_TEMPLATE = """#!/usr/bin/env python3
-
-import os
-import sys
-from subprocess import check_call, CalledProcessError
-
-real_env = os.environ.copy()
-
-
-def run_pkg_config(args, env):
- check_call(['/usr/bin/pkg-config'] + args, env=env)
-
-
-def get_pkg_env_for(basedir):
- current_env = real_env.copy()
- env = {{}}
- env['PKG_CONFIG_PATH'] = ':'.join([
- '{{basedir}}/lib/pkgconfig',
- '{{basedir}}/lib/{arch_triplet}/pkgconfig',
- '{{basedir}}/usr/lib/pkgconfig',
- '{{basedir}}/usr/lib/{arch_triplet}/pkgconfig',
- '{{basedir}}/usr/share/pkgconfig',
- '{{basedir}}/usr/local/lib/pkgconfig',
- '{{basedir}}/usr/local/lib/{arch_triplet}/pkgconfig',
- '{{basedir}}/usr/local/share/pkgconfig']).format(basedir=basedir)
- env['PKG_CONFIG_SYSROOT_DIR'] = basedir
- env['PKG_CONFIG_LIBDIR'] = ''
-
- current_env.update(env)
-
- return current_env
-
-
-def modules_exist(modules, env):
- try:
- check_call(['/usr/bin/pkg-config', '--exists'] + modules, env=env)
- return True
- except CalledProcessError:
- return False
-
-
-def main():
- args = sys.argv[1:]
- modules = list(filter(lambda x: not x.startswith('-'), args))
-
- env = get_pkg_env_for('{installdir}')
- if modules_exist(modules, env):
- run_pkg_config(args, env)
- return
-
- env = get_pkg_env_for('{stagedir}')
- if modules_exist(modules, env):
- run_pkg_config(args, env)
- return
-
- run_pkg_config(args, env=real_env)
-
-
-if __name__ == '__main__':
- main()
-"""
-
-
-def _create_pkg_config_override(bindir, installdir, stagedir, arch_triplet):
- pkg_config_path = os.path.join(bindir, 'pkg-config')
- os.makedirs(os.path.dirname(pkg_config_path), exist_ok=True)
-
- pkg_config_content = _PKG_CONFIG_TEMPLATE.format(
- installdir=installdir, stagedir=stagedir, arch_triplet=arch_triplet)
-
- with open(pkg_config_path, 'w') as fn:
- fn.write(pkg_config_content)
- os.chmod(pkg_config_path, 0o755)
-
- return ['PATH={}:$PATH'.format(bindir)]
-
-
def _expand_filesets_for(step, properties):
filesets = properties.get('filesets', {})
fileset_for_step = properties.get(step, {})
@@ -31,6 +31,7 @@
import snapcraft
from snapcraft.internal import (
common,
+ lifecycle,
pluginhandler,
states,
)
@@ -518,6 +519,113 @@ def test_migratable_filesets_single_really_really_nested_file(self):
self.assertEqual({'foo', 'foo/bar', 'foo/bar/baz'}, dirs)
+class RealStageTestCase(tests.TestCase):
+
+ def setUp(self):
+ super().setUp()
+ self.make_snapcraft_yaml("""name: pc-file-test
+version: 1.0
+summary: test pkg-config .pc
+description: when the .pc files reach stage the should be reprefixed
+confinement: strict
+
+parts:
+ stage-pc:
+ plugin: nil
+""")
+
+ def test_pc_files_correctly_prefixed(self):
+ pc_file = os.path.join('usr', 'lib', 'pkgconfig', 'granite.pc')
+ stage_pc_install = os.path.join(
+ 'parts', 'stage-pc', 'install', pc_file)
+ stage_pc_stage = os.path.join('stage', pc_file)
+
+ # Run build
+ lifecycle.execute('build', snapcraft.ProjectOptions())
+
+ # Simulate a .pc file was installed
+ os.makedirs(os.path.dirname(stage_pc_install))
+ with open(stage_pc_install, 'w') as f:
+ f.write('prefix=/usr\n')
+ f.write('exec_prefix=${prefix}\n')
+ f.write('libdir=${prefix}/lib\n')
+ f.write('includedir=${prefix}/include\n')
+ f.write('\n')
+ f.write('Name: granite\n')
+ f.write('Description: elementary\'s Application Framework\n')
+ f.write('Version: 0.4\n')
+ f.write('Libs: -L${libdir} -lgranite\n')
+ f.write('Cflags: -I${includedir}/granite\n')
+ f.write('Requires: cairo gee-0.8 glib-2.0 gio-unix-2.0 '
+ 'gobject-2.0\n')
+
+ # Now we stage
+ lifecycle.execute('stage', snapcraft.ProjectOptions())
+
+ with open(stage_pc_stage) as f:
+ pc_file_content = f.read()
+ expected_pc_file_content = """prefix={}/stage/usr
+exec_prefix=${{prefix}}
+libdir=${{prefix}}/lib
+includedir=${{prefix}}/include
+
+Name: granite
+Description: elementary's Application Framework
+Version: 0.4
+Libs: -L${{libdir}} -lgranite
+Cflags: -I${{includedir}}/granite
+Requires: cairo gee-0.8 glib-2.0 gio-unix-2.0 gobject-2.0
+""".format(os.getcwd())
+
+ self.assertEqual(pc_file_content, expected_pc_file_content)
+
+ def test_pc_files_correctly_prefixed_when_installed(self):
+ pc_file = os.path.join('usr', 'lib', 'pkgconfig', 'granite.pc')
+ install_path = os.path.join(
+ os.getcwd(), 'parts', 'stage-pc', 'install')
+ stage_pc_install = os.path.join(install_path, pc_file)
+ stage_pc_stage = os.path.join('stage', pc_file)
+
+ # Run build
+ lifecycle.execute('build', snapcraft.ProjectOptions())
+
+ # Simulate a .pc file was installed
+ os.makedirs(os.path.dirname(stage_pc_install))
+ with open(stage_pc_install, 'w') as f:
+ f.write('prefix={}/usr\n'.format(install_path))
+ f.write('exec_prefix=${prefix}\n')
+ f.write('libdir=${prefix}/lib\n')
+ f.write('includedir=${prefix}/include\n')
+ f.write('\n')
+ f.write('Name: granite\n')
+ f.write('Description: elementary\'s Application Framework\n')
+ f.write('Version: 0.4\n')
+ f.write('Libs: -L${libdir} -lgranite\n')
+ f.write('Cflags: -I${includedir}/granite\n')
+ f.write('Requires: cairo gee-0.8 glib-2.0 gio-unix-2.0 '
+ 'gobject-2.0\n')
+
+ # Now we stage
+ lifecycle.execute('stage', snapcraft.ProjectOptions())
+
+ with open(stage_pc_stage) as f:
+ pc_file_content = f.read()
+ expected_pc_file_content = """prefix={}/stage/usr
+exec_prefix=${{prefix}}
+libdir=${{prefix}}/lib
+includedir=${{prefix}}/include
+
+Name: granite
+Description: elementary's Application Framework
+Version: 0.4
+Libs: -L${{libdir}} -lgranite
+Cflags: -I${{includedir}}/granite
+Requires: cairo gee-0.8 glib-2.0 gio-unix-2.0 gobject-2.0
+""".format(os.getcwd())
+
+ self.assertEqual(pc_file_content, expected_pc_file_content)
+
+
class PluginMakedirsTestCase(tests.TestCase):
scenarios = [
Oops, something went wrong.