2013-03-11 14:31:26 +00:00
#
# Rewrite of the builtin install cludge...
#
# (C) 2010-2013, jw@suse.de, Novell Inc., openSUSE.org
# Distribute under GPLv2 or GPLv3
#
# 2010-10-12, jw V0.1 -- initial draft
2019-07-16 08:08:00 +00:00
# 2011-03-23, jw V0.2 -- added --force to allow downgrades. Finished platform matching.
2013-03-11 14:31:26 +00:00
# Uses getpac_default_project, rather than enabled zypper repos.
# 2011-03-24, jw V0.3 -- type 'a' to add repo permanently.
# 2011-03-29, jw V0.4 -- know the ibs repo url default.
# 2011-03-31, jw V0.5 -- osc bse integrated.
# zypper --gpg-auto-import-keys helps reducing the number of questions asked.
# 2011-04-11, jw V0.6 -- using osc_cache, added -I option.
# 2011-05-03, jw V0.7 -- guessing perl package names
# 2011-05-14, jw V0.8 -- options -v and --arch added. arch matching added. debugging for sled and x86_64
# 2011-05-14, jw V0.9 -- crude sorting of choices: if project matches platform, put this first.
# 2011-05-20, jw V0.10 -- bugfix proj_name.
# 2011-05-30, jw V0.11 -- bugfix system_name_words
# 2012-01-20, jw V0.12 -- added hardcoded urls for 12.1/repo/oss, non-oss; for completeness only.
# uploaded to https://gitorious.org/osc-plugin-install/
2019-07-16 08:08:00 +00:00
# 2012-01-22, jw V0.13 -- class TeePopen added.
2013-03-11 14:31:26 +00:00
# trying unpublished packages as a fallback, code half done.
# 2012-01-23, jw V0.14 -- using get_binarylist() and get_binary_file(), finishing fallback code.
# Improved _user_prompt() .. msg is not None ..., packman download url added.
# 2012-02-21, jw V0.15 -- improved _matches_in_name() to prefer exact matches over suffix matches.
# 2012-07-12, jw V0.16 -- no stacktrace, when package does not exist.
# 2012-09-13, jw V0.17 -- also weed out .xml files! -U --prefer-unpublished added
# 2012-12-07, jw V0.18 -- added direct osc bse result usage. (args[0] is None)
# 2012-12-27, jw V0.19 -- added ymp parsing in _layered_repos(). ET is horrible with namespaces.
# 2013-02-06, jw V0.20 -- added _pipe_from_cmd_stdout() to obsolete TeePopen() where it misbehaves.
2019-07-16 08:08:00 +00:00
# 2013-02-18, jw V0.21 -- TeePopen(): shorten long hex strings and useless
2013-03-11 14:31:26 +00:00
# urls, so that overwriting lines with \r is not fooled
# by line wraps.
2013-06-24 11:55:21 +00:00
# 2013-02-23, -- shortening typo fixed.
# 2013-06-05, jw, V0.22 -- added lispish parens to print statements to make newer osc happy.
2015-06-24 07:46:18 +00:00
# 2013-06-27, jw, V0.23 -- ported forward to new osc. Abondoning print(...,
# file=sys.stderr) as it is invalid syntax for
# plugins. It is valid for the main code though. No
# idea what is wrong.
# 2013-11-17, jw, V0.24 -- select-binary option added.
# 2015-06-03, jw, V0.25 -- survive r['baseproject']. Hackish.
2013-03-11 14:31:26 +00:00
#
# FIXME: osc ll -b KDE:Distro:Factory digikam
# shows packages for 12.2, osc in does not.
#
# osc in [project] package
# is a user interface for zypper in [-p project_repo_url ] package; osc thus
2019-07-16 08:08:00 +00:00
# becomes the swiss-army knive of packaging.
2013-03-11 14:31:26 +00:00
# osc mkpac; wget; vi; osc build; osc commit; osc install; done.
#
# The most striking difference is, that osc install can find the correct
# repository url by itself in most cases. It allows users to think in terms of
# projects and packages, and just forget about repository URLs.
#
# If you want to specify a particular project, you can do so by project name.
# otherwise osc in will honor the repository list you compiled when you used
# zypper. But unlike zypper, it does not simply fail when a package (or
# dependency) cannot be found through these zypper repositories, it continues
# to search the build service and suggests projects that might have what you
2019-07-16 08:08:00 +00:00
# need.
2013-03-11 14:31:26 +00:00
#
# Osc install prompts you with candidate packages from a list of projects that
# build the package for your system. It can discern platform and architecture
2019-07-16 08:08:00 +00:00
# from /etc/os-release and/or your ~/.oscrc build_project setting.
2013-03-11 14:31:26 +00:00
#
2019-07-16 08:08:00 +00:00
# Osc install suggests to add repo URLs for newly used projects to the zypper
2013-03-11 14:31:26 +00:00
# repository list. This has little importance for osc, but is very
# helpful, when you have to directly use zypper again some day.
2019-07-16 08:08:00 +00:00
#
# "osc in" actually just calls "zypper -p --from" most of the time to
2013-03-11 14:31:26 +00:00
# get the dependencies resolved correctly and such.
#
# The second benefit is with project layering.
# Packages from a project that is using complex repository paths (aka layering)
2019-07-16 08:08:00 +00:00
# can be installed easily from the web-UI, where yast receives
# a helping ymp file.
2013-03-11 14:31:26 +00:00
# With zypper, packages from such a project just fail due to missing dependendcies.
#
# (or worse, zypper may 'succeed' in resolving dependencies from different
# repositories).
# 'osc in' analyzes the meta data of the project, and creates the proper list
# of repositories for zypper, just as yast would do.
2019-07-16 08:08:00 +00:00
#
2013-03-11 14:31:26 +00:00
# Project owners often pull in all the dependencies of their packages
# into their projects, (via link or aggregate), just to avoid this problem.
# That should never be needed.
#
# As third benefit is it saves bandwidth.
# 'osc in' cooperates with 'osc build'. Whenever it downloads a package for
# installation, you can also add it to the package cache. This saves duplicate
# downloads during package development. And vice versa of course: When you do
# 'osc build; osc in' you have good chances, that nothing needs to be
# downloaded during 'osc in' -- because it looks (unlike zypper) into your
2019-07-16 08:08:00 +00:00
# package caches. Zypper has a per-repository property 'keeppackages'
2013-03-11 14:31:26 +00:00
# which would cache them in /var/cache/zypp/packages -- this is helpful.
#
# 'osc in' needs no parameters, if called within a package checkout directory.
# It will install what 'osc ls -b' would list for that package directory.
# Except -debuginfo and -devel packages, which are not installed by default
# (but printed out, so that you know.)
#
# This plugin can also be called as '/usr/bin/apt-get'.
2019-07-16 08:08:00 +00:00
# In this case, its options very closely resembles the original
2013-03-11 14:31:26 +00:00
# apt-get. That could pacify some ex-debian users, who would possibly
# freak out, when they learn the raw complexity of zypper.
#
2019-07-16 08:08:00 +00:00
# One of the hardest task is to hide most
# repository/platfrom/project/distribution details from the user.
# End users and normal packagers should not need to learn the
2013-03-11 14:31:26 +00:00
# difference between openSUSE_11.3 and openSUSE:11.3 -- they should be allowed
# to use either spelling in all cases.
# We do that, by initially analyzing disturls found in your rpm database
# A typical disturl reads:
# obs://build.opensuse.org/Documentation:Tools/openSUSE_11.3/ccedd5c76ce44fd2d48348fd9249072a-sikuli
# where 'Documentation:Tools' is a project, and 'openSUSE_11.3' is a platform.
# Zypper would have a corresponding repository
# http://download.opensuse.org/repositories/Documentation:/Tools/openSUSE_11.3
# (Note the hideous '/' character between 'Documentation:' and 'Tools'!)
# Another example:
# obs://build.opensuse.org/openSUSE:11.3:Update:Test/standard/07bb29ae70e34affb224de39e2fab3ba-java-1_6_0-sun
# The corresponding build service repo is known by osc ls -b is:
# https://api.opensuse.org/build/openSUSE:11.3:Update:Test/standard/i586/java-1_6_0-sun
# because it has no location on the mirrors, it is cached as
# /var/tmp/osbuild-packagecache/openSUSE/var/tmp/osbuild-packagecache/openSUSE\:11.3\:Update\:Test/standard/i586/
# its use would circumvent the 'published' flag, and put load on the api, that
# should be on the mirrors.
# its corresponding zypper repository is
# http://download.opensuse.org/update/11.3/
2019-07-16 08:08:00 +00:00
# -> it appears there is no public mapping between download.opensuse.org directories and
2013-03-11 14:31:26 +00:00
# projects.
# -> We could crawl the entire download.opensuse.org,
2019-07-16 08:08:00 +00:00
# pull a few rpms from each directory and thus learn which projects
2013-03-11 14:31:26 +00:00
# are behind.
# -> this is a TODO for a centralized mapping service, which this plugin could query.
#
# http://download.opensuse.org/repositories/openSUSE:/11.3:/NonFree/standard
2019-07-16 08:08:00 +00:00
#
# FIXME:
# If your package requires a virtual provide, current zypper repo metadata is needed to
2013-03-11 14:31:26 +00:00
# map this to package names. E.g.
# Two ways how this can fail:
# a) the package was recently added to a different repo, which is not refreshed
2019-07-16 08:08:00 +00:00
# when running osc in. (osc in refreshes only the one repo, from which the
2013-03-11 14:31:26 +00:00
# package comes)
2019-07-16 08:08:00 +00:00
# b) the repo where those dependendcies should come from are not in the zypper list
2013-03-11 14:31:26 +00:00
# at all. osc in suggests to add repos, but one may not want to do that often.
# E.g:
# Problem: nothing provides libfreeimageplus.so.3()(64bit) needed by freecad-devel-0.13rc.svn5443-32.1.x86_64
# If you see this, run 'sudo zypper ref' then retry. If it works it was issue a).
# If not, see if the repo list printed by zypper ref, contains all needed repos.
2019-07-16 08:08:00 +00:00
# Solution: parse the project layering, add all repos, that are needed, then run the
2013-03-11 14:31:26 +00:00
# install.
2019-07-16 08:08:00 +00:00
# FIXME: osc in should print out the description from meta pkg, so that the user
# has something meaningful to read. Packagers may also put special hints there about the
2013-03-11 14:31:26 +00:00
# usage or installation of the package.
#
# rpm -q --qf '%{disturl}\n' kernel-desktop
# obs://build.opensuse.org/openSUSE:Maintenance:970/openSUSE_12.2_Update/d5ce0cc876098d68d533ae47996a63ff-kernel-desktop.openSUSE_12.2_Update
# How to map from this obs:// url to the following download url???
# http://download.opensuse.org/repositories/openSUSE:/Maintenance:/970/openSUSE_12.2_Update/i586/kernel-desktop-debuginfo-3.4.11-2.16.1.i586.rpm
2013-07-09 09:39:20 +00:00
from __future__ import print_function
2019-07-16 08:08:00 +00:00
import os
import re
import subprocess
import sys
import traceback
from platform import uname
2013-07-09 09:39:20 +00:00
from osc import cmdln
2013-03-11 14:31:26 +00:00
global OSC_INS_PLUGIN_VERSION , OSC_INS_PLUGIN_NAME
2015-06-24 07:46:18 +00:00
OSC_INS_PLUGIN_VERSION = ' 0.25 '
2013-03-11 14:31:26 +00:00
OSC_INS_PLUGIN_NAME = traceback . extract_stack ( ) [ - 1 ] [ 0 ] + ' V ' + OSC_INS_PLUGIN_VERSION
# this table is obsoleted by get_repositories_of_project()
OSC_INS_REPO_MAP = """
2019-07-16 08:08:00 +00:00
{
2013-03-11 14:31:26 +00:00
' * ' : { ' * ' : [ ' http://unknown.donwload.server( % {apiurl} )/ ' ] } ,
2019-07-16 08:08:00 +00:00
' https://api.opensuse.org ' :
2013-03-11 14:31:26 +00:00
{
' * ' : [ ' http://download.opensuse.org/repositories ' ] ,
' openSUSE:11.3 ' : [ ' http://download.opensuse.org/distribution/11.3/repo/oss ' ] ,
' openSUSE:11.4 ' : [ ' http://download.opensuse.org/distribution/11.4/repo/oss ' ] ,
' openSUSE:12.1 ' : [ ' http://download.opensuse.org/distribution/12.1/repo/oss ' ] ,
' openSUSE:12.2 ' : [ ' http://download.opensuse.org/distribution/12.2/repo/oss ' ] ,
2015-06-24 07:46:18 +00:00
' openSUSE:12.3 ' : [ ' http://download.opensuse.org/distribution/12.3/repo/oss ' ] ,
2013-03-11 14:31:26 +00:00
' openSUSE:Factory ' : [ ' http://download.opensuse.org/distribution/openSUSE-current/repo/oss ' ] ,
' openSUSE:11.3:NonFree ' : [ ' http://download.opensuse.org/distribution/11.3/repo/non-oss ' ] ,
' openSUSE:11.4:NonFree ' : [ ' http://download.opensuse.org/distribution/11.4/repo/non-oss ' ] ,
' openSUSE:12.1:NonFree ' : [ ' http://download.opensuse.org/distribution/12.1/repo/non-oss ' ] ,
' openSUSE:12.2:NonFree ' : [ ' http://download.opensuse.org/distribution/12.2/repo/non-oss ' ] ,
2015-06-24 07:46:18 +00:00
' openSUSE:12.3:NonFree ' : [ ' http://download.opensuse.org/distribution/12.3/repo/non-oss ' ] ,
2013-03-11 14:31:26 +00:00
' openSUSE:Factory:NonFree ' : [ ' http://download.opensuse.org/distribution/openSUSE-current/repo/non-oss ' ] ,
' ... ' : [ ' ... ' ]
} ,
' https://api.suse.de ' :
{
} ,
' https://pmbs-api.links2linux.org ' :
{
# FIXME: home projects are not there, unfortunatly
' * ' : [ ' http://pmbs.links2linux.org/download ' ]
}
} """
2019-07-16 08:08:00 +00:00
2013-03-11 14:31:26 +00:00
@cmdln.hide ( 1 )
@cmdln.alias ( ' in ' )
2019-07-16 08:08:00 +00:00
@cmdln.option ( ' -p ' , ' --platform ' , metavar = ' SUSE_RELEASE ' , help = ' platform substring to match. Default: guess platform from /etc/os-release ' )
@cmdln.option ( ' -a ' , ' --arch ' , metavar = ' ARCH ' , help = ' system architecture. Default: guess platform from /etc/os-release ' )
2013-03-11 14:31:26 +00:00
@cmdln.option ( ' -f ' , ' --first ' , action = ' store_true ' , help = ' if multiple projects offer a package, choose the first. Default: Ask user ' )
@cmdln.option ( ' -v ' , ' --verbose ' , action = ' store_true ' , help = ' babble while working ' )
@cmdln.option ( ' -I ' , ' --no-cache ' , action = ' store_true ' , help = ' ignore cached packages, always download. Default: check build cache /var/tmp/osbuild-packagecache ' )
@cmdln.option ( ' -U ' , ' --prefer-unpublished ' , action = ' store_true ' , help = ' Grab unpublished binary directly from the API. Usefull if publishing is slow. Default: use normal mirror system. ' )
2015-06-24 07:46:18 +00:00
@cmdln.option ( ' -S ' , ' --select-binary ' , help = ' Type a number for the binary, default first in list, aka 0 ' )
2013-03-11 14:31:26 +00:00
#@cmdln.prep(cwd_proj_pack)
def do_install ( self , subcmd , opts , * args ) :
""" $ {cmd_name} : install a package after build via zypper in -r
2019-07-16 08:08:00 +00:00
CAUTION : Experimental code . This needs a sane
2013-03-11 14:31:26 +00:00
algorithm to derive a repourl from ( apiurl , project , package )
2019-07-16 08:08:00 +00:00
osc in
2013-03-11 14:31:26 +00:00
take PROJECT name and PACKAGE name from current directory .
osc in PACKAGE
2019-07-16 08:08:00 +00:00
find PACKAGE in this build service . The project of the current directoy , ( if any )
has highest precedence , followed by the projects listed in
2013-03-11 14:31:26 +00:00
~ / . oscrc : getpac_default_project ( if any ) , followed by the project repos
registered with zypper .
osc in PATH : / TO : / PACKAGE . rpm
Use a path returned by osc bse .
osc in PROJECT PACKAGE
install PACKAGE from PROJECT .
Binary packages often have the same name as their source packages , but not always .
2019-07-16 08:08:00 +00:00
With osc install , PACKAGE names are binary package names .
2013-03-11 14:31:26 +00:00
$ { cmd_usage }
$ { cmd_option_list }
"""
apiurl = self . get_api_url ( )
if len ( args ) == 1 and not re . search ( ' \ .(rpm|ymp)$ ' , args [ 0 ] ) :
2019-07-16 08:08:00 +00:00
args = slash_split ( args )
2013-03-11 14:31:26 +00:00
if len ( args ) == 0 :
2019-07-16 08:08:00 +00:00
args = expand_proj_pack ( list ( args ) )
print ( " proj/pack from current working directory: " , args )
2013-03-11 14:31:26 +00:00
platform = None
dl = ' http://unknown.donwload.server( %s )/ ' % apiurl
if apiurl == ' https://api.opensuse.org ' :
dl = ' http://download.opensuse.org/repositories '
if apiurl == ' https://api.suse.de ' :
dl = ' http://download.suse.de/ibs '
if apiurl == ' https://pmbs-api.links2linux.org ' :
dl = ' http://pmbs.links2linux.org/download '
# FIXME: home projects are not there, unfortunatly
2019-07-16 08:08:00 +00:00
# default_platform = 'openSUSE_Tumbleweed'
2013-03-11 14:31:26 +00:00
osc_cache = ' /var/tmp/osbuild-packagecache '
2019-07-16 08:08:00 +00:00
etc_S_r = ' /etc/os-release '
2013-03-11 14:31:26 +00:00
if len ( args ) == 1 :
2019-07-16 08:08:00 +00:00
if re . search ( ' \ .rpm$ ' , args [ 0 ] ) :
if re . match ( ' https?:// ' , args [ 0 ] ) :
url = args [ 0 ]
else :
url = " %s / %s " % ( dl , args [ 0 ] )
args = [ None , url ]
print ( " using direct rpm url ( %s ). \n " % ( url ) ) # , file=sys.stderr)
elif re . search ( ' \ .ymp$ ' , args [ 0 ] ) :
print ( " ymp file not implemented. \n " )
sys . exit ( 0 )
# FIXME:
# if there is only one argument, and it ends in .ymp
# then fetch it, Parse XML to get all
# metapackage.group.repositories.repository.url
# construct zypper commands to add all these repos.
# and construct zypper cmd's for all
# metapackage.group.software.item.name
#
# if args[0] is already an url, the use it as is.
2013-03-11 14:31:26 +00:00
else :
2019-07-16 08:08:00 +00:00
m = re . match ( ' perl \ ((.*) \ )$ ' , args [ 0 ] )
if m :
# a perlish RPM capability
args = ( ' perl- ' + re . sub ( ' :: ' , ' - ' , m . group ( 1 ) ) , )
print ( " obs name -> %s " % ( args [ 0 ] ) )
elif re . search ( ' :: ' , args [ 0 ] ) :
# a cpan name
args = ( ' perl- ' + re . sub ( ' :: ' , ' - ' , args [ 0 ] ) , )
print ( " obs name -> %s " % ( args [ 0 ] ) )
all = self . _search_projects ( apiurl , args [ 0 ] )
# [ {'name': 'python-json-rpc-lib', 'repository': 'openSUSE_Factory',
# 'package': 'python-json-rpc-lib', 'type': 'rpm',
# 'filepath': 'home:/dec16180/openSUSE_Factory/x86_64/python-json-rpc-lib-20090604-5.1.x86_64.rpm',
# 'filename': 'python-json-rpc-lib-20090604-5.1.x86_64.rpm', 'project': 'home:dec16180',
# 'baseproject': 'openSUSE:Factory', 'version': '20090604-5.1', 'arch': 'x86_64'}, ... ]
if not opts . no_cache :
self . _find_cached ( all , osc_cache )
# extract all platforms, then ...
seen = { }
arch_words = uname ( ) [ - 1 ]
if ( opts . arch is not None ) :
# and nothing else.
arch_words = [ opts . arch ]
if opts . arch == ' i386 ' or opts . arch == ' i586 ' or opts . arch == ' i686 ' :
arch_words . append ( ' i586 ' )
arch_words . append ( ' i686 ' )
arch_words . append ( ' i386 ' )
if opts . verbose :
print ( " arch_words: " , arch_words )
for r in all :
if ' baseproject ' not in r :
r [ ' baseproject ' ] = r [ ' project ' ] # hack to survive, but probably wrong...
if opts . verbose :
print ( " seen " , r [ ' project ' ] , r [ ' baseproject ' ] )
if r [ ' repository ' ] == ' standard ' :
r [ ' repository ' ] = re . sub ( ' : ' , ' _ ' , r [ ' baseproject ' ] )
if r [ ' arch ' ] in arch_words or r [ ' arch ' ] == ' noarch ' :
seen [ r [ ' repository ' ] ] = 1
if opts . verbose :
print ( seen )
best = self . _best_platform ( etc_S_r , seen . keys ( ) , opts )
# ...filter down by best matching platform
# my @res = grep { $_->{repository} eq $best } @all;
# python: filter() ???
res = [ ]
seen = { }
for r in all :
proj_name = r [ ' project ' ]
if r [ ' repository ' ] == best and proj_name not in seen :
if r [ ' arch ' ] in arch_words or r [ ' arch ' ] == ' noarch ' :
if proj_name == best :
res . insert ( 0 , r )
else :
res . append ( r ) # list each project only once, with best matching arch.
seen [ proj_name ] = 1
if not res :
raise oscerr . WrongArgs ( ' Could not find %s . \n (Use two args to avoid searching, try --arch, --platform, or try another build service). ' % args [ 0 ] )
i = 1
for r in res :
cached = ' '
if ' cached ' in r :
cached = ' (cached %s ) ' % ( r [ ' cached ' ] [ ' size ' ] )
print ( " %2d : %-50s %-15s %-10s %s " % ( i , r [ ' project ' ] , r [ ' version ' ] , r [ ' arch ' ] , cached ) )
i + = 1
print ( ' ' )
if opts . arch :
print ( " WARNING: --arch option is unreliable. zypper might still choose something different! " )
if len ( res ) > 1 :
nr = _user_prompt ( " Type number from above list (default=1), press ENTER " , None , None )
idx = 0
try :
idx = int ( nr ) - 1
args = [ res [ idx ] [ ' project ' ] , res [ idx ] [ ' name ' ] ]
except KeyError :
idx = 0
args = [ res [ 0 ] [ ' project ' ] , res [ 0 ] [ ' name ' ] ]
try :
args [ 1 ] = res [ idx ] [ ' cached ' ] [ ' path ' ]
print ( ' using %s ' % ( args [ 1 ] ) )
except KeyError :
print ( ' using %s / %s ' % ( args [ 0 ] , args [ 1 ] ) )
#}
2013-03-11 14:31:26 +00:00
if args [ 0 ] is not None :
2019-07-16 08:08:00 +00:00
# FIXME: what an ugly hack!
if apiurl == ' https://api.opensuse.org ' and args [ 0 ] == ' openSUSE:Factory ' :
url = ' http://download.opensuse.org/distribution/openSUSE-current/repo/oss '
elif apiurl == ' https://api.opensuse.org ' and args [ 0 ] == ' openSUSE:11.3 ' :
url = ' http://download.opensuse.org/distribution/11.3/repo/oss '
elif apiurl == ' https://api.opensuse.org ' and args [ 0 ] == ' openSUSE:11.4 ' :
url = ' http://download.opensuse.org/distribution/11.4/repo/oss '
elif apiurl == ' https://api.opensuse.org ' and args [ 0 ] == ' openSUSE:12.1 ' :
url = ' http://download.opensuse.org/distribution/12.1/repo/oss '
elif apiurl == ' https://api.opensuse.org ' and args [ 0 ] == ' openSUSE:12.2 ' :
url = ' http://download.opensuse.org/distribution/12.2/repo/oss '
elif apiurl == ' https://api.opensuse.org ' and args [ 0 ] == ' openSUSE:12.3 ' :
url = ' http://download.opensuse.org/distribution/12.3/repo/oss '
elif apiurl == ' https://api.opensuse.org ' and args [ 0 ] == ' openSUSE:11.3:NonFree ' :
url = ' http://download.opensuse.org/distribution/11.3/repo/non-oss '
elif apiurl == ' https://api.opensuse.org ' and args [ 0 ] == ' openSUSE:11.4:NonFree ' :
url = ' http://download.opensuse.org/distribution/11.4/repo/non-oss '
elif apiurl == ' https://api.opensuse.org ' and args [ 0 ] == ' openSUSE:12.1:NonFree ' :
url = ' http://download.opensuse.org/distribution/12.1/repo/non-oss '
elif apiurl == ' https://api.opensuse.org ' and args [ 0 ] == ' openSUSE:12.2:NonFree ' :
url = ' http://download.opensuse.org/distribution/12.2/repo/non-oss '
elif apiurl == ' https://api.opensuse.org ' and args [ 0 ] == ' openSUSE:12.3:NonFree ' :
url = ' http://download.opensuse.org/distribution/12.3/repo/non-oss '
elif apiurl == ' https://api.opensuse.org ' and args [ 0 ] == ' openSUSE:Factory:NonFree ' :
url = ' http://download.opensuse.org/distribution/openSUSE-current/repo/non-oss '
else :
repos = get_repositories_of_project ( apiurl , args [ 0 ] )
# print("get_repositories_of_project(%s,%s) returns " % ( apiurl, args[0]))
# print(repos)
platform = self . _best_platform ( etc_S_r ,
get_repositories_of_project ( apiurl , args [ 0 ] ) , opts )
url = " %s / %s / %s " % ( dl , re . sub ( ' : ' , ' :/ ' , args [ 0 ] ) , platform )
if args [ 1 ] [ 0 ] == ' / ' :
# local disk pathname, coming from a cache..
# zypper bug: with -p url, url is always refreshed, our --no-refresh is ignored.
# hence without -p url; use --no-cache if dependencies fail.
cmd = " sudo zypper --no-refresh -v in --force %s " % args [ 1 ]
cmdv = [ ' sudo ' , ' zypper ' , ' --no-refresh ' , ' -v ' , ' in ' , ' --force ' , args [ 1 ] ]
else :
cmd = " (repo= " + url + " ; sudo zypper -p $repo "
cmdv = [ ' sudo ' , ' zypper ' , ' -p ' , url ]
for u in ( self . _layered_repos ( args [ 0 ] , platform , args [ 1 ] ) ) :
if u != url :
cmd + = " -p " + u
cmdv . extend ( [ " -p " , u ] )
cmd + = " --gpg-auto-import-keys --no-refresh -v in --force --from $repo " + args [ 1 ] + " ) "
cmdv . extend ( [ ' --gpg-auto-import-keys ' , ' --no-refresh ' , ' -v ' , ' in ' , ' --force ' , ' --from ' , url , args [ 1 ] ] )
2013-03-11 14:31:26 +00:00
#}
else :
2019-07-16 08:08:00 +00:00
cmd = " (sudo zypper in %s ) " % url
cmdv = [ ' sudo ' , ' zypper ' , ' in ' , url ]
2013-03-11 14:31:26 +00:00
2013-06-24 11:55:21 +00:00
print ( " Suggested installation command: \n " + cmd )
2013-03-11 14:31:26 +00:00
if args [ 0 ] is not None :
2019-07-16 08:08:00 +00:00
all = self . _pipe_from_cmd_stdout ( ( ' zypper ' , ' lr ' , ' -e ' , ' - ' ) ) # no sudo with lr
if all . find ( ' baseurl= ' + url ) > 0 :
print ( " repo %s already known, enabling ... " % ( url ) )
2013-03-11 14:31:26 +00:00
p = subprocess . Popen ( [ ' sudo ' , ' zypper ' , ' mr ' , ' -e ' , url ] )
os . waitpid ( p . pid , 0 )
2019-07-16 08:08:00 +00:00
else :
print ( " (Type ' a ' to add the repo ( ' A ' for all repos) permanently) Press Enter to continue. " )
a = sys . stdin . readline ( )
if a . find ( ' A ' ) > = 0 :
print ( " adding all layered repos permanently is not implemented. " )
a = " a "
if a . find ( ' a ' ) > = 0 :
all = self . _pipe_from_cmd_stdout ( ( ' zypper ' , ' lr ' , ' -e ' , ' - ' ) ) # no sudo with lr
if all . find ( ' baseurl= ' + url ) > 0 :
print ( " is already there, enabling it. " )
p = subprocess . Popen ( [ ' sudo ' , ' zypper ' , ' mr ' , ' -e ' , url ] )
os . waitpid ( p . pid , 0 )
else :
p = subprocess . Popen ( [ ' sudo ' , ' zypper ' , ' ar ' , url , ' obs:// ' + args [ 0 ] ] )
os . waitpid ( p . pid , 0 )
# FIXME: now we should no longer use -p with cmd
2013-03-11 14:31:26 +00:00
# We need a way to monitor what the command is printing. Without delaying, prompts and such.
# subprocess communicate() delays everything.
2019-07-16 08:08:00 +00:00
if platform is None :
platform = ' PLATFORM '
2013-03-11 14:31:26 +00:00
# old code: os.execvp(cmdv[0], cmdv)
# Fixme: can we replace that with _tee_from_cmd_stdout() ??
2013-06-24 11:55:21 +00:00
print ( " Go: " )
2013-03-11 14:31:26 +00:00
buf = str ( TeePopen ( cmdv , verbose = True ) )
if opts . prefer_unpublished or re . search ( " Package ' \ S+ ' not found " , buf ) :
2019-07-16 08:08:00 +00:00
import osc . build
import tempfile
# FIXME: dependencies are not resolved here...o
# very likely to run into somehting like this:
# Forcing installation of 'glade3-3.7.0-8.1.i586' from repository 'Plain RPM files cache'.
# Problem: nothing provides libgladeui-1.so.9 needed by glade3-3.7.0-8.1.i586
print ( " not there, ... trying unpublished (CTRL-C to abort) Press Enter to continue. " )
a = sys . stdin . readline ( )
binaries = get_binarylist ( apiurl , args [ 0 ] , platform , osc . build . hostarch , package = args [ 1 ] , verbose = True )
# [publican-2.3-15.26.noarch.rpm, publican-2.3-15.26.src.rpm]
# [copyfs-1.0-1.1.i586.rpm, copyfs-1.0-1.1.src.rpm, rpmlint.log]
# weed out non-binaries.
binaries = filter ( lambda x : not re . search ( ' (src \ .rpm| \ .log| \ .xml|_statistics)$ ' , str ( x ) ) , binaries )
# sort shortest name is first, so that foo-debuginfo comes after foo
if len ( binaries ) > 1 :
print ( " multiple binaries available: " )
print ( binaries )
binaries . sort ( lambda x , y : cmp ( len ( str ( x ) ) , len ( str ( y ) ) ) )
if ( opts . select_binary ) :
binaries = binaries [ int ( opts . select_binary ) : ]
print ( binaries )
# filter down for starting with my name, optional.
mainbin = filter ( lambda x : re . match ( args [ 1 ] , str ( x ) ) , binaries )
mainbin . extend ( binaries )
if len ( mainbin ) > 0 :
tmpfile = tempfile . mktemp ( suffix = ' - ' + str ( mainbin [ 0 ] ) )
get_binary_file ( apiurl , args [ 0 ] , platform , osc . build . hostarch , str ( mainbin [ 0 ] ) ,
package = args [ 1 ] , target_filename = tmpfile )
TeePopen ( [ ' sudo ' , ' zypper ' , ' --no-refresh ' , ' -v ' , ' in ' , ' --force ' , tmpfile ] , verbose = True )
os . unlink ( tmpfile )
else :
print ( " There is no %s for you. " % ( args [ 1 ] ) )
2013-06-24 11:55:21 +00:00
print ( " \n -- osc %s , by jw@suse.de " % ( OSC_INS_PLUGIN_NAME ) )
2013-03-11 14:31:26 +00:00
2019-07-16 08:08:00 +00:00
2013-03-11 14:31:26 +00:00
def _layered_repos ( self , proj , platform , pack ) :
apiurl = self . get_api_url ( )
ymp = None
2019-07-16 08:08:00 +00:00
if apiurl == ' https://api.opensuse.org ' :
2013-03-11 14:31:26 +00:00
# with obs we have this:
# http://software.opensuse.org/ymp/home:jnweiger:freecad/openSUSE_12.2/freecad.ymp
ymp = ' http://software.opensuse.org/ymp/ %s / %s / %s .ymp ' % ( proj , platform , pack )
try :
f = http_GET ( ymp )
except :
2013-06-24 11:55:21 +00:00
print ( " Oops: failed to grab \n %s " % ymp )
print ( " FIXME: should pull osc meta prj instead... \n " )
2013-03-11 14:31:26 +00:00
# FIXME: retry by reading meta prj
return [ ]
metapackage = ET . parse ( f ) . getroot ( )
2019-07-16 08:08:00 +00:00
2013-03-11 14:31:26 +00:00
urls = [ ]
# ET cannot find tags by name, if namespaces are being used.
# urls = [ node.tag for node in metapackage.findall(".//{*}url") ]
#
# The xml code we get would be trivial to parse, if there were no namespace declaration
2019-07-16 08:08:00 +00:00
# With xmlns=, each and every tag with findall or iterfind needs to be written with the
# full namespace in curly braces.
2013-03-11 14:31:26 +00:00
# Example findall('.//{http://opensuse.org/Standards/One_Click_Install}repository}')
2019-07-16 08:08:00 +00:00
# would match a repository node. This is horrible, I don't want to hardcode namespaces or
2013-03-11 14:31:26 +00:00
# uglify my code with complex namespace handling at all.
#
# <metapackage xmlns="http://opensuse.org/Standards/One_Click_Install">
# <group>
# <repositories>
# <repository recommended="true">
# <name>devel:gcc</name>
# <summary>GNU Compiler Collection container</summary>
# <description>Contains compilers updating openSUSE releases ...</description>
# <url>http://download.opensuse.org/repositories/devel:/gcc/openSUSE_12.2/</url>
# </repository>
#
####
# instead, we use the fact, that all repository nodes have an attribute 'recommended'
# and we grab all children whose tagname matches 'url', with an optional ns-prefix.
####
for repository in metapackage . iterfind ( " .//*[@recommended= ' true ' ] " ) :
for node in repository :
if re . search ( " }?url$ " , node . tag ) :
url = re . sub ( ' /$ ' , ' ' , node . text )
2019-07-16 08:08:00 +00:00
print ( " from ymp: " + url )
2013-03-11 14:31:26 +00:00
urls . append ( url )
return urls
def _read_system_name ( self , file , opts ) :
2013-06-24 11:55:21 +00:00
print ( " using %s to match build platforms " % ( file ) )
2013-03-11 14:31:26 +00:00
text = open ( file ) . read ( )
2019-07-16 08:08:00 +00:00
text = text . replace ( ' " ' , ' ' )
2013-03-11 14:31:26 +00:00
a = { }
for i in ( re . split ( " [ \ s_:= \ ( \ )]+ " , text ) ) :
2019-07-16 08:08:00 +00:00
a [ i ] = 2
2013-03-11 14:31:26 +00:00
for i in ( re . split ( " [ \ s:= \ ( \ )]+ " , text ) ) :
2019-07-16 08:08:00 +00:00
a [ i ] = 3
if ' i386 ' in a and ' i586 ' not in a :
a [ ' i586 ' ] = a [ ' i386 ' ] - 1
if ' i386 ' in a and ' i686 ' not in a :
a [ ' i686 ' ] = a [ ' i386 ' ] - 1
if ' i586 ' in a and ' i386 ' not in a :
a [ ' i386 ' ] = a [ ' i586 ' ] - 1
if ' i586 ' in a and ' i686 ' not in a :
a [ ' i686 ' ] = a [ ' i586 ' ] - 1
if ' i686 ' in a and ' i386 ' not in a :
a [ ' i386 ' ] = a [ ' i686 ' ] - 1
if ' i686 ' in a and ' i586 ' not in a :
a [ ' i586 ' ] = a [ ' i686 ' ] - 1
2013-03-11 14:31:26 +00:00
# SUSE Linux Enterprise Desktop 11 (x86_64)
# VERSION = 11
# PATCHLEVEL = 1
###################
# -> SLE_11_SP1
m = re . match ( ' SUSE \ s+Linux \ s+Enterprise \ s+( \ w+) \ s+( \ d+) \ s ' , text , re . I )
if m :
2019-07-16 08:08:00 +00:00
v = m . group ( 2 )
s = ' S '
if m . group ( 1 ) == ' Desktop ' :
s = ' D '
a [ ' SLE ' + s + ' _ ' + v ] = 4
a [ ' SLE ' ' _ ' + v ] = 4
m2 = re . search ( ' ^PATCHLEVEL \ s*= \ s*( \ d+) ' , text , re . I | re . M )
if m2 :
sp = m2 . group ( 1 )
a [ ' SLE ' + s + ' _ ' + v + ' _SP ' + sp ] = 5
a [ ' SLE ' ' _ ' + v + ' _SP ' + sp ] = 5
if opts . verbose :
print ( " _read_system_name( %s ) -> ' %s ' " % ( file , a ) )
2013-03-11 14:31:26 +00:00
self . system_name_words = a
return a
2019-07-16 08:08:00 +00:00
2013-03-11 14:31:26 +00:00
def _best_platform ( self , etc_suse_release , repos , opts ) :
"""
given an etc_suse_release file , or a plain platform name ( repository )
this compares a list of repos , and returns the one which literally matches best .
It also compares with conf . config [ ' build_project ' ] and gives sanity warnings .
etc_suse_release is ignored , if platform is not None .
"""
2019-07-16 08:08:00 +00:00
default_platform = ' openSUSE_Tumbleweed '
2013-03-11 14:31:26 +00:00
platform_in = opts . platform
if opts . verbose :
2019-07-16 08:08:00 +00:00
print ( " _best_platform: etc_suse_release= %s , platform_in= %s , repos= %s " % ( etc_suse_release , platform_in , repos ) )
2013-03-11 14:31:26 +00:00
if platform_in :
2019-07-16 08:08:00 +00:00
platform_words = { platform_in : 1 }
2013-03-11 14:31:26 +00:00
else :
2019-07-16 08:08:00 +00:00
if hasattr ( self , ' system_name_words ' ) :
# initialized by calling _read_system_name() earlier.
platform_words = self . system_name_words
else :
platform_words = self . _read_system_name ( etc_suse_release , opts )
platform_words [ ' standard ' ] = 1 # a fallback
2013-03-11 14:31:26 +00:00
platform = None
build_platform = None
2019-07-16 08:08:00 +00:00
if ' build_project ' in conf . config :
build_platform = conf . config [ ' build_project ' ]
2013-03-11 14:31:26 +00:00
max_score = 0
if len ( repos ) :
2019-07-16 08:08:00 +00:00
for i in ( range ( 0 , len ( repos ) ) ) :
2020-10-13 20:00:25 +00:00
repos = list ( repos )
2019-07-16 08:08:00 +00:00
score = self . _matches_in_name ( repos [ i ] , platform_words )
if opts . verbose :
print ( " repo %s : score %s " % ( repos [ i ] , score ) )
if score > max_score :
max_score = score
platform = repos [ i ]
2013-03-11 14:31:26 +00:00
if build_platform :
2019-07-16 08:08:00 +00:00
score = self . _matches_in_name ( build_platform , platform_words )
if score > max_score :
if ( platform and build_platform != platform ) :
print ( " CAUTION: .oscrc:build_project %s disagrees with best matching platform %s " % ( build_platform , platform ) )
else :
platform = build_platform
if score < max_score :
print ( " CAUTION: .oscrc:build_project %s does not match: low score= %d " % ( build_platform , score ) )
2013-03-11 14:31:26 +00:00
if platform :
2019-07-16 08:08:00 +00:00
print ( " Best matching platform is %s " % ( platform ) )
if opts . platform and opts . platform != platform :
platform = opts . platform
print ( " cmdline takes precedence: -p %s " % ( opts . platform ) )
2013-03-11 14:31:26 +00:00
else :
2019-07-16 08:08:00 +00:00
platform = default_platform
print ( " Default platform= %s (no scores). Use ' build_project ' in ~/.oscrc or -p to override " % ( platform ) )
2013-03-11 14:31:26 +00:00
return platform
def _matches_in_name ( self , name , words ) :
"""
words is a dictionary of keywords with score values . Name is matched against each word .
A word can score up to 4 times : infix , prefix , suffix or exact match .
Any left - over wordlike tokens in name count ( slightly ) against the score .
"""
score = 0
remainder = name
for m in words . keys ( ) :
remainder = re . sub ( re . escape ( m ) , ' ' , remainder )
2019-07-16 08:08:00 +00:00
if name . find ( m ) > = 0 :
score + = 10 * words [ m ]
if name . startswith ( m ) :
score + = 10 * words [ m ]
if name . endswith ( m ) :
score + = 10 * words [ m ]
if name == m :
score + = 10 * words [ m ]
2013-03-11 14:31:26 +00:00
if len ( remainder ) :
2019-07-16 08:08:00 +00:00
remainder = re . sub ( ' [ _:]+ ' , ' ' , remainder )
# if opts.verbose:
# print("%s left-over pieces: %s" % (name, remainder.split()))
score - = len ( remainder . split ( ) )
2013-03-11 14:31:26 +00:00
return score
def _prefered_projects ( self , apiurl ) :
if len ( conf . config [ ' getpac_default_project ' ] ) :
2019-07-16 08:08:00 +00:00
candidates = re . split ( ' [, ]+ ' , conf . config [ ' getpac_default_project ' ] )
2013-03-11 14:31:26 +00:00
else :
2019-07-16 08:08:00 +00:00
candidates = [ ]
2013-03-11 14:31:26 +00:00
prio = { }
n = len ( candidates )
for c in candidates :
2019-07-16 08:08:00 +00:00
prio [ c ] = n
n - = 1
2013-03-11 14:31:26 +00:00
return prio
2019-07-16 08:08:00 +00:00
2013-03-11 14:31:26 +00:00
def _find_cached ( self , all , osc_cache ) :
2019-07-16 08:08:00 +00:00
for r in all :
# /var/tmp/osbuild-packagecache/devel:languages:perl/openSUSE_11.3/i586/perl-macros-1.0-14.1.i586.rpm
path = ' / ' . join ( [ osc_cache , r [ ' project ' ] , r [ ' repository ' ] , r [ ' arch ' ] , r [ ' filename ' ] ] )
st = None
try :
st = os . stat ( path )
except :
pass
if st :
r [ ' cached ' ] = { ' size ' : st . st_size , ' path ' : path }
2013-03-11 14:31:26 +00:00
def _search_projects ( self , apiurl , packname ) :
2019-07-16 08:08:00 +00:00
pref = self . _prefered_projects ( apiurl )
# GET https://api.opensuse.org/search/published/binary/id?match=@name='file_unpack'
# <collection matches="4">
# <binary name="file_unpack" project="devel:languages:perl"
# arch="noarch" filename="file_unpack-0.37-4.1.noarch.rpm"
# filepath="devel:/languages:/perl/openSUSE_11.2/noarch/file_unpack-0.37-4.1.noarch.rpm"
# baseproject="openSUSE:11.2" type="rpm" />
#...
# DOESNOTWORK: search cannot have keywords with slashes.
# collection = search(apiurl, 'published/binary/id'=xpath)
query = { ' match ' : " @name= ' %s ' " % packname }
u = makeurl ( apiurl , [ ' search ' , ' published ' , ' binary ' , ' id ' ] , query )
print ( u )
f = http_GET ( u )
collection = ET . parse ( f ) . getroot ( )
found = [ ]
for f in collection . findall ( ' binary ' ) :
found . append ( f . attrib )
found . sort ( key = lambda e : ( - pref . get ( e [ ' project ' ] , 0 ) , e [ ' project ' ] , e [ ' repository ' ] ) )
return found
# namespace clash: same method in osc-legal.py
2015-06-24 07:46:18 +00:00
def _user_prompt ( prompt , msg , injected ) :
2019-07-16 08:08:00 +00:00
if msg is not None :
msg = msg . rstrip ( )
2013-03-11 14:31:26 +00:00
if injected :
2019-07-16 08:08:00 +00:00
if msg is not None :
return msg + " \n " + injected
return injected
2013-06-24 11:55:21 +00:00
print ( prompt )
2019-07-16 08:08:00 +00:00
if msg is not None :
print ( " > " + msg )
2013-03-11 14:31:26 +00:00
sys . stdout . write ( " > " )
response = sys . stdin . readline ( ) . strip ( )
2019-07-16 08:08:00 +00:00
if msg is not None :
response = msg + " \n " + response
2013-03-11 14:31:26 +00:00
return response
2019-07-16 08:08:00 +00:00
2013-03-11 14:31:26 +00:00
class TeePopen ( ) :
"""
2019-07-16 08:08:00 +00:00
A popen - like redirector , that does not suffer from unexpected buffering .
It uses a PTY , to make the subprocess believe it is connected to a terminal ,
rather than a pipe . There are several disadvantages involved in this technique : one , the
pty module lacks signal handling ; second , stderr / stdout cannot be distinguished .
"""
def __init__ ( self , cmdv , tee_fd = None , silent = False , verbose = False ) :
if tee_fd is None :
self . tee = StringIO ( )
self . internal_fd = True
else :
self . tee = tee_fd
self . silent = silent if silent else ' '
if verbose :
print ( ' + ' , ' ' . join ( cmdv ) )
#
# python lambda is the only way to look into the surrounding scope.
# But then python lambda cannot do anything except a simple expression.
# hence we need both
# a method
# which can have assignments and multiple statements, but cannot see the scope.
# and a lambda
# which sees the scope, but cannot have assignements and multiple statements.
# and we need to pass in a reference, so that it is mutable from inside.
# using a one element array.
# Total: three ugly hacks, that would be a plain anonymous sub {} in perl.
import pty
# FIXME: this code has better signal handling than pty.spawn:
# http://code.google.com/p/lilykde/source/browse/trunk/runpty.py?r=314
# FIXME: sudo needs to reprompt for a password, when behind a pty
pty . spawn ( cmdv , lambda fd : self . tee_read ( fd ) )
def tee_read ( self , fd ) :
"""
subclass and overwrite this , if a tee ' ing to a file-like object is inadequate
tee_read is called whenever fd was found readable . fd is the masterside of a PTY .
"""
2020-10-13 20:00:25 +00:00
r = ( os . read ( fd , 1024 ) ) . decode ( )
2019-07-16 08:08:00 +00:00
self . tee . write ( r )
# shorten long hex strings and useless urls, they just look ugly.
r = re . sub ( ' Retrieving: [0-9a-f]+ \ - ' , ' Retrieving: ...- ' , r , re . M )
r = re . sub ( " Adding repository ' \ S+ ' " , ' Adding: ... ' , r , re . M )
r = re . sub ( " Building repository ' \ S+ ' cache " , ' Cache: ... ' , r , re . M )
2020-10-13 20:00:25 +00:00
return self . silent if self . silent else r . encode ( )
2019-07-16 08:08:00 +00:00
def __str__ ( self ) :
return self . tee . getvalue ( ) if self . internal_fd else self . tee
def __del__ ( self ) :
if self . internal_fd :
self . tee . close ( )
2015-06-24 07:46:18 +00:00
2013-03-11 14:31:26 +00:00
def _tee_from_cmd_stdout ( self , cmdv ) :
2019-07-16 08:08:00 +00:00
return self . _pipe_from_cmd_stdout ( cmdv , progress = None , term = None , tee = True )
2013-03-11 14:31:26 +00:00
def _pipe_from_cmd_stdout ( self , cmdv , progress = ' . ' , term = ' \n ' , tee = False ) :
2019-07-16 08:08:00 +00:00
""" modelled after subprocess.communicate()
handles only the trivial case of stdout pipe ,
but has a nice progress indicator .
Use tee = True to mirror stdout to stdout * and * return a copy .
"""
# bad: communicate blocks and buffers till the end.
# all = subprocess.Popen(['sudo', 'zypper', 'lr', '-e', '-'], stdout=subprocess.PIPE).communicate()[0]
# bad: cannot make password prompts via stderr appear, while suppressing stdout.
# FIXME: repeated sudo commands all ask again for password.
p = subprocess . Popen ( cmdv , stdout = subprocess . PIPE )
all = ' '
while True :
2020-10-13 20:00:25 +00:00
r = p . stdout . read ( ) . decode ( )
2019-07-16 08:08:00 +00:00
if tee :
sys . stdout . write ( r )
if progress is not None :
sys . stderr . write ( progress )
if len ( r ) == 0 :
break
all + = r
p . stdout . close ( )
p . wait ( )
if term is not None :
sys . stderr . write ( term )
return all