plaso-rubanetra/utils/build_dependencies.py

2790 lines
89 KiB
Python
Raw Permalink Normal View History

2020-04-06 16:48:34 +00:00
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2014 The Plaso Project Authors.
# Please see the AUTHORS file for details on individual authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Script to automate creating builds of plaso dependencies."""
import abc
import argparse
import fileinput
import glob
import io
import json
import logging
import os
import platform
import re
import shutil
import subprocess
import sys
import tarfile
import time
import urllib2
try:
import ConfigParser as configparser
except ImportError:
import configparser
# Since os.path.abspath() uses the current working directory (cwd)
# os.path.abspath(__file__) will point to a different location if
# cwd has been changed. Hence we preserve the absolute location of __file__.
__file__ = os.path.abspath(__file__)
# TODO: look into merging functionality with update dependencies script.
class DependencyDefinition(object):
"""Class that implements a dependency definition."""
def __init__(self, name):
"""Initializes the dependency definition.
Args:
name: the name of the dependency.
"""
self.description_long = None
self.description_short = None
self.dpkg_dependencies = None
self.dpkg_name = None
self.download_url = None
self.homepage_url = None
self.maintainer = None
self.name = name
class DependencyDefinitionReader(object):
"""Class that implements a dependency definition reader."""
def _GetConfigValue(self, config_parser, section_name, value_name):
"""Retrieves a value from the config parser.
Args:
config_parser: the configuration parser (instance of ConfigParser).
section_name: the name of the section that contains the value.
value_name: the name of the value.
Returns:
An object containing the value or None if the value does not exists.
"""
try:
return config_parser.get(section_name, value_name)
except configparser.NoOptionError:
return
def Read(self, file_object):
"""Reads dependency definitions.
Args:
file_object: the file-like object to read from.
Yields:
Dependency definitions (instances of DependencyDefinition).
"""
# TODO: replace by:
# config_parser = configparser. ConfigParser(interpolation=None)
config_parser = configparser.RawConfigParser()
config_parser.readfp(file_object)
for section_name in config_parser.sections():
dependency_definition = DependencyDefinition(section_name)
dependency_definition.description_long = self._GetConfigValue(
config_parser, section_name, 'description_long')
dependency_definition.description_short = self._GetConfigValue(
config_parser, section_name, 'description_short')
dependency_definition.dpkg_dependencies = self._GetConfigValue(
config_parser, section_name, 'dpkg_dependencies')
dependency_definition.dpkg_name = self._GetConfigValue(
config_parser, section_name, 'dpkg_name')
dependency_definition.download_url = self._GetConfigValue(
config_parser, section_name, 'download_url')
dependency_definition.homepage_url = self._GetConfigValue(
config_parser, section_name, 'homepage_url')
dependency_definition.maintainer = self._GetConfigValue(
config_parser, section_name, 'maintainer')
# Need at minimum a name and a download URL.
if dependency_definition.name and dependency_definition.download_url:
yield dependency_definition
class DownloadHelper(object):
"""Class that helps in downloading a project."""
def __init__(self):
"""Initializes the download helper."""
super(DownloadHelper, self).__init__()
self._cached_url = u''
self._cached_page_content = ''
def Download(self, project_name, project_version):
"""Downloads the project for a given project name and version.
Args:
project_name: the name of the project.
project_version: the version of the project.
Returns:
The filename if successful also if the file was already downloaded
or None on error.
"""
download_url = self.GetDownloadUrl(project_name, project_version)
if not download_url:
logging.warning(u'Unable to determine download URL for: {0:s}'.format(
project_name))
return
return self.DownloadFile(download_url)
def DownloadFile(self, download_url):
"""Downloads a file from the URL and returns the filename.
The filename is extracted from the last part of the URL.
Args:
download_url: the URL where to download the file.
Returns:
The filename if successful also if the file was already downloaded
or None on error.
"""
_, _, filename = download_url.rpartition(u'/')
if not os.path.exists(filename):
logging.info(u'Downloading: {0:s}'.format(download_url))
url_object = urllib2.urlopen(download_url)
if url_object.code != 200:
return
file_object = open(filename, 'wb')
file_object.write(url_object.read())
file_object.close()
return filename
def DownloadPageContent(self, download_url):
"""Downloads the page content from the URL and caches it.
Args:
download_url: the URL where to download the page content.
Returns:
The page content if successful, None otherwise.
"""
if not download_url:
return
if self._cached_url != download_url:
url_object = urllib2.urlopen(download_url)
if url_object.code != 200:
return
self._cached_page_content = url_object.read()
self._cached_url = download_url
return self._cached_page_content
@abc.abstractmethod
def GetDownloadUrl(self, project_name, project_version):
"""Retrieves the download URL for a given project name and version.
Args:
project_name: the name of the project.
project_version: the version of the project.
Returns:
The download URL of the project or None on error.
"""
@abc.abstractmethod
def GetProjectIdentifier(self, project_name):
"""Retrieves the project identifier for a given project name.
Args:
project_name: the name of the project.
Returns:
The project identifier or None on error.
"""
class GoogleCodeWikiDownloadHelper(DownloadHelper):
"""Class that helps in downloading a wiki-based Google code project."""
def GetLatestVersion(self, project_name):
"""Retrieves the latest version number for a given project name.
Args:
project_name: the name of the project.
Returns:
The a string containing the latest version number or None on error.
"""
download_url = u'https://code.google.com/p/{0:s}/downloads/list'.format(
project_name)
page_content = self.DownloadPageContent(download_url)
if not page_content:
return
# The format of the project download URL is:
# href="//{project name}.googlecode.com/files/
# {project name}-{version}.tar.gz
expression_string = (
u'href="//{0:s}.googlecode.com/files/'
u'{0:s}-([0-9]+[.][0-9]+|[0-9]+[.][0-9]+[.][0-9]+)[.]tar[.]gz').format(
project_name)
matches = re.findall(expression_string, page_content)
if not matches:
return
# Split the version string and convert every digit into an integer.
# A string compare of both version strings will yield an incorrect result.
matches = [map(int, match.split(u'.')) for match in matches]
# Find the latest version number and transform it back into a string.
return u'.'.join([u'{0:d}'.format(digit) for digit in max(matches)])
def GetDownloadUrl(self, project_name, project_version):
"""Retrieves the download URL for a given project name and version.
Args:
project_name: the name of the project.
project_version: the version of the project.
Returns:
The download URL of the project or None on error.
"""
return (
u'https://{0:s}.googlecode.com/files/{0:s}-{1:s}.tar.gz').format(
project_name, project_version)
def GetProjectIdentifier(self, project_name):
"""Retrieves the project identifier for a given project name.
Args:
project_name: the name of the project.
Returns:
The project identifier or None on error.
"""
return u'com.google.code.p.{0:s}'.format(project_name)
class GithubReleasesDownloadHelper(DownloadHelper):
"""Class that helps in downloading a project with GitHub releases."""
def __init__(self, organization):
"""Initializes the download helper.
Args:
organization: the github organization or user name.
"""
super(GithubReleasesDownloadHelper, self).__init__()
self.organization = organization
def GetLatestVersion(self, project_name):
"""Retrieves the latest version number for a given project name.
Args:
project_name: the name of the project.
Returns:
The latest version number or 0 on error.
"""
download_url = u'https://github.com/{0:s}/{1:s}/releases'.format(
self.organization, project_name)
page_content = self.DownloadPageContent(download_url)
if not page_content:
return 0
# The format of the project download URL is:
# /{organization}/{project name}/releases/download/{git tag}/
# {project name}{status-}{version}.tar.gz
# Note that the status is optional and will be: beta, alpha or experimental.
expression_string = (
u'/{0:s}/{1:s}/releases/download/[^/]*/{1:s}-[a-z-]*([0-9]+)'
u'[.]tar[.]gz').format(self.organization, project_name)
matches = re.findall(expression_string, page_content)
if not matches:
return 0
return int(max(matches))
def GetDownloadUrl(self, project_name, project_version):
"""Retrieves the download URL for a given project name and version.
Args:
project_name: the name of the project.
project_version: the version of the project.
Returns:
The download URL of the project or None on error.
"""
download_url = u'https://github.com/{0:s}/{1:s}/releases'.format(
self.organization, project_name)
page_content = self.DownloadPageContent(download_url)
if not page_content:
return
# The format of the project download URL is:
# /{organization}/{project name}/releases/download/{git tag}/
# {project name}{status-}{version}.tar.gz
# Note that the status is optional and will be: beta, alpha or experimental.
expression_string = (
u'/{0:s}/{1:s}/releases/download/[^/]*/{1:s}-[a-z-]*{2!s}'
u'[.]tar[.]gz').format(self.organization, project_name, project_version)
matches = re.findall(expression_string, page_content)
if len(matches) != 1:
# Try finding a match without the status in case the project provides
# multiple versions with a different status.
expression_string = (
u'/{0:s}/{1:s}/releases/download/[^/]*/{1:s}-*{2!s}'
u'[.]tar[.]gz').format(
self.organization, project_name, project_version)
matches = re.findall(expression_string, page_content)
if not matches or len(matches) != 1:
return
return u'https://github.com{0:s}'.format(matches[0])
def GetProjectIdentifier(self, project_name):
"""Retrieves the project identifier for a given project name.
Args:
project_name: the name of the project.
Returns:
The project identifier or None on error.
"""
return u'com.github.{0:s}.{1:s}'.format(self.organization, project_name)
class GoogleDriveDownloadHelper(DownloadHelper):
"""Class that helps in downloading a Google Drive hosted project."""
@abc.abstractmethod
def GetGoogleDriveDownloadsUrl(self, project_name):
"""Retrieves the Google Drive Download URL.
Args:
project_name: the name of the project.
Returns:
The downloads URL or None on error.
"""
def GetLatestVersion(self, project_name):
"""Retrieves the latest version number for a given project name.
Args:
project_name: the name of the project.
Returns:
The latest version number or 0 on error.
"""
download_url = self.GetGoogleDriveDownloadsUrl(project_name)
page_content = self.DownloadPageContent(download_url)
if not page_content:
return 0
# The format of the project download URL is:
# /host/{random string}/{project name}-{status-}{version}.tar.gz
# Note that the status is optional and will be: beta, alpha or experimental.
expression_string = u'/host/[^/]*/{0:s}-[a-z-]*([0-9]+)[.]tar[.]gz'.format(
project_name)
matches = re.findall(expression_string, page_content)
if not matches:
return 0
return int(max(matches))
def GetDownloadUrl(self, project_name, project_version):
"""Retrieves the download URL for a given project name and version.
Args:
project_name: the name of the project.
project_version: the version of the project.
Returns:
The download URL of the project or None on error.
"""
download_url = self.GetGoogleDriveDownloadsUrl(project_name)
page_content = self.DownloadPageContent(download_url)
if not page_content:
return
# The format of the project download URL is:
# /host/{random string}/{project name}-{status-}{version}.tar.gz
# Note that the status is optional and will be: beta, alpha or experimental.
expression_string = u'/host/[^/]*/{0:s}-[a-z-]*{1!s}[.]tar[.]gz'.format(
project_name, project_version)
matches = re.findall(expression_string, page_content)
if len(matches) != 1:
# Try finding a match without the status in case the project provides
# multiple versions with a different status.
expression_string = u'/host/[^/]*/{0:s}-{1!s}[.]tar[.]gz'.format(
project_name, project_version)
matches = re.findall(expression_string, page_content)
if not matches or len(matches) != 1:
return
return u'https://googledrive.com{0:s}'.format(matches[0])
# TODO: Merge with LibyalGithubReleasesDownloadHelper when Google Drive
# support is no longer needed.
# pylint: disable=abstract-method
class LibyalGitHubDownloadHelper(DownloadHelper):
"""Class that helps in downloading a libyal GitHub project."""
def __init__(self):
"""Initializes the download helper."""
super(LibyalGitHubDownloadHelper, self).__init__()
self._download_helper = None
def GetWikiConfigurationSourcePackageUrl(self, project_name):
"""Retrieves the source package URL from the libyal wiki configuration.
Args:
project_name: the name of the project.
Returns:
The source package URL or None on error.
"""
download_url = (
u'https://raw.githubusercontent.com/libyal/{0:s}/master/'
u'{0:s}-wiki.ini').format(project_name)
page_content = self.DownloadPageContent(download_url)
if not page_content:
return
config_parser = configparser.RawConfigParser()
config_parser.readfp(io.BytesIO(page_content))
return json.loads(config_parser.get('source_package', 'url'))
def GetLatestVersion(self, project_name):
"""Retrieves the latest version number for a given project name.
Args:
project_name: the name of the project.
Returns:
The latest version number or 0 on error.
"""
if not self._download_helper:
download_url = self.GetWikiConfigurationSourcePackageUrl(project_name)
if download_url.startswith('https://github.com'):
self._download_helper = LibyalGithubReleasesDownloadHelper()
elif download_url.startswith('https://googledrive.com'):
self._download_helper = LibyalGoogleDriveDownloadHelper(download_url)
return self._download_helper.GetLatestVersion(project_name)
def GetDownloadUrl(self, project_name, project_version):
"""Retrieves the download URL for a given project name and version.
Args:
project_name: the name of the project.
project_version: the version of the project.
Returns:
The download URL of the project or None on error.
"""
if not self._download_helper:
download_url = self.GetWikiConfigurationSourcePackageUrl(project_name)
if download_url.startswith('https://github.com'):
self._download_helper = LibyalGithubReleasesDownloadHelper()
elif download_url.startswith('https://googledrive.com'):
self._download_helper = LibyalGoogleDriveDownloadHelper(download_url)
return self._download_helper.GetDownloadUrl(project_name, project_version)
class LibyalGoogleDriveDownloadHelper(GoogleDriveDownloadHelper):
"""Class that helps in downloading a libyal project with Google Drive."""
def __init__(self, google_drive_url):
"""Initializes the download helper.
Args:
google_drive_url: the project Google Drive URL.
"""
super(LibyalGoogleDriveDownloadHelper, self).__init__()
self._google_drive_url = google_drive_url
def GetGoogleDriveDownloadsUrl(self, project_name):
"""Retrieves the Download URL from the GitHub project page.
Args:
project_name: the name of the project.
Returns:
The downloads URL or None on error.
"""
return self._google_drive_url
class LibyalGithubReleasesDownloadHelper(GithubReleasesDownloadHelper):
"""Class that helps in downloading a libyal project with GitHub releases."""
def __init__(self):
"""Initializes the download helper."""
super(LibyalGithubReleasesDownloadHelper, self).__init__('libyal')
class Log2TimelineGitHubDownloadHelper(GithubReleasesDownloadHelper):
"""Class that helps in downloading a log2timeline GitHub project."""
def __init__(self):
"""Initializes the download helper."""
super(Log2TimelineGitHubDownloadHelper, self).__init__('log2timeline')
class PyPiDownloadHelper(DownloadHelper):
"""Class that helps in downloading a pypi code project."""
def GetLatestVersion(self, project_name):
"""Retrieves the latest version number for a given project name.
Args:
project_name: the name of the project.
Returns:
The a string containing the latest version number or None on error.
"""
# TODO: add support to handle index of packages pages, e.g. for pyparsing.
download_url = u'https://pypi.python.org/pypi/{0:s}'.format(project_name)
page_content = self.DownloadPageContent(download_url)
if not page_content:
return
# The format of the project download URL is:
# https://pypi.python.org/packages/source/{first letter project name}/
# {project name}/{project name}-{version}.tar.gz
expression_string = (
u'https://pypi.python.org/packages/source/{0:s}/{1:s}/'
u'{1:s}-([0-9]+[.][0-9]+|[0-9]+[.][0-9]+[.][0-9]+)[.]tar[.]gz').format(
project_name[0], project_name)
matches = re.findall(expression_string, page_content)
if not matches:
return
# Split the version string and convert every digit into an integer.
# A string compare of both version strings will yield an incorrect result.
matches = [map(int, match.split(u'.')) for match in matches]
# Find the latest version number and transform it back into a string.
return u'.'.join([u'{0:d}'.format(digit) for digit in max(matches)])
def GetDownloadUrl(self, project_name, project_version):
"""Retrieves the download URL for a given project name and version.
Args:
project_name: the name of the project.
project_version: the version of the project.
Returns:
The download URL of the project or None on error.
"""
return (
u'https://pypi.python.org/packages/source/{0:s}/{1:s}/'
u'{1:s}-{2:s}.tar.gz').format(
project_name[0], project_name, project_version)
def GetProjectIdentifier(self, project_name):
"""Retrieves the project identifier for a given project name.
Args:
project_name: the name of the project.
Returns:
The project identifier or None on error.
"""
return u'org.python.pypi.{0:s}'.format(project_name)
class SourceForgeDownloadHelper(DownloadHelper):
"""Class that helps in downloading a Source Forge project."""
def GetLatestVersion(self, project_name):
"""Retrieves the latest version number for a given project name.
Args:
project_name: the name of the project.
Returns:
The a string containing the latest version number or None on error.
"""
# TODO: make this more robust to detect different naming schemes.
download_url = 'http://sourceforge.net/projects/{0:s}/files/{0:s}/'.format(
project_name)
page_content = self.DownloadPageContent(download_url)
if not page_content:
return 0
# The format of the project download URL is:
# /projects/{project name}/files/{project name}/{project name}-{version}/
expression_string = (
'<a href="/projects/{0:s}/files/{0:s}/'
'{0:s}-([0-9]+[.][0-9]+[.][0-9]+)/"').format(project_name)
matches = re.findall(expression_string, page_content)
if not matches:
return 0
numeric_matches = [''.join(match.split('.')) for match in matches]
return matches[numeric_matches.index(max(numeric_matches))]
def GetDownloadUrl(self, project_name, project_version):
"""Retrieves the download URL for a given project name and version.
Args:
project_name: the name of the project.
project_version: the version of the project.
Returns:
The download URL of the project or None on error.
"""
download_url = (
'http://downloads.sourceforge.net/project/{0:s}/{0:s}/{0:s}-{1:s}'
'/{0:s}-{1:s}.tar.gz').format(project_name, project_version)
return self.DownloadFile(download_url)
def GetProjectIdentifier(self, project_name):
"""Retrieves the project identifier for a given project name.
Args:
project_name: the name of the project.
Returns:
The project identifier or None on error.
"""
return u'net.sourceforge.projects.{0:s}'.format(project_name)
class SourceHelper(object):
"""Base class that helps in managing the source code."""
def __init__(self, project_name):
"""Initializes the source helper.
Args:
project_name: the name of the project.
"""
super(SourceHelper, self).__init__()
self.project_name = project_name
@abc.abstractmethod
def Create(self):
"""Creates the source directory.
Returns:
The name of the source directory if successful or None on error.
"""
@abc.abstractmethod
def GetProjectIdentifier(self):
"""Retrieves the project identifier for a given project name.
Returns:
The project identifier or None on error.
"""
class SourcePackageHelper(SourceHelper):
"""Class that manages the source code from a source package."""
ENCODING = 'utf-8'
def __init__(self, download_helper, project_name):
"""Initializes the source package helper.
Args:
download_helper: the download helper (instance of DownloadHelper).
project_name: the name of the project.
"""
super(SourcePackageHelper, self).__init__(project_name)
self._download_helper = download_helper
self._project_version = None
self._source_filename = None
@property
def project_version(self):
"""The project version."""
if not self._project_version:
self._project_version = self._download_helper.GetLatestVersion(
self.project_name)
return self._project_version
def Clean(self):
"""Removes previous versions of source packages and directories."""
if not self.project_version:
return
filenames_to_ignore = re.compile(
u'^{0:s}-.*{1!s}'.format(self.project_name, self.project_version))
# Remove previous versions of source packages in the format:
# library-*.tar.gz
filenames = glob.glob(u'{0:s}-*.tar.gz'.format(self.project_name))
for filename in filenames:
if not filenames_to_ignore.match(filename):
logging.info(u'Removing: {0:s}'.format(filename))
os.remove(filename)
# Remove previous versions of source directories in the format:
# library-{version}
filenames = glob.glob(u'{0:s}-*'.format(self.project_name))
for filename in filenames:
if os.path.isdir(filename) and not filenames_to_ignore.match(filename):
logging.info(u'Removing: {0:s}'.format(filename))
shutil.rmtree(filename)
def Create(self):
"""Creates the source directory from the source package.
Returns:
The name of the source directory if successful or None on error.
"""
if not self._source_filename:
_ = self.Download()
if not self._source_filename or not os.path.exists(self._source_filename):
return
archive = tarfile.open(self._source_filename, 'r:gz', encoding='utf-8')
directory_name = ''
for tar_info in archive.getmembers():
filename = getattr(tar_info, 'name', None)
try:
filename = filename.decode(self.ENCODING)
except UnicodeDecodeError:
logging.warning(
u'Unable to decode filename in tar file: {0:s}'.format(
self._source_filename))
continue
if filename is None:
logging.warning(u'Missing filename in tar file: {0:s}'.format(
self._source_filename))
continue
if not directory_name:
# Note that this will set directory name to an empty string
# if filename start with a /.
directory_name, _, _ = filename.partition(u'/')
if not directory_name or directory_name.startswith(u'..'):
logging.error(
u'Unsuppored directory name in tar file: {0:s}'.format(
self._source_filename))
return
if os.path.exists(directory_name):
break
logging.info(u'Extracting: {0:s}'.format(self._source_filename))
elif not filename.startswith(directory_name):
logging.warning(
u'Skipping: {0:s} in tar file: {1:s}'.format(
filename, self._source_filename))
continue
archive.extract(tar_info)
archive.close()
return directory_name
def Download(self):
"""Downloads the source package.
Returns:
The filename of the source package if successful also if the file was
already downloaded or None on error.
"""
if not self._source_filename:
if not self.project_version:
return
self._source_filename = self._download_helper.Download(
self.project_name, self.project_version)
return self._source_filename
def GetProjectIdentifier(self):
"""Retrieves the project identifier for a given project name.
Returns:
The project identifier or None on error.
"""
return self._download_helper.GetProjectIdentifier(self.project_name)
class PythonModuleDpkgBuildFilesGenerator(object):
"""Class that helps in generating dpkg build files for Python modules."""
_EMAIL_ADDRESS = u'Log2Timeline <log2timeline-dev@googlegroups.com>'
_DOCS_FILENAMES = [
u'CHANGES', u'CHANGES.txt', u'CHANGES.TXT',
u'LICENSE', u'LICENSE.txt', u'LICENSE.TXT',
u'README', u'README.txt', u'README.TXT']
_CHANGELOG_TEMPLATE = u'\n'.join([
u'python-{project_name:s} ({project_version!s}-1) unstable; urgency=low',
u'',
u' * Auto-generated',
u'',
u' -- {maintainer_email_address:s} {date_time:s}'])
_COMPAT_TEMPLATE = u'\n'.join([
u'7'])
_CONTROL_TEMPLATE = u'\n'.join([
u'Source: python-{project_name:s}',
u'Section: misc',
u'Priority: extra',
u'Maintainer: {upstream_maintainer:s}',
u'Build-Depends: debhelper (>= 7), python, python-setuptools',
u'Standards-Version: 3.8.3',
u'Homepage: {upstream_homepage:s}',
u'',
u'Package: python-{project_name:s}',
u'Section: python',
u'Architecture: all',
u'Depends: {depends:s}',
u'Description: {description_short:s}',
u' {description_long:s}',
u''])
_COPYRIGHT_TEMPLATE = u'\n'.join([
u''])
_RULES_TEMPLATE = u'\n'.join([
u'#!/usr/bin/make -f',
u'# debian/rules that uses debhelper >= 7.',
u'',
u'# Uncomment this to turn on verbose mode.',
u'#export DH_VERBOSE=1',
u'',
u'# This has to be exported to make some magic below work.',
u'export DH_OPTIONS',
u'',
u'',
u'%:',
u' dh $@',
u'',
u'override_dh_auto_clean:',
u'',
u'override_dh_auto_test:',
u'',
u'override_dh_installmenu:',
u'',
u'override_dh_installmime:',
u'',
u'override_dh_installmodules:',
u'',
u'override_dh_installlogcheck:',
u'',
u'override_dh_installlogrotate:',
u'',
u'override_dh_installpam:',
u'',
u'override_dh_installppp:',
u'',
u'override_dh_installudev:',
u'',
u'override_dh_installwm:',
u'',
u'override_dh_installxfonts:',
u'',
u'override_dh_gconf:',
u'',
u'override_dh_icons:',
u'',
u'override_dh_perl:',
u'',
u'override_dh_pysupport:',
u''])
def __init__(
self, project_name, project_version, dependency_definition):
"""Initializes the dpkg build files generator.
Args:
project_name: the name of the project.
project_version: the version of the project.
dependency_definition: the dependency definition object (instance of
DependencyDefinition).
"""
super(PythonModuleDpkgBuildFilesGenerator, self).__init__()
self._project_name = project_name
self._project_version = project_version
self._dependency_definition = dependency_definition
def _GenerateChangelogFile(self, dpkg_path):
"""Generate the dpkg build changelog file.
Args:
dpkg_path: the path to the dpkg files.
"""
timezone_minutes, _ = divmod(time.timezone, 60)
timezone_hours, timezone_minutes = divmod(timezone_minutes, 60)
# If timezone_hours is -1 {0:02d} will format as -1 instead of -01
# hence we detect the sign and force a leading zero.
if timezone_hours < 0:
timezone_string = u'-{0:02d}{1:02d}'.format(
-timezone_hours, timezone_minutes)
else:
timezone_string = u'+{0:02d}{1:02d}'.format(
timezone_hours, timezone_minutes)
date_time_string = u'{0:s} {1:s}'.format(
time.strftime('%a, %d %b %Y %H:%M:%S'), timezone_string)
if self._dependency_definition.dpkg_name:
project_name = self._dependency_definition.dpkg_name
else:
project_name = self._project_name
template_values = {
'project_name': project_name,
'project_version': self._project_version,
'maintainer_email_address': self._EMAIL_ADDRESS,
'date_time': date_time_string}
filename = os.path.join(dpkg_path, u'changelog')
with open(filename, 'wb') as file_object:
data = self._CHANGELOG_TEMPLATE.format(**template_values)
file_object.write(data.encode('utf-8'))
def _GenerateCompatFile(self, dpkg_path):
"""Generate the dpkg build compat file.
Args:
dpkg_path: the path to the dpkg files.
"""
filename = os.path.join(dpkg_path, u'compat')
with open(filename, 'wb') as file_object:
data = self._COMPAT_TEMPLATE
file_object.write(data.encode('utf-8'))
def _GenerateControlFile(self, dpkg_path):
"""Generate the dpkg build control file.
Args:
dpkg_path: the path to the dpkg files.
"""
if self._dependency_definition.dpkg_name:
project_name = self._dependency_definition.dpkg_name
else:
project_name = self._project_name
depends = []
if self._dependency_definition.dpkg_dependencies:
depends.append(self._dependency_definition.dpkg_dependencies)
depends.append('${{shlibs:Depends}}')
depends.append('${{python:Depends}}')
depends = u', '.join(depends)
template_values = {
'project_name': project_name,
'upstream_maintainer': self._dependency_definition.maintainer,
'upstream_homepage': self._dependency_definition.homepage_url,
'depends': depends,
'description_short': self._dependency_definition.description_short,
'description_long': self._dependency_definition.description_long}
filename = os.path.join(dpkg_path, u'control')
with open(filename, 'wb') as file_object:
data = self._CONTROL_TEMPLATE.format(**template_values)
file_object.write(data.encode('utf-8'))
def _GenerateCopyrightFile(self, dpkg_path):
"""Generate the dpkg build copyright file.
Args:
dpkg_path: the path to the dpkg files.
"""
license_file = os.path.join(
os.path.dirname(os.path.dirname(__file__)), u'config', u'licenses',
u'LICENSE.{0:s}'.format(self._project_name))
filename = os.path.join(dpkg_path, u'copyright')
shutil.copy(license_file, filename)
def _GenerateDocsFile(self, dpkg_path):
"""Generate the dpkg build .docs file.
Args:
dpkg_path: the path to the dpkg files.
"""
if self._dependency_definition.dpkg_name:
project_name = self._dependency_definition.dpkg_name
else:
project_name = self._project_name
# Determine the available doc files.
doc_files = []
for filename in self._DOCS_FILENAMES:
if os.path.exists(filename):
doc_files.append(filename)
filename = os.path.join(
dpkg_path, u'python-{0:s}.docs'.format(project_name))
with open(filename, 'wb') as file_object:
file_object.write(u'\n'.join(doc_files))
def _GenerateRulesFile(self, dpkg_path):
"""Generate the dpkg build rules file.
Args:
dpkg_path: the path to the dpkg files.
"""
filename = os.path.join(dpkg_path, u'rules')
with open(filename, 'wb') as file_object:
data = self._RULES_TEMPLATE
file_object.write(data.encode('utf-8'))
def GenerateFiles(self, dpkg_path):
"""Generate the dpkg build files.
Args:
dpkg_path: the path to the dpkg files.
"""
os.mkdir(dpkg_path)
self._GenerateChangelogFile(dpkg_path)
self._GenerateCompatFile(dpkg_path)
self._GenerateControlFile(dpkg_path)
self._GenerateCopyrightFile(dpkg_path)
self._GenerateDocsFile(dpkg_path)
self._GenerateRulesFile(dpkg_path)
class BuildHelper(object):
"""Base class that helps in building."""
LOG_FILENAME = u'build.log'
def __init__(self, dependency_definition):
"""Initializes the build helper.
Args:
dependency_definition: the dependency definition object (instance of
DependencyDefinition).
"""
super(BuildHelper, self).__init__()
self._dependency_definition = dependency_definition
class DpkgBuildHelper(BuildHelper):
"""Class that helps in building dpkg packages (.deb)."""
# TODO: determine BUILD_DEPENDENCIES from the build files?
# TODO: what about flex, byacc?
_BUILD_DEPENDENCIES = frozenset([
'git',
'build-essential',
'autotools-dev',
'autoconf',
'automake',
'autopoint',
'libtool',
'gettext',
'debhelper',
'fakeroot',
'quilt',
'zlib1g-dev',
'libbz2-dev',
'libssl-dev',
'libfuse-dev',
'python-dev',
'python-setuptools',
'libsqlite3-dev',
])
def _BuildPrepare(self, source_directory):
"""Make the necassary preperations before building the dpkg packages.
Args:
source_directory: the name of the source directory.
Returns:
True if the preparations were successful, False otherwise.
"""
# Script to run before building, e.g. to change the dpkg build files.
if os.path.exists(u'prep-dpkg.sh'):
command = u'sh ../prep-dpkg.sh'
exit_code = subprocess.call(
u'(cd {0:s} && {1:s})'.format(source_directory, command), shell=True)
if exit_code != 0:
logging.error(u'Running: "{0:s}" failed.'.format(command))
return False
return True
def _BuildFinalize(self, source_directory):
"""Make the necassary finalizations after building the dpkg packages.
Args:
source_directory: the name of the source directory.
Returns:
True if the finalizations were successful, False otherwise.
"""
# Script to run after building, e.g. to automatically upload
# the dpkg package files to an apt repository.
if os.path.exists(u'post-dpkg.sh'):
command = u'sh ../post-dpkg.sh'
exit_code = subprocess.call(
u'(cd {0:s} && {1:s})'.format(source_directory, command), shell=True)
if exit_code != 0:
logging.error(u'Running: "{0:s}" failed.'.format(command))
return False
return True
@classmethod
def CheckBuildDependencies(cls):
"""Checks if the build dependencies are met.
Returns:
A list of package names that need to be installed or an empty list.
"""
missing_packages = []
for package_name in cls._BUILD_DEPENDENCIES:
if not cls.CheckIsInstalled(package_name):
missing_packages.append(package_name)
return missing_packages
@classmethod
def CheckIsInstalled(cls, package_name):
"""Checks if a package is installed.
Args:
package_name: the name of the package.
Returns:
A boolean value containing true if the package is installed
false otherwise.
"""
command = u'dpkg-query -l {0:s} >/dev/null 2>&1'.format(package_name)
exit_code = subprocess.call(command, shell=True)
return exit_code == 0
class LibyalDpkgBuildHelper(DpkgBuildHelper):
"""Class that helps in building libyal dpkg packages (.deb)."""
def __init__(self, dependency_definition):
"""Initializes the build helper.
Args:
dependency_definition: the dependency definition object (instance of
DependencyDefinition).
"""
super(LibyalDpkgBuildHelper, self).__init__(dependency_definition)
self.architecture = platform.machine()
if self.architecture == 'i686':
self.architecture = 'i386'
elif self.architecture == 'x86_64':
self.architecture = 'amd64'
def Build(self, source_helper):
"""Builds the dpkg packages.
Args:
source_helper: the source helper (instance of SourceHelper).
Returns:
True if the build was successful, False otherwise.
"""
source_filename = source_helper.Download()
logging.info(u'Building deb of: {0:s}'.format(source_filename))
source_directory = source_helper.Create()
if not source_directory:
logging.error(
u'Extraction of source package: {0:s} failed'.format(source_filename))
return False
dpkg_directory = os.path.join(source_directory, u'dpkg')
if not os.path.exists(dpkg_directory):
dpkg_directory = os.path.join(source_directory, u'config', u'dpkg')
if not os.path.exists(dpkg_directory):
logging.error(u'Missing dpkg sub directory in: {0:s}'.format(
source_directory))
return False
debian_directory = os.path.join(source_directory, u'debian')
# If there is a debian directory remove it and recreate it from
# the dpkg directory.
if os.path.exists(debian_directory):
logging.info(u'Removing: {0:s}'.format(debian_directory))
shutil.rmtree(debian_directory)
shutil.copytree(dpkg_directory, debian_directory)
if not self._BuildPrepare(source_directory):
return False
command = u'dpkg-buildpackage -uc -us -rfakeroot > {0:s} 2>&1'.format(
os.path.join(u'..', self.LOG_FILENAME))
exit_code = subprocess.call(
u'(cd {0:s} && {1:s})'.format(source_directory, command), shell=True)
if exit_code != 0:
logging.error(u'Running: "{0:s}" failed.'.format(command))
return False
if not self._BuildFinalize(source_directory):
return False
return True
def Clean(self, source_helper):
"""Cleans the dpkg packages in the current directory.
Args:
source_helper: the source helper (instance of SourceHelper).
"""
filenames_to_ignore = re.compile(u'^{0:s}[-_].*{1!s}'.format(
source_helper.project_name, source_helper.project_version))
# Remove files of previous versions in the format:
# library[-_]version-1_architecture.*
filenames = glob.glob(
u'{0:s}[-_]*[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-1_'
u'{1:s}.*'.format(source_helper.project_name, self.architecture))
for filename in filenames:
if not filenames_to_ignore.match(filename):
logging.info(u'Removing: {0:s}'.format(filename))
os.remove(filename)
# Remove files of previous versions in the format:
# library[-_]*version-1.*
filenames = glob.glob(
u'{0:s}[-_]*[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-1.*'.format(
source_helper.project_name))
for filename in filenames:
if not filenames_to_ignore.match(filename):
logging.info(u'Removing: {0:s}'.format(filename))
os.remove(filename)
def GetOutputFilename(self, source_helper):
"""Retrieves the filename of one of the resulting files.
Args:
source_helper: the source helper (instance of SourceHelper).
Returns:
A filename of one of the resulting dpkg packages.
"""
return u'{0:s}_{1!s}-1_{2:s}.deb'.format(
source_helper.project_name, source_helper.project_version,
self.architecture)
class PythonModuleDpkgBuildHelper(DpkgBuildHelper):
"""Class that helps in building python module dpkg packages (.deb)."""
def Build(self, source_helper):
"""Builds the dpkg packages.
Args:
source_helper: the source helper (instance of SourceHelper).
Returns:
True if the build was successful, False otherwise.
"""
source_filename = source_helper.Download()
logging.info(u'Building deb of: {0:s}'.format(source_filename))
source_directory = source_helper.Create()
if not source_directory:
logging.error(
u'Extraction of source package: {0:s} failed'.format(source_filename))
return False
dpkg_directory = os.path.join(source_directory, u'dpkg')
if not os.path.exists(dpkg_directory):
dpkg_directory = os.path.join(source_directory, u'config', u'dpkg')
if not os.path.exists(dpkg_directory):
# Generate the dpkg build files if necessary.
os.chdir(source_directory)
build_files_generator = PythonModuleDpkgBuildFilesGenerator(
source_helper.project_name, source_helper.project_version,
self._dependency_definition)
build_files_generator.GenerateFiles(u'dpkg')
os.chdir(u'..')
dpkg_directory = os.path.join(source_directory, u'dpkg')
if not os.path.exists(dpkg_directory):
logging.error(u'Missing dpkg sub directory in: {0:s}'.format(
source_directory))
return False
debian_directory = os.path.join(source_directory, u'debian')
# If there is a debian directory remove it and recreate it from
# the dpkg directory.
if os.path.exists(debian_directory):
logging.info(u'Removing: {0:s}'.format(debian_directory))
shutil.rmtree(debian_directory)
shutil.copytree(dpkg_directory, debian_directory)
if not self._BuildPrepare(source_directory):
return False
command = u'dpkg-buildpackage -uc -us -rfakeroot > {0:s} 2>&1'.format(
os.path.join(u'..', self.LOG_FILENAME))
exit_code = subprocess.call(
u'(cd {0:s} && {1:s})'.format(source_directory, command), shell=True)
if exit_code != 0:
logging.error(u'Running: "{0:s}" failed.'.format(command))
return False
if not self._BuildFinalize(source_directory):
return False
return True
def Clean(self, source_helper):
"""Cleans the dpkg packages in the current directory.
Args:
source_helper: the source helper (instance of SourceHelper).
"""
filenames_to_ignore = re.compile(u'^python-{0:s}[-_].*{1!s}'.format(
source_helper.project_name, source_helper.project_version))
# Remove files of previous versions in the format:
# python-{project name}[-_]{project version}-1_architecture.*
filenames = glob.glob(
u'python-{0:s}[-_]*-1_all.*'.format(source_helper.project_name))
for filename in filenames:
if not filenames_to_ignore.match(filename):
logging.info(u'Removing: {0:s}'.format(filename))
os.remove(filename)
# Remove files of previous versions in the format:
# python-{project name}[-_]*version-1.*
filenames = glob.glob(
u'python-{0:s}[-_]*-1.*'.format(source_helper.project_name))
for filename in filenames:
if not filenames_to_ignore.match(filename):
logging.info(u'Removing: {0:s}'.format(filename))
os.remove(filename)
def GetOutputFilename(self, source_helper):
"""Retrieves the filename of one of the resulting files.
Args:
source_helper: the source helper (instance of SourceHelper).
Returns:
A filename of one of the resulting dpkg packages.
"""
if self._dependency_definition.dpkg_name:
project_name = self._dependency_definition.dpkg_name
else:
project_name = source_helper.project_name
return u'python-{0:s}_{1!s}-1_all.deb'.format(
project_name, source_helper.project_version)
class MsiBuildHelper(BuildHelper):
"""Class that helps in building Microsoft Installer packages (.msi)."""
def __init__(self, dependency_definition):
"""Initializes the build helper.
Args:
dependency_definition: the dependency definition object (instance of
DependencyDefinition).
"""
super(MsiBuildHelper, self).__init__(dependency_definition)
self.architecture = platform.machine()
if self.architecture == 'x86':
self.architecture = 'win32'
elif self.architecture == 'AMD64':
self.architecture = 'win-amd64'
class LibyalMsiBuildHelper(MsiBuildHelper):
"""Class that helps in building Microsoft Installer packages (.msi)."""
def __init__(self, dependency_definition):
"""Initializes the build helper.
Args:
dependency_definition: the dependency definition object (instance of
DependencyDefinition).
Raises:
RuntimeError: if the Visual Studio version could be determined or
msvscpp-convert.py could not be found.
"""
super(LibyalMsiBuildHelper, self).__init__(dependency_definition)
if os.environ['VS90COMNTOOLS']:
self.version = '2008'
elif not os.environ['VS100COMNTOOLS']:
self.version = '2010'
elif not os.environ['VS110COMNTOOLS']:
self.version = '2012'
elif not os.environ['VS120COMNTOOLS']:
self.version = '2013'
else:
raise RuntimeError(u'Unable to determine Visual Studio version.')
if self.version != '2008':
self._msvscpp_convert = os.path.join(
os.path.dirname(__file__), u'msvscpp-convert.py')
if not os.path.exists(self._msvscpp_convert):
raise RuntimeError(u'Unable to find msvscpp-convert.py')
def _BuildPrepare(self, source_directory):
"""Prepares the source for building with Visual Studio.
Args:
source_directory: the name of the source directory.
"""
# For the vs2008 build make sure the binary is XP compatible,
# by setting WINVER to 0x0501. For the vs2010 build WINVER is
# set to 0x0600 (Windows Vista).
# WINVER is set in common\config_winapi.h or common\config_msc.h.
config_filename = os.path.join(
source_directory, u'common', u'config_winapi.h')
# If the WINAPI configuration file is not available use
# the MSC compiler configuration file instead.
if not os.path.exists(config_filename):
config_filename = os.path.join(
source_directory, u'common', u'config_msc.h')
# Add a line to the config file that sets WINVER.
parsing_mode = 0
for line in fileinput.input(config_filename, inplace=1):
# Remove trailing whitespace and end-of-line characters.
line = line.rstrip()
if parsing_mode != 2 or line:
if parsing_mode == 1:
if self.version == '2008':
if not line.startswith('#define WINVER 0x0501'):
print '#define WINVER 0x0501'
print ''
else:
if not line.startswith('#define WINVER 0x0600'):
print '#define WINVER 0x0600'
print ''
parsing_mode = 2
elif line.startswith('#define _CONFIG_'):
parsing_mode = 1
print line
def _ConvertSolutionFiles(self, source_directory):
"""Converts the Visual Studio solution and project files.
Args:
source_directory: the name of the source directory.
"""
os.chdir(source_directory)
solution_filenames = glob.glob(os.path.join(u'msvscpp', u'*.sln'))
if len(solution_filenames) != 1:
logging.error(u'Unable to find Visual Studio solution file')
return False
solution_filename = solution_filenames[0]
if not os.path.exists(u'vs2008'):
command = u'{0:s} {1:s} --to {2:s} {3:s}'.format(
sys.executable, self._msvscpp_convert, self.version,
solution_filename)
exit_code = subprocess.call(command, shell=False)
if exit_code != 0:
logging.error(u'Running: "{0:s}" failed.'.format(command))
return False
# Note that setup.py needs the Visual Studio solution directory
# to be named: msvscpp. So replace the Visual Studio 2008 msvscpp
# solution directory with the converted one.
os.rename(u'msvscpp', u'vs2008')
os.rename(u'vs{0:s}'.format(self.version), u'msvscpp')
os.chdir(u'..')
def Build(self, source_helper):
"""Builds using Visual Studio.
Args:
source_helper: the source helper (instance of SourceHelper).
Returns:
True if the build was successful, False otherwise.
"""
source_filename = source_helper.Download()
logging.info(u'Building: {0:s} with Visual Studio {1:s}'.format(
source_filename, self.version))
source_directory = source_helper.Create()
if not source_directory:
logging.error(
u'Extraction of source package: {0:s} failed'.format(source_filename))
return False
# Search common locations for MSBuild.exe
if self.version == '2008':
msbuild = u'{0:s}:{1:s}{2:s}'.format(
u'C', os.sep, os.path.join(
u'Windows', u'Microsoft.NET', u'Framework', u'v3.5',
u'MSBuild.exe'))
# Note that MSBuild in .NET 3.5 does not support vs2010 solution files
# and MSBuild in .NET 4.0 is needed instead.
elif self.version in ['2010', '2012', '2013']:
msbuild = u'{0:s}:{1:s}{2:s}'.format(
u'C', os.sep, os.path.join(
u'Windows', u'Microsoft.NET', u'Framework', u'v4.0.30319',
u'MSBuild.exe'))
if not os.path.exists(msbuild):
logging.error(u'Unable to find MSBuild.exe')
return False
if self.version == '2008':
if not os.environ['VS90COMNTOOLS']:
logging.error(u'Missing VS90COMNTOOLS environment variable.')
return False
elif self.version == '2010':
if not os.environ['VS100COMNTOOLS']:
logging.error(u'Missing VS100COMNTOOLS environment variable.')
return False
elif self.version == '2012':
if not os.environ['VS110COMNTOOLS']:
logging.error(u'Missing VS110COMNTOOLS environment variable.')
return False
elif self.version == '2013':
if not os.environ['VS120COMNTOOLS']:
logging.error(u'Missing VS120COMNTOOLS environment variable.')
return False
# For the Visual Studio builds later than 2008 the convert the 2008
# solution and project files need to be converted to the newer version.
if self.version in ['2010', '2012', '2013']:
self._ConvertSolutionFiles(source_directory)
self._BuildPrepare(source_directory)
# Detect architecture based on Visual Studion Platform environment
# variable. If not set the platform with default to Win32.
msvscpp_platform = os.environ.get('Platform', None)
if not msvscpp_platform:
msvscpp_platform = os.environ.get('TARGET_CPU', None)
if not msvscpp_platform or msvscpp_platform == 'x86':
msvscpp_platform = 'Win32'
if msvscpp_platform not in ['Win32', 'x64']:
logging.error(u'Unsupported build platform: {0:s}'.format(
msvscpp_platform))
return False
if self.version == '2008' and msvscpp_platform == 'x64':
logging.error(u'Unsupported 64-build platform for vs2008.')
return False
solution_filenames = glob.glob(os.path.join(
source_directory, u'msvscpp', u'*.sln'))
if len(solution_filenames) != 1:
logging.error(u'Unable to find Visual Studio solution file')
return False
solution_filename = solution_filenames[0]
command = (
u'{0:s} /p:Configuration=Release /p:Platform={1:s} /noconsolelogger '
u'/fileLogger /maxcpucount {2:s}').format(
msbuild, msvscpp_platform, solution_filename)
exit_code = subprocess.call(command, shell=False)
if exit_code != 0:
logging.error(u'Running: "{0:s}" failed.'.format(command))
return False
python_module_name, _, _ = source_directory.partition(u'-')
python_module_name = u'py{0:s}'.format(python_module_name[3:])
python_module_directory = os.path.join(
source_directory, python_module_name)
python_module_dist_directory = os.path.join(
python_module_directory, u'dist')
if not os.path.exists(python_module_dist_directory):
build_directory = os.path.join(u'..', u'..')
os.chdir(python_module_directory)
# Setup.py uses VS90COMNTOOLS which is vs2008 specific
# so we need to set it for the other Visual Studio versions.
if self.version == '2010':
os.environ['VS90COMNTOOLS'] = os.environ['VS100COMNTOOLS']
elif self.version == '2012':
os.environ['VS90COMNTOOLS'] = os.environ['VS110COMNTOOLS']
elif self.version == '2013':
os.environ['VS90COMNTOOLS'] = os.environ['VS120COMNTOOLS']
command = u'{0:s} setup.py bdist_msi'.format(sys.executable)
exit_code = subprocess.call(command, shell=False)
if exit_code != 0:
logging.error(u'Running: "{0:s}" failed.'.format(command))
return False
# Move the msi to the build directory.
msi_filename = glob.glob(os.path.join(
u'dist', u'{0:s}-*.msi'.format(python_module_name)))
logging.info(u'Moving: {0:s}'.format(msi_filename[0]))
shutil.move(msi_filename[0], build_directory)
os.chdir(build_directory)
return True
def Clean(self, source_helper):
"""Cleans the build and dist directory.
Args:
source_helper: the source helper (instance of SourceHelper).
"""
# Remove previous versions of msis.
filenames_to_ignore = re.compile(u'{0:s}-.*{1!s}.1.{2:s}-py2.7.msi'.format(
source_helper.project_name, source_helper.project_version,
self.architecture))
msi_filenames_glob = u'{0:s}-*.1.{1:s}-py2.7.msi'.format(
source_helper.project_name, self.architecture)
filenames = glob.glob(msi_filenames_glob)
for filename in filenames:
if not filenames_to_ignore.match(filename):
logging.info(u'Removing: {0:s}'.format(filename))
os.remove(filename)
def GetOutputFilename(self, source_helper):
"""Retrieves the filename of one of the resulting files.
Args:
source_helper: the source helper (instance of SourceHelper).
Returns:
A filename of one of the resulting msis.
"""
return u'{0:s}-{1!s}.1.{2:s}-py2.7.msi'.format(
source_helper.project_name, source_helper.project_version,
self.architecture)
class PythonModuleMsiBuildHelper(MsiBuildHelper):
"""Class that helps in building Microsoft Installer packages (.msi)."""
def Build(self, source_helper):
"""Builds the msi.
Args:
source_helper: the source helper (instance of SourceHelper).
Returns:
True if the build was successful, False otherwise.
"""
source_filename = source_helper.Download()
logging.info(u'Building msi of: {0:s}'.format(source_filename))
source_directory = source_helper.Create()
if not source_directory:
logging.error(
u'Extraction of source package: {0:s} failed'.format(source_filename))
return False
command = u'{0:s} setup.py bdist_msi > {1:s} 2>&1'.format(
sys.executable, os.path.join(u'..', self.LOG_FILENAME))
exit_code = subprocess.call(
u'(cd {0:s} && {1:s})'.format(source_directory, command), shell=True)
if exit_code != 0:
logging.error(u'Running: "{0:s}" failed.'.format(command))
return False
# Move the msi to the build directory.
msi_filename = glob.glob(os.path.join(
source_directory, u'dist', u'{0:s}-*.msi'.format(
source_helper.project_name)))
logging.info(u'Moving: {0:s}'.format(msi_filename[0]))
shutil.move(msi_filename[0], '.')
return True
def Clean(self, source_helper):
"""Cleans the build and dist directory.
Args:
source_helper: the source helper (instance of SourceHelper).
"""
# Remove previous versions build directories.
for filename in [u'build', u'dist']:
if os.path.exists(filename):
logging.info(u'Removing: {0:s}'.format(filename))
shutil.rmtree(filename, True)
# Remove previous versions of msis.
filenames_to_ignore = re.compile(u'{0:s}-.*{1!s}.{2:s}.msi'.format(
source_helper.project_name, source_helper.project_version,
self.architecture))
msi_filenames_glob = u'{0:s}-*.{1:s}.msi'.format(
source_helper.project_name, self.architecture)
filenames = glob.glob(msi_filenames_glob)
for filename in filenames:
if not filenames_to_ignore.match(filename):
logging.info(u'Removing: {0:s}'.format(filename))
os.remove(filename)
def GetOutputFilename(self, source_helper):
"""Retrieves the filename of one of the resulting files.
Args:
source_helper: the source helper (instance of SourceHelper).
Returns:
A filename of one of the resulting msis.
"""
# TODO: this does not work for dfvfs at the moment. Fix this.
return u'{0:s}-{1!s}.{2:s}.msi'.format(
source_helper.project_name, source_helper.project_version,
self.architecture)
class PkgBuildHelper(BuildHelper):
"""Class that helps in building MacOS-X packages (.pkg)."""
def __init__(self, dependency_definition):
"""Initializes the build helper.
Args:
dependency_definition: the dependency definition object (instance of
DependencyDefinition).
"""
super(PkgBuildHelper, self).__init__(dependency_definition)
self._pkgbuild = os.path.join(u'/', u'usr', u'bin', u'pkgbuild')
def _BuildDmg(self, pkg_filename, dmg_filename):
"""Builds the distributable disk image (.dmg) from the pkg.
Args:
pkg_filename: the name of the pkg file (which is technically
a directory).
dmg_filename: the name of the dmg file.
Returns:
True if the build was successful, False otherwise.
"""
command = (
u'hdiutil create {0:s} -srcfolder {1:s} -fs HFS+').format(
dmg_filename, pkg_filename)
exit_code = subprocess.call(command, shell=True)
if exit_code != 0:
logging.error(u'Running: "{0:s}" failed.'.format(command))
return False
return True
def _BuildPkg(
self, source_directory, project_identifier, project_version,
pkg_filename):
"""Builds the distributable disk image (.dmg) from the pkg.
Args:
source_directory: the name of the source directory.
project_identifier: the project identifier.
project_version: the version of the project.
pkg_filename: the name of the pkg file (which is technically
a directory).
Returns:
True if the build was successful, False otherwise.
"""
command = (
u'{0:s} --root {1:s}/tmp/ --identifier {2:s} '
u'--version {3!s} --ownership recommended {4:s}').format(
self._pkgbuild, source_directory, project_identifier,
project_version, pkg_filename)
exit_code = subprocess.call(command, shell=True)
if exit_code != 0:
logging.error(u'Running: "{0:s}" failed.'.format(command))
return False
return True
def Clean(self, source_helper):
"""Cleans the MacOS-X packages in the current directory.
Args:
source_helper: the source helper (instance of SourceHelper).
"""
filenames_to_ignore = re.compile(u'^{0:s}-.*{1!s}'.format(
source_helper.project_name, source_helper.project_version))
# Remove files of previous versions in the format:
# project-*version.dmg
filenames = glob.glob(u'{0:s}-*.dmg'.format(source_helper.project_name))
for filename in filenames:
if not filenames_to_ignore.match(filename):
logging.info(u'Removing: {0:s}'.format(filename))
os.remove(filename)
# Remove files of previous versions in the format:
# project-*version.pkg
filenames = glob.glob(u'{0:s}-*.pkg'.format(source_helper.project_name))
for filename in filenames:
if not filenames_to_ignore.match(filename):
logging.info(u'Removing: {0:s}'.format(filename))
os.remove(filename)
def GetOutputFilename(self, source_helper):
"""Retrieves the filename of one of the resulting files.
Args:
source_helper: the source helper (instance of SourceHelper).
Returns:
A filename of one of the resulting rpms.
"""
return u'{0:s}-{1!s}.dmg'.format(
source_helper.project_name, source_helper.project_version)
class LibyalPkgBuildHelper(PkgBuildHelper):
"""Class that helps in building MacOS-X packages (.pkg)."""
def Build(self, source_helper):
"""Builds the pkg package and distributable disk image (.dmg).
Args:
source_helper: the source helper (instance of SourceHelper).
Returns:
True if the build was successful, False otherwise.
"""
source_filename = source_helper.Download()
logging.info(u'Building pkg of: {0:s}'.format(source_filename))
source_directory = source_helper.Create()
if not source_directory:
logging.error(
u'Extraction of source package: {0:s} failed'.format(source_filename))
return False
dmg_filename = u'{0:s}-{1!s}.dmg'.format(
source_helper.project_name, source_helper.project_version)
pkg_filename = u'{0:s}-{1!s}.pkg'.format(
source_helper.project_name, source_helper.project_version)
log_filename = os.path.join(u'..', self.LOG_FILENAME)
sdks_path = os.path.join(
u'/', u'Applications', u'Xcode.app', u'Contents', u'Developer',
u'Platforms', u'MacOSX.platform', u'Developer', u'SDKs')
for sub_path in [u'MacOSX10.7.sdk', u'MacOSX10.8.sdk', u'MacOSX10.9.sdk']:
sdk_path = os.path.join(sdks_path, sub_path)
if os.path.isdir(sub_path):
break
if sdk_path:
cflags = u'CFLAGS="-isysroot {0:s}"'.format(sdk_path)
ldflags = u'LDFLAGS="-Wl,-syslibroot,{0:s}"'.format(sdk_path)
else:
cflags = u''
ldflags = u''
if not os.path.exists(pkg_filename):
if cflags and ldflags:
command = (
u'{0:s} {1:s} ./configure --prefix=/usr --enable-python '
u'--with-pyprefix --disable-dependency-tracking > {2:s} '
u'2>&1').format(cflags, ldflags, log_filename)
else:
command = (
u'./configure --prefix=/usr --enable-python --with-pyprefix '
u'> {0:s} 2>&1').format(log_filename)
exit_code = subprocess.call(
u'(cd {0:s} && {1:s})'.format(source_directory, command), shell=True)
if exit_code != 0:
logging.error(u'Running: "{0:s}" failed.'.format(command))
return False
command = u'make >> {0:s} 2>&1'.format(log_filename)
exit_code = subprocess.call(
u'(cd {0:s} && {1:s})'.format(source_directory, command), shell=True)
if exit_code != 0:
logging.error(u'Running: "{0:s}" failed.'.format(command))
return False
command = u'make install DESTDIR={0:s}/tmp >> {1:s} 2>&1'.format(
os.path.abspath(source_directory), log_filename)
exit_code = subprocess.call(
u'(cd {0:s} && {1:s})'.format(source_directory, command), shell=True)
if exit_code != 0:
logging.error(u'Running: "{0:s}" failed.'.format(command))
return False
share_doc_path = os.path.join(
source_directory, u'tmp', u'usr', u'share', u'doc',
source_helper.project_name)
if not os.path.exists(share_doc_path):
os.makedirs(share_doc_path)
shutil.copy(os.path.join(source_directory, u'AUTHORS'), share_doc_path)
shutil.copy(os.path.join(source_directory, u'COPYING'), share_doc_path)
shutil.copy(os.path.join(source_directory, u'NEWS'), share_doc_path)
shutil.copy(os.path.join(source_directory, u'README'), share_doc_path)
project_identifier = u'com.github.libyal.{0:s}'.format(
source_helper.project_name)
if not self._BuildPkg(
source_directory, project_identifier, source_helper.project_version,
pkg_filename):
return False
if not self._BuildDmg(pkg_filename, dmg_filename):
return False
return True
class PythonModulePkgBuildHelper(PkgBuildHelper):
"""Class that helps in building MacOS-X packages (.pkg)."""
def Build(self, source_helper):
"""Builds the pkg package and distributable disk image (.dmg).
Args:
source_helper: the source helper (instance of SourceHelper).
Returns:
True if the build was successful, False otherwise.
"""
source_filename = source_helper.Download()
logging.info(u'Building pkg of: {0:s}'.format(source_filename))
source_directory = source_helper.Create()
if not source_directory:
logging.error(
u'Extraction of source package: {0:s} failed'.format(source_filename))
return False
dmg_filename = u'{0:s}-{1!s}.dmg'.format(
source_helper.project_name, source_helper.project_version)
pkg_filename = u'{0:s}-{1!s}.pkg'.format(
source_helper.project_name, source_helper.project_version)
log_filename = os.path.join(u'..', self.LOG_FILENAME)
if not os.path.exists(pkg_filename):
command = u'python setup.py build > {0:s} 2>&1'.format(log_filename)
exit_code = subprocess.call(
u'(cd {0:s} && {1:s})'.format(source_directory, command), shell=True)
if exit_code != 0:
logging.error(u'Running: "{0:s}" failed.'.format(command))
return False
command = u'python setup.py install --root={0:s}/tmp > {1:s} 2>&1'.format(
os.path.abspath(source_directory), log_filename)
exit_code = subprocess.call(
u'(cd {0:s} && {1:s})'.format(source_directory, command), shell=True)
if exit_code != 0:
logging.error(u'Running: "{0:s}" failed.'.format(command))
return False
# Copy the license file to the egg-info sub directory.
for license_file in [
u'COPYING', u'LICENSE', u'LICENSE.TXT', u'LICENSE.txt']:
if not os.path.exists(os.path.join(source_directory, license_file)):
continue
command = (
u'find ./tmp -type d -name \\*.egg-info -exec cp {0:s} {{}} '
u'\\;').format(license_file)
exit_code = subprocess.call(
u'(cd {0:s} && {1:s})'.format(source_directory, command),
shell=True)
if exit_code != 0:
logging.error(u'Running: "{0:s}" failed.'.format(command))
return False
project_identifier = source_helper.GetProjectIdentifier()
if not self._BuildPkg(
source_directory, project_identifier, source_helper.project_version,
pkg_filename):
return False
if not self._BuildDmg(pkg_filename, dmg_filename):
return False
return True
class RpmBuildHelper(BuildHelper):
"""Class that helps in building rpm packages (.rpm)."""
# TODO: determine BUILD_DEPENDENCIES from the build files?
_BUILD_DEPENDENCIES = frozenset([
'git',
'binutils',
'autoconf',
'automake',
'libtool',
'gettext-devel',
'make',
'pkgconfig',
'gcc',
'gcc-c++',
'flex',
'byacc',
'zlib-devel',
'bzip2-devel',
'openssl-devel',
'fuse-devel',
'rpm-build',
'python-devel',
'git',
'python-dateutil',
'python-setuptools',
'sqlite-devel',
])
def __init__(self, dependency_definition):
"""Initializes the build helper.
Args:
dependency_definition: the dependency definition object (instance of
DependencyDefinition).
"""
super(RpmBuildHelper, self).__init__(dependency_definition)
self.architecture = platform.machine()
self.rpmbuild_path = os.path.join(u'~', u'rpmbuild')
self.rpmbuild_path = os.path.expanduser(self.rpmbuild_path)
self._rpmbuild_rpms_path = os.path.join(
self.rpmbuild_path, u'RPMS', self.architecture)
self._rpmbuild_sources_path = os.path.join(self.rpmbuild_path, u'SOURCES')
self._rpmbuild_specs_path = os.path.join(self.rpmbuild_path, u'SPECS')
def _BuildFromSpecFile(self, spec_filename):
"""Builds the rpms directly from a spec file.
Args:
spec_filename: the name of the spec file as stored in the rpmbuild
SPECS sub directory.
Returns:
True if the build was successful, False otherwise.
"""
current_path = os.getcwd()
os.chdir(self.rpmbuild_path)
command = u'rpmbuild -ba {0:s} > {1:s} 2>&1'.format(
os.path.join(u'SPECS', spec_filename), self.LOG_FILENAME)
exit_code = subprocess.call(command, shell=True)
if exit_code != 0:
logging.error(u'Running: "{0:s}" failed.'.format(command))
os.chdir(current_path)
return exit_code == 0
def _BuildFromSourcePackage(self, source_filename):
"""Builds the rpms directly from the source package file.
For this to work the source package needs to contain a valid rpm .spec file.
Args:
source_filename: the name of the source package file.
Returns:
True if the build was successful, False otherwise.
"""
command = u'rpmbuild -ta {0:s} > {1:s} 2>&1'.format(
source_filename, self.LOG_FILENAME)
exit_code = subprocess.call(command, shell=True)
if exit_code != 0:
logging.error(u'Running: "{0:s}" failed.'.format(command))
return False
return True
def _CreateRpmbuildDirectories(self):
"""Creates the rpmbuild and sub directories."""
if not os.path.exists(self.rpmbuild_path):
os.mkdir(self.rpmbuild_path)
if not os.path.exists(self._rpmbuild_sources_path):
os.mkdir(self._rpmbuild_sources_path)
if not os.path.exists(self._rpmbuild_specs_path):
os.mkdir(self._rpmbuild_specs_path)
def _CreateSpecFile(self, project_name, spec_file_data):
"""Creates a spec file in the rpmbuild directory.
Args:
project_name: the name of the project.
spec_file_data: the spec file data.
"""
spec_filename = os.path.join(
self._rpmbuild_specs_path, u'{0:s}.spec'.format(project_name))
spec_file = open(spec_filename, 'w')
spec_file.write(spec_file_data)
spec_file.close()
def _CopySourceFile(self, source_filename):
"""Copies the source file to the rpmbuild directory.
Args:
source_filename: the name of the source package file.
"""
shutil.copy(source_filename, self._rpmbuild_sources_path)
def _MoveRpms(self, project_name, project_version):
"""Moves the rpms from the rpmbuild directory into to current directory.
Args:
project_name: the name of the project.
project_version: the version of the project.
"""
filenames = glob.glob(os.path.join(
self._rpmbuild_rpms_path, u'{0:s}-*{1!s}-1.{2:s}.rpm'.format(
project_name, project_version, self.architecture)))
for filename in filenames:
logging.info(u'Moving: {0:s}'.format(filename))
shutil.move(filename, '.')
@classmethod
def CheckBuildDependencies(cls):
"""Checks if the build dependencies are met.
Returns:
A list of package names that need to be installed or an empty list.
"""
missing_packages = []
for package_name in cls._BUILD_DEPENDENCIES:
if not cls.CheckIsInstalled(package_name):
missing_packages.append(package_name)
return missing_packages
@classmethod
def CheckIsInstalled(cls, package_name):
"""Checks if a package is installed.
Args:
package_name: the name of the package.
Returns:
A boolean value containing true if the package is installed
false otherwise.
"""
command = u'rpm -qi {0:s} >/dev/null 2>&1'.format(package_name)
exit_code = subprocess.call(command, shell=True)
return exit_code == 0
def Clean(self, source_helper):
"""Cleans the rpmbuild directory.
Args:
source_helper: the source helper (instance of SourceHelper).
"""
# Remove previous versions build directories.
filenames_to_ignore = re.compile(u'{0:s}-{1!s}'.format(
source_helper.project_name, source_helper.project_version))
filenames = glob.glob(os.path.join(
self.rpmbuild_path, u'BUILD', u'{0:s}-*'.format(
source_helper.project_name)))
for filename in filenames:
if not filenames_to_ignore.match(filename):
logging.info(u'Removing: {0:s}'.format(filename))
shutil.rmtree(filename)
# Remove previous versions of rpms.
filenames_to_ignore = re.compile(
u'{0:s}-.*{1!s}-1.{2:s}.rpm'.format(
source_helper.project_name, source_helper.project_version,
self.architecture))
rpm_filenames_glob = u'{0:s}-*-1.{1:s}.rpm'.format(
source_helper.project_name, self.architecture)
filenames = glob.glob(rpm_filenames_glob)
for filename in filenames:
if not filenames_to_ignore.match(filename):
logging.info(u'Removing: {0:s}'.format(filename))
os.remove(filename)
filenames = glob.glob(os.path.join(
self.rpmbuild_path, u'RPMS', self.architecture, rpm_filenames_glob))
for filename in filenames:
if not filenames_to_ignore.match(filename):
logging.info(u'Removing: {0:s}'.format(filename))
os.remove(filename)
# Remove previous versions of source rpms.
filenames_to_ignore = re.compile(u'{0:s}-.*{1!s}-1.src.rpm'.format(
source_helper.project_name, source_helper.project_version))
filenames = glob.glob(os.path.join(
self.rpmbuild_path, u'SRPMS',
u'{0:s}-*-1.src.rpm'.format(source_helper.project_name)))
for filename in filenames:
if not filenames_to_ignore.match(filename):
logging.info(u'Removing: {0:s}'.format(filename))
os.remove(filename)
def GetOutputFilename(self, source_helper):
"""Retrieves the filename of one of the resulting files.
Args:
source_helper: the source helper (instance of SourceHelper).
Returns:
A filename of one of the resulting rpms.
"""
return u'{0:s}-{1!s}-1.{2:s}.rpm'.format(
source_helper.project_name, source_helper.project_version,
self.architecture)
class LibyalRpmBuildHelper(RpmBuildHelper):
"""Class that helps in building libyal rpm packages (.rpm)."""
def Build(self, source_helper):
"""Builds the rpms.
Args:
source_helper: the source helper (instance of SourceHelper).
Returns:
True if the build was successful, False otherwise.
"""
source_filename = source_helper.Download()
logging.info(u'Building rpm of: {0:s}'.format(source_filename))
# rpmbuild wants the library filename without the status indication.
rpm_source_filename = u'{0:s}-{1!s}.tar.gz'.format(
source_helper.project_name, source_helper.project_version)
os.rename(source_filename, rpm_source_filename)
build_successful = self._BuildFromSourcePackage(rpm_source_filename)
if build_successful:
# Move the rpms to the build directory.
self._MoveRpms(source_helper.project_name, source_helper.project_version)
# Remove BUILD directory.
filename = os.path.join(
self.rpmbuild_path, u'BUILD', u'{0:s}-{1!s}'.format(
source_helper.project_name, source_helper.project_version))
logging.info(u'Removing: {0:s}'.format(filename))
shutil.rmtree(filename)
# Remove SRPMS file.
filename = os.path.join(
self.rpmbuild_path, u'SRPMS', u'{0:s}-{1!s}-1.src.rpm'.format(
source_helper.project_name, source_helper.project_version))
logging.info(u'Removing: {0:s}'.format(filename))
os.remove(filename)
# Change the library filename back to the original.
os.rename(rpm_source_filename, source_filename)
return build_successful
class PythonModuleRpmBuildHelper(RpmBuildHelper):
"""Class that helps in building rpm packages (.rpm)."""
def __init__(self, dependency_definition):
"""Initializes the build helper.
Args:
dependency_definition: the dependency definition object (instance of
DependencyDefinition).
"""
super(PythonModuleRpmBuildHelper, self).__init__(dependency_definition)
self.architecture = 'noarch'
def Build(self, source_helper):
"""Builds the rpms.
Args:
source_helper: the source helper (instance of SourceHelper).
Returns:
True if the build was successful, False otherwise.
"""
source_filename = source_helper.Download()
logging.info(u'Building rpm of: {0:s}'.format(source_filename))
source_directory = source_helper.Create()
if not source_directory:
logging.error(
u'Extraction of source package: {0:s} failed'.format(source_filename))
return False
command = u'python setup.py bdist_rpm > {0:s} 2>&1'.format(
os.path.join(u'..', self.LOG_FILENAME))
exit_code = subprocess.call(
u'(cd {0:s} && {1:s})'.format(source_directory, command), shell=True)
if exit_code != 0:
logging.error(u'Running: "{0:s}" failed.'.format(command))
return False
# Move the rpms to the build directory.
filenames = glob.glob(os.path.join(
source_directory, u'dist', u'{0:s}-{1!s}-1.{2:s}.rpm'.format(
source_helper.project_name, source_helper.project_version,
self.architecture)))
for filename in filenames:
logging.info(u'Moving: {0:s}'.format(filename))
shutil.move(filename, '.')
return True
def Clean(self, source_helper):
"""Cleans the build and dist directory.
Args:
source_helper: the source helper (instance of SourceHelper).
"""
# Remove previous versions build directories.
for filename in [u'build', u'dist']:
if os.path.exists(filename):
logging.info(u'Removing: {0:s}'.format(filename))
shutil.rmtree(filename, True)
# Remove previous versions of rpms.
filenames_to_ignore = re.compile(u'{0:s}-.*{1!s}-1.{2:s}.rpm'.format(
source_helper.project_name, source_helper.project_version,
self.architecture))
rpm_filenames_glob = u'{0:s}-*-1.{1:s}.rpm'.format(
source_helper.project_name, self.architecture)
filenames = glob.glob(rpm_filenames_glob)
for filename in filenames:
if not filenames_to_ignore.match(filename):
logging.info(u'Removing: {0:s}'.format(filename))
os.remove(filename)
class DependencyBuilder(object):
"""Class that helps in building dependencies."""
_LIBYAL_LIBRARIES = frozenset([
'libbde', 'libesedb', 'libevt', 'libevtx', 'libewf', 'libfwsi', 'liblnk',
'libmsiecf', 'libolecf', 'libqcow', 'libregf', 'libsmdev', 'libsmraw',
'libvhdi', 'libvmdk', 'libvshadow'])
_PATCHES_URL = (
u'https://googledrive.com/host/0B30H7z4S52FleW5vUHBnblJfcjg/'
u'3rd%20party/patches')
_PYTHON_MODULES = frozenset([
'bencode', 'binplist', 'construct', 'dfvfs', 'dpkt', 'pyparsing',
'pysqlite', 'pytz', 'PyYAML', 'six'])
def __init__(self, build_target):
"""Initializes the dependency builder.
Args:
build_target: the build target.
"""
super(DependencyBuilder, self).__init__()
self._build_target = build_target
def _BuildDependency(
self, download_helper, dependency_definition):
"""Builds a dependency.
Args:
download_helper: the download helper (instance of DownloadHelper).
dependency_definition: the dependency definition object (instance of
DependencyDefinition).
Returns:
True if the build is successful or False on error.
"""
source_helper = SourcePackageHelper(
download_helper, dependency_definition.name)
source_helper.Clean()
if self._build_target == 'download':
source_filename = source_helper.Download()
# If available run the script post-download.sh after download.
if os.path.exists(u'post-download.sh'):
command = u'sh ./post-download.sh {0:s}'.format(source_filename)
exit_code = subprocess.call(command, shell=True)
if exit_code != 0:
logging.error(u'Running: "{0:s}" failed.'.format(command))
return False
# TODO
elif dependency_definition.name in self._LIBYAL_LIBRARIES:
if not self._BuildLibyalLibrary(source_helper, dependency_definition):
return False
elif dependency_definition.name in self._PYTHON_MODULES:
if not self._BuildPythonModule(source_helper, dependency_definition):
return False
else:
return False
return True
def _BuildLibyalLibrary(self, source_helper, dependency_definition):
"""Builds a libyal library and its Python module dependency.
Args:
source_helper: the source helper (instance of SourceHelper).
dependency_definition: the dependency definition object (instance of
DependencyDefinition).
Returns:
True if the build is successful or False on error.
"""
build_helper = None
if self._build_target == 'dpkg':
build_helper = LibyalDpkgBuildHelper(dependency_definition)
elif self._build_target in ['msi']:
# TODO: setup dokan and zlib in build directory.
build_helper = LibyalMsiBuildHelper(dependency_definition)
elif self._build_target == 'pkg':
build_helper = LibyalPkgBuildHelper(dependency_definition)
elif self._build_target == 'rpm':
build_helper = LibyalRpmBuildHelper(dependency_definition)
if not build_helper:
return False
output_filename = build_helper.GetOutputFilename(source_helper)
build_helper.Clean(source_helper)
if not os.path.exists(output_filename):
if not build_helper.Build(source_helper):
logging.warning((
u'Build of: {0:s} failed, for more information check '
u'{1:s}').format(
source_helper.project_name, build_helper.LOG_FILENAME))
return False
if os.path.exists(build_helper.LOG_FILENAME):
logging.info(u'Removing: {0:s}'.format(build_helper.LOG_FILENAME))
os.remove(build_helper.LOG_FILENAME)
return True
def _BuildPythonModule(self, source_helper, dependency_definition):
"""Builds a Python module dependency.
Args:
source_helper: the source helper (instance of SourceHelper).
dependency_definition: the dependency definition object (instance of
DependencyDefinition).
Returns:
True if the build is successful or False on error.
"""
build_helper = None
if self._build_target == 'dpkg':
build_helper = PythonModuleDpkgBuildHelper(dependency_definition)
elif self._build_target in ['msi']:
# TODO: setup sqlite in build directory.
build_helper = PythonModuleMsiBuildHelper(dependency_definition)
elif self._build_target == 'pkg':
build_helper = PythonModulePkgBuildHelper(dependency_definition)
elif self._build_target == 'rpm':
build_helper = PythonModuleRpmBuildHelper(dependency_definition)
if not build_helper:
return False
output_filename = build_helper.GetOutputFilename(source_helper)
build_helper.Clean(source_helper)
if not os.path.exists(output_filename):
if not build_helper.Build(source_helper):
logging.warning((
u'Build of: {0:s} failed, for more information check '
u'{1:s}').format(
source_helper.project_name, build_helper.LOG_FILENAME))
return False
if os.path.exists(build_helper.LOG_FILENAME):
logging.info(u'Removing: {0:s}'.format(build_helper.LOG_FILENAME))
os.remove(build_helper.LOG_FILENAME)
return True
def Build(self, dependency_definition):
"""Builds a dependency.
Args:
dependency_definition: the dependency definition object (instance of
DependencyDefinition).
Returns:
True if the build is successful or False on error.
Raises:
ValueError: if the project type is unsupported.
"""
download_url = dependency_definition.download_url
if download_url.endswith(u'/'):
download_url = download_url[:-1]
# Unify http:// and https:// URLs for the download helper check.
if download_url.startswith(u'https://'):
download_url = u'http://{0:s}'.format(download_url[8:])
if (download_url.startswith(u'http://code.google.com/p/') and
download_url.endswith(u'/downloads/list')):
download_helper = GoogleCodeWikiDownloadHelper()
elif download_url.startswith(u'http://pypi.python.org/pypi/'):
download_helper = PyPiDownloadHelper()
elif (download_url.startswith(u'http://sourceforge.net/projects/') and
download_url.endswith(u'/files')):
download_helper = SourceForgeDownloadHelper()
# TODO: make this a more generic github download helper when
# when Google Drive support is no longer needed.
elif (download_url.startswith(u'http://github.com/libyal/') or
download_url.startswith(u'http://googledrive.com/host/')):
download_helper = LibyalGitHubDownloadHelper()
elif download_url.startswith(u'http://github.com/log2timeline/'):
download_helper = Log2TimelineGitHubDownloadHelper()
else:
raise ValueError(u'Unsupported downloads URL.')
return self._BuildDependency(download_helper, dependency_definition)
def Main():
build_targets = frozenset(['download', 'dpkg', 'msi', 'pkg', 'rpm'])
args_parser = argparse.ArgumentParser(description=(
'Downloads and builds the latest versions of plaso dependencies.'))
args_parser.add_argument(
'build_target', choices=sorted(build_targets), action='store',
metavar='BUILD_TARGET', default=None, help='The build target.')
args_parser.add_argument(
'--build-directory', '--build_directory', action='store',
metavar='DIRECTORY', dest='build_directory', type=unicode,
default=u'dependencies', help=(
u'The location of the the build directory.'))
args_parser.add_argument(
'-c', '--config', dest='config_file', action='store',
metavar='CONFIG_FILE', default=None,
help='path of the build configuration file.')
options = args_parser.parse_args()
if not options.build_target:
print u'Build target missing.'
print u''
args_parser.print_help()
print u''
return False
if options.build_target not in build_targets:
print u'Unsupported build target: {0:s}.'.format(options.build_target)
print u''
args_parser.print_help()
print u''
return False
if not options.config_file:
options.config_file = os.path.join(
os.path.dirname(__file__), 'dependencies.ini')
if not os.path.exists(options.config_file):
print u'No such config file: {0:s}.'.format(options.config_file)
print u''
return False
logging.basicConfig(
level=logging.INFO, format=u'[%(levelname)s] %(message)s')
if options.build_target == 'dpkg':
missing_packages = DpkgBuildHelper.CheckBuildDependencies()
if missing_packages:
print (u'Required build package(s) missing. Please install: '
u'{0:s}.'.format(u', '.join(missing_packages)))
print u''
return False
elif options.build_target == 'rpm':
missing_packages = RpmBuildHelper.CheckBuildDependencies()
if missing_packages:
print (u'Required build package(s) missing. Please install: '
u'{0:s}.'.format(u', '.join(missing_packages)))
print u''
return False
dependency_builder = DependencyBuilder(options.build_target)
# TODO: allow for patching e.g. dpkt 1.8.
# Have builder check patches URL.
# TODO: package ipython.
# TODO:
# (u'protobuf', DependencyBuilder.PROJECT_TYPE_GOOGLE_CODE_WIKI),
# ./configure
# make
# cd python
# python setup.py build
# python setup.py install --root $PWD/tmp
#
# Build of rpm fails:
# python setup.py bdist_rpm
#
# Solution: use protobuf-python.spec to build
# TODO: download and build sqlite3 from source?
# http://www.sqlite.org/download.html
# or copy sqlite3.h, .lib and .dll to src/ directory?
# TODO: rpm build of psutil is broken, fix upstream or add patching.
# (u'psutil', DependencyBuilder.PROJECT_TYPE_PYPI),
builds = []
with open(options.config_file) as file_object:
dependency_definition_reader = DependencyDefinitionReader()
for dependency_definition in dependency_definition_reader.Read(file_object):
builds.append(dependency_definition)
if not os.path.exists(options.build_directory):
os.mkdir(options.build_directory)
current_working_directory = os.getcwd()
os.chdir(options.build_directory)
result = True
for dependency_definition in builds:
if not dependency_builder.Build(dependency_definition):
print u'Failed building: {0:s}'.format(dependency_definition.name)
result = False
break
os.chdir(current_working_directory)
return result
if __name__ == '__main__':
if not Main():
sys.exit(1)
else:
sys.exit(0)