2016-06-02 10:13:02 +00:00
#!/usr/bin/env python3
2014-12-19 11:41:55 +00:00
# Development tool - utility functions for plugins
#
# Copyright (C) 2014 Intel Corporation
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 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, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
2015-05-11 13:17:01 +00:00
""" Devtool plugins module """
2014-12-19 11:41:55 +00:00
import os
import sys
import subprocess
import logging
2015-12-22 04:03:06 +00:00
import re
2014-12-19 11:41:55 +00:00
logger = logging . getLogger ( ' devtool ' )
2015-05-27 14:59:09 +00:00
class DevtoolError ( Exception ) :
""" Exception for handling devtool errors """
2016-07-10 23:07:56 +00:00
def __init__ ( self , message , exitcode = 1 ) :
super ( DevtoolError , self ) . __init__ ( message )
self . exitcode = exitcode
2015-05-27 14:59:09 +00:00
2014-12-19 11:41:55 +00:00
def exec_build_env_command ( init_path , builddir , cmd , watch = False , * * options ) :
2015-05-11 13:17:01 +00:00
""" Run a program in bitbake build context """
2014-12-19 11:41:55 +00:00
import bb
if not ' cwd ' in options :
options [ " cwd " ] = builddir
if init_path :
2015-04-20 16:47:05 +00:00
# As the OE init script makes use of BASH_SOURCE to determine OEROOT,
# and can't determine it when running under dash, we need to set
# the executable to bash to correctly set things up
if not ' executable ' in options :
options [ ' executable ' ] = ' bash '
2014-12-19 11:41:55 +00:00
logger . debug ( ' Executing command: " %s " using init path %s ' % ( cmd , init_path ) )
init_prefix = ' . %s %s > /dev/null && ' % ( init_path , builddir )
else :
logger . debug ( ' Executing command " %s " ' % cmd )
init_prefix = ' '
if watch :
if sys . stdout . isatty ( ) :
# Fool bitbake into thinking it's outputting to a terminal (because it is, indirectly)
2015-05-14 09:18:18 +00:00
cmd = ' script -e -q -c " %s " /dev/null ' % cmd
2014-12-19 11:41:55 +00:00
return exec_watch ( ' %s %s ' % ( init_prefix , cmd ) , * * options )
else :
return bb . process . run ( ' %s %s ' % ( init_prefix , cmd ) , * * options )
def exec_watch ( cmd , * * options ) :
2015-05-11 13:17:01 +00:00
""" Run program with stdout shown on sys.stdout """
2015-05-14 09:18:18 +00:00
import bb
2016-05-18 18:52:33 +00:00
if isinstance ( cmd , str ) and not " shell " in options :
2014-12-19 11:41:55 +00:00
options [ " shell " ] = True
process = subprocess . Popen (
cmd , stdout = subprocess . PIPE , stderr = subprocess . STDOUT , * * options
)
buf = ' '
while True :
out = process . stdout . read ( 1 )
2016-05-18 18:57:23 +00:00
out = out . decode ( ' utf-8 ' )
2014-12-19 11:41:55 +00:00
if out :
sys . stdout . write ( out )
sys . stdout . flush ( )
buf + = out
elif out == ' ' and process . poll ( ) != None :
break
2015-05-14 09:18:18 +00:00
if process . returncode != 0 :
raise bb . process . ExecutionError ( cmd , process . returncode , buf , None )
return buf , None
2014-12-19 11:41:55 +00:00
2015-06-16 16:16:51 +00:00
def exec_fakeroot ( d , cmd , * * kwargs ) :
""" Run a command under fakeroot (pseudo, in fact) so that it picks up the appropriate file permissions """
# Grab the command and check it actually exists
fakerootcmd = d . getVar ( ' FAKEROOTCMD ' , True )
if not os . path . exists ( fakerootcmd ) :
logger . error ( ' pseudo executable %s could not be found - have you run a build yet? pseudo-native should install this and if you have run any build then that should have been built ' )
return 2
# Set up the appropriate environment
newenv = dict ( os . environ )
fakerootenv = d . getVar ( ' FAKEROOTENV ' , True )
for varvalue in fakerootenv . split ( ) :
if ' = ' in varvalue :
splitval = varvalue . split ( ' = ' , 1 )
newenv [ splitval [ 0 ] ] = splitval [ 1 ]
return subprocess . call ( " %s %s " % ( fakerootcmd , cmd ) , env = newenv , * * kwargs )
2015-09-30 13:51:53 +00:00
def setup_tinfoil ( config_only = False , basepath = None , tracking = False ) :
2015-05-11 13:17:01 +00:00
""" Initialize tinfoil api from bitbake """
2014-12-19 11:41:55 +00:00
import scriptpath
2015-09-23 10:05:23 +00:00
orig_cwd = os . path . abspath ( os . curdir )
2015-10-14 20:15:33 +00:00
try :
if basepath :
os . chdir ( basepath )
bitbakepath = scriptpath . add_bitbake_lib_path ( )
if not bitbakepath :
logger . error ( " Unable to find bitbake by searching parent directory of this script or PATH " )
sys . exit ( 1 )
import bb . tinfoil
tinfoil = bb . tinfoil . Tinfoil ( tracking = tracking )
tinfoil . prepare ( config_only )
tinfoil . logger . setLevel ( logger . getEffectiveLevel ( ) )
finally :
os . chdir ( orig_cwd )
2014-12-19 11:41:55 +00:00
return tinfoil
2015-08-30 07:49:10 +00:00
def get_recipe_file ( cooker , pn ) :
""" Find recipe file corresponding a package name """
import oe . recipeutils
recipefile = oe . recipeutils . pn_to_recipe ( cooker , pn )
if not recipefile :
skipreasons = oe . recipeutils . get_unavailable_reasons ( cooker , pn )
if skipreasons :
logger . error ( ' \n ' . join ( skipreasons ) )
else :
logger . error ( " Unable to find any recipe file matching %s " % pn )
return recipefile
2016-01-26 02:53:54 +00:00
def parse_recipe ( config , tinfoil , pn , appends , filter_workspace = True ) :
2015-08-30 07:49:10 +00:00
""" Parse recipe of a package """
import oe . recipeutils
recipefile = get_recipe_file ( tinfoil . cooker , pn )
if not recipefile :
# Error already logged
return None
if appends :
append_files = tinfoil . cooker . collection . get_file_appends ( recipefile )
2016-01-26 02:53:54 +00:00
if filter_workspace :
# Filter out appends from the workspace
append_files = [ path for path in append_files if
not path . startswith ( config . workspace_path ) ]
2015-09-22 16:21:26 +00:00
else :
append_files = None
2015-08-30 07:49:10 +00:00
return oe . recipeutils . parse_recipe ( recipefile , append_files ,
tinfoil . config_data )
2015-09-22 16:21:24 +00:00
2016-01-06 11:15:55 +00:00
def check_workspace_recipe ( workspace , pn , checksrc = True , bbclassextend = False ) :
2015-09-22 16:21:24 +00:00
"""
Check that a recipe is in the workspace and ( optionally ) that source
is present .
"""
2016-01-06 11:15:55 +00:00
workspacepn = pn
2016-05-18 18:39:44 +00:00
for recipe , value in workspace . items ( ) :
2016-01-06 11:15:55 +00:00
if recipe == pn :
break
if bbclassextend :
recipefile = value [ ' recipefile ' ]
if recipefile :
targets = get_bbclassextend_targets ( recipefile , recipe )
if pn in targets :
workspacepn = recipe
break
else :
2015-09-22 16:21:24 +00:00
raise DevtoolError ( " No recipe named ' %s ' in your workspace " % pn )
2016-01-06 11:15:55 +00:00
2015-09-22 16:21:24 +00:00
if checksrc :
2016-01-06 11:15:55 +00:00
srctree = workspace [ workspacepn ] [ ' srctree ' ]
2015-09-22 16:21:24 +00:00
if not os . path . exists ( srctree ) :
2016-01-06 11:15:55 +00:00
raise DevtoolError ( " Source tree %s for recipe %s does not exist " % ( srctree , workspacepn ) )
2015-09-22 16:21:24 +00:00
if not os . listdir ( srctree ) :
2016-01-06 11:15:55 +00:00
raise DevtoolError ( " Source tree %s for recipe %s is empty " % ( srctree , workspacepn ) )
return workspacepn
2015-09-22 16:21:27 +00:00
def use_external_build ( same_dir , no_same_dir , d ) :
"""
Determine if we should use B != S ( separate build and source directories ) or not
"""
b_is_s = True
if no_same_dir :
logger . info ( ' Using separate build directory since --no-same-dir specified ' )
b_is_s = False
elif same_dir :
logger . info ( ' Using source tree as build directory since --same-dir specified ' )
elif bb . data . inherits_class ( ' autotools-brokensep ' , d ) :
logger . info ( ' Using source tree as build directory since recipe inherits autotools-brokensep ' )
elif d . getVar ( ' B ' , True ) == os . path . abspath ( d . getVar ( ' S ' , True ) ) :
logger . info ( ' Using source tree as build directory since that would be the default for this recipe ' )
else :
b_is_s = False
return b_is_s
2015-09-22 16:21:33 +00:00
def setup_git_repo ( repodir , version , devbranch , basetag = ' devtool-base ' ) :
"""
Set up the git repository for the source tree
"""
import bb . process
if not os . path . exists ( os . path . join ( repodir , ' .git ' ) ) :
bb . process . run ( ' git init ' , cwd = repodir )
bb . process . run ( ' git add . ' , cwd = repodir )
2015-04-23 14:11:42 +00:00
commit_cmd = [ ' git ' , ' commit ' , ' -q ' ]
stdout , _ = bb . process . run ( ' git status --porcelain ' , cwd = repodir )
if not stdout :
commit_cmd . append ( ' --allow-empty ' )
commitmsg = " Initial empty commit with no upstream sources "
elif version :
2015-09-22 16:21:33 +00:00
commitmsg = " Initial commit from upstream at version %s " % version
else :
commitmsg = " Initial commit from upstream "
2015-04-23 14:11:42 +00:00
commit_cmd + = [ ' -m ' , commitmsg ]
bb . process . run ( commit_cmd , cwd = repodir )
2015-09-22 16:21:33 +00:00
bb . process . run ( ' git checkout -b %s ' % devbranch , cwd = repodir )
bb . process . run ( ' git tag -f %s ' % basetag , cwd = repodir )
2015-12-22 04:03:06 +00:00
def recipe_to_append ( recipefile , config , wildcard = False ) :
"""
Convert a recipe file to a bbappend file path within the workspace .
NOTE : if the bbappend already exists , you should be using
workspace [ args . recipename ] [ ' bbappend ' ] instead of calling this
function .
"""
appendname = os . path . splitext ( os . path . basename ( recipefile ) ) [ 0 ]
if wildcard :
appendname = re . sub ( r ' _.* ' , ' _ % ' , appendname )
appendpath = os . path . join ( config . workspace_path , ' appends ' )
appendfile = os . path . join ( appendpath , appendname + ' .bbappend ' )
return appendfile
2016-01-06 11:15:54 +00:00
def get_bbclassextend_targets ( recipefile , pn ) :
"""
Cheap function to get BBCLASSEXTEND and then convert that to the
list of targets that would result .
"""
import bb . utils
values = { }
def get_bbclassextend_varfunc ( varname , origvalue , op , newlines ) :
values [ varname ] = origvalue
return origvalue , None , 0 , True
with open ( recipefile , ' r ' ) as f :
bb . utils . edit_metadata ( f , [ ' BBCLASSEXTEND ' ] , get_bbclassextend_varfunc )
targets = [ ]
bbclassextend = values . get ( ' BBCLASSEXTEND ' , ' ' ) . split ( )
if bbclassextend :
for variant in bbclassextend :
if variant == ' nativesdk ' :
targets . append ( ' %s - %s ' % ( variant , pn ) )
elif variant in [ ' native ' , ' cross ' , ' crosssdk ' ] :
targets . append ( ' %s - %s ' % ( pn , variant ) )
return targets