Permalink
209 lines (168 sloc) 6.96 KB
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright (C) 2016-2017 Canonical Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# 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 contextlib
import logging
import os
from snapcraft.internal import common
logger = logging.getLogger(__name__)
class BasePlugin:
@classmethod
def schema(cls):
"""Return a json-schema for the plugin's properties as a dictionary.
Of importance to plugin authors is the 'properties' keyword and
optionally the 'requires' keyword with a list of required
'properties'.
By default the properties will be that of a standard VCS,
override in custom implementations if required.
"""
return {
'$schema': 'http://json-schema.org/draft-04/schema#',
'type': 'object',
'additionalProperties': False,
'properties': {},
'pull-properties': [],
'build-properties': [],
'required': [],
}
@classmethod
def get_pull_properties(cls):
schema_pull_properties = cls.schema().get('pull-properties', [])
if schema_pull_properties:
logger.warning(
'Use of pull-properties in the schema is deprecated.\n'
'Plugins should now implement get_pull_properties')
return schema_pull_properties
return []
@classmethod
def get_build_properties(cls):
schema_build_properties = cls.schema().get('build-properties', [])
if schema_build_properties:
logger.warning(
'Use of build-properties in the schema is deprecated.\n'
'Plugins should now implement get_build_properties')
return schema_build_properties
return []
@property
def PLUGIN_STAGE_SOURCES(self):
"""Define alternative sources.list."""
return getattr(self, '_PLUGIN_STAGE_SOURCES', [])
@property
def stage_packages(self):
return self._stage_packages
@stage_packages.setter
def stage_packages(self, value):
self._stage_packages = value
def __init__(self, name, options, project=None):
self.name = name
self.build_snaps = []
self.build_packages = []
self._stage_packages = []
with contextlib.suppress(AttributeError):
self._stage_packages = options.stage_packages.copy()
with contextlib.suppress(AttributeError):
self.build_packages = options.build_packages.copy()
with contextlib.suppress(AttributeError):
self.build_snaps = options.build_snaps.copy()
self.project = project
self.options = options
# The remote parts can have a '/' in them to separate the main project
# part with the subparts. This is rather unfortunate as it affects the
# the layout of parts inside the parts directory causing collisions
# between the main project part and its subparts.
part_dir = name.replace('/', '\N{BIG SOLIDUS}')
if project:
self.partdir = os.path.join(project.parts_dir, part_dir)
else:
self.partdir = os.path.join(os.getcwd(), 'parts', part_dir)
self.sourcedir = os.path.join(self.partdir, 'src')
self.installdir = os.path.join(self.partdir, 'install')
self.statedir = os.path.join(self.partdir, 'state')
self.osrepodir = os.path.join(self.partdir, 'ubuntu')
self.build_basedir = os.path.join(self.partdir, 'build')
source_subdir = getattr(self.options, 'source_subdir', None)
if source_subdir:
self.builddir = os.path.join(self.build_basedir, source_subdir)
else:
self.builddir = self.build_basedir
# The API
def pull(self):
"""Pull the source code and/or internal prereqs to build the part."""
pass
def clean_pull(self):
"""Clean the pulled source for this part."""
pass
def build(self):
"""Build the source code retrieved from the pull phase."""
pass
def clean_build(self):
"""Clean the artifacts that resulted from building this part."""
pass
def get_manifest(self):
"""Return the information to record after the build of this part.
:rtype: dict
"""
pass
def snap_fileset(self):
"""Return a list of files to include or exclude in the resulting snap
The staging phase of a plugin's lifecycle may populate many things
into the staging directory in order to succeed in building a
project.
During the stripping phase and in order to have a clean snap, the
plugin can provide additional logic for stripping build components
from the final snap and alleviate the part author from doing so for
repetetive filesets.
These are the rules to honor when creating such list:
- includes can be just listed
- excludes must be preceded by -
For example::
(['bin', 'lib', '-include'])
"""
return ([])
def env(self, root):
"""Return a list with the execution environment for building.
Plugins often need special environment variables exported to the
system for some builds to take place. This is a list of strings
of the form key=value. The parameter root is the path to this part.
:param str root: The root for the part
"""
return []
def enable_cross_compilation(self):
"""Enable cross compilation for the plugin."""
raise NotImplementedError(
'The plugin used by {!r} does not support cross-compiling '
'to a different target architecture'.format(self.name))
@property
def parallel_build_count(self):
"""Number of CPU's to use for building.
Number comes from `project.parallel_build_count` unless the part
has defined `disable-parallel` as `True`.
"""
if getattr(self.options, 'disable_parallel', False):
return 1
else:
return self.project.parallel_build_count
# Helpers
def run(self, cmd, cwd=None, **kwargs):
if not cwd:
cwd = self.builddir
print(' '.join(cmd))
os.makedirs(cwd, exist_ok=True)
return common.run(cmd, cwd=cwd, **kwargs)
def run_output(self, cmd, cwd=None, **kwargs):
if not cwd:
cwd = self.builddir
os.makedirs(cwd, exist_ok=True)
return common.run_output(cmd, cwd=cwd, **kwargs)