All of lore.kernel.org
 help / color / mirror / Atom feed
From: Kang Kai <Kai.Kang@windriver.com>
To: Joshua Lock <josh@linux.intel.com>
Cc: "poky@yoctoproject.org" <poky@yoctoproject.org>
Subject: Please help to review the Yocto 1656: Recipe creation/import script
Date: Thu, 19 Jan 2012 17:33:47 +0800	[thread overview]
Message-ID: <4F17E37B.7030506@windriver.com> (raw)
In-Reply-To: <4F17C7D9.10506@windriver.com>

[-- Attachment #1: Type: text/plain, Size: 300 bytes --]

Hi Josh,

The attachment is the the design document V2 and implement.

The V2 document changes are:
1 remove patch_set from argument list
2 deb is not support right now

Any comment is welcome.
And I will on vacation until Jan 30, so my reply may not in time.

Thanks&  Regards,
Kai



[-- Attachment #2: recipe_creation_bb.txt --]
[-- Type: text/plain, Size: 1027 bytes --]

    Design document for Recipe creation/import script

* Purpose

The feature is from Yocto 1.2 Bug 1656. A script or similar system
that would allow you to give is an upstream URL, tarball, patch set,
package (SRPM, or debian style) and generate a recipe based on those
instructions. 

Name it with bitbake-createbb, and integrate into the build system
to use the build system infrastructure that similiar with bitbake-runtask.

* Usage:
bitbake-createbb <SRC_URI> 

* Design Flow
1 download the source package from the SRC_URI
2 get package name and version from package or tarball name. 
3 unpack to /tmp/bitbake-createbb-pid
4 if tarball, get summary and description from freshmeat.org, README files and pkgconfig files;
  otherwise get them from SRPM spec file or debian control file
5 get license, license file and checksum from LICENSE/COPYING files or freshmeat.org. 
6 get dependent packages
  |-- get them by parse configure files
  |-- just parse spec file 
8 remove downloaded files




[-- Attachment #3: bitbake-createbb --]
[-- Type: text/plain, Size: 19557 bytes --]

#!/usr/bin/env python

import os
import sys
import shutil
import re
import urllib
import sha, md5, hashlib

tmpdir = "/tmp/bitbake_createbb.%s" % os.getpid()
bin_file_name = ''
pkgname = ''
pkgversion = ''
summary = ''
description = ''
homepage = ''
license = []
license_files = {}
licenses = {}
depends = []
src_uri = ''
src_uri_md5sum = ''
src_uri_sha256sum = ''
inherits = []

# support 2 package types: tar and rpm
pkgtype = ""

def usage():
    print "This is the bitbake import bb file script.  Usage:\n"
    print "\tbitbake-createbb <SRC_URI> \n"

def download(pkg_uri):
    try:
        shutil.rmtree(tmpdir)
    except:
        pass

    cwd = os.getcwd()
    os.mkdir(tmpdir)
    os.chdir(tmpdir)
    print 'Downloading package: ', pkg_uri, ' to ', tmpdir
    ret = os.system("wget --quiet " + pkg_uri)
    os.chdir(cwd)

    return ret

def guess_name_by_uri(uri):
    global bin_file_name, pkgname, pkgversion, pkgtype

    elems = uri.rsplit('/', 1)
    pkg = bin_file_name = elems[1]

    if pkg.endswith("tgz"):
        pkg = re.sub("tgz$", "tar.gz", pkg)

    match = re.match('(.*?)\-([0-9\.\-\~]+)\.tar', pkg)
    if match:
        pkgtype = 'tar'
        pkgname =  match.group(1)
        pkgversion = match.group(2)

    match = re.match('(.*?)\-([0-9\.\~]+).*?\.src.rpm', pkg)
    if match:
        pkgtype = 'rpm'
        pkgname = match.group(1)
        pkgversion = match.group(2)

def get_md5_sha256_sum():
    global src_uri_md5sum, src_uri_sha256sum

    curdir = os.getcwd()
    os.chdir(tmpdir)

    f = file(bin_file_name, 'r')
    content = f.read()
    f.close()

    md_five = md5.new()
    md_five.update(content)
    src_uri_md5sum = md_five.hexdigest()

    sha_256 = hashlib.sha256()
    sha_256.update(content)
    src_uri_sha256sum = sha_256.hexdigest()

    os.chdir(curdir)

def unpack_package():
    global bin_file_name

    cwd = os.getcwd()
    os.chdir(tmpdir)
    if pkgtype == 'rpm':
        cmdline = "rpm2cpio " + bin_file_name + " | cpio -id"
        ret = os.system(cmdline)
        if ret != 0:
            return ret
        for item in os.listdir('.'):
            if re.match(pkgname + '([0-9\.\-\~]*)\.tar', item):
                bin_file_name = item
                break

    cmdline = "tar axf " + bin_file_name
    ret = os.system(cmdline)

    os.chdir(cwd)
    return ret

# get the fall-back description when other way fail
# check homepage at same time
def guess_description_from_readme(readme):
    global description, homepage
    f = file(readme)
    state = 0
    desc = ''

    for line in f:
        if state == 1 and re.match('^\n', line) and len(desc) > 80:
            state = 2
        if state == 0 and len(line) > 1:
            state = 1
        if state == 1:
            desc += line

        match = re.search('(http\:\/\/.*$name.*\.org)', line)
        if match:
            url = match.group(1)
            if re.search('bug') or len(homepage) > 1:
                pass
            else:
                homepage = url

    f.close()
    if (len(desc) > 4 and len(description) < 3):
        description = desc

def guess_description_from_freecode(pkgname):
    global description
    desc = ''
    state = 0

    html = urllib.urlopen("http://freecode.com/projects/" + pkgname)
    for line in html:
        if state == 1:
            desc += line
        if state == 0 and re.search('\<div class\=\"project-detail\"\>', line):
            state = 1
        if state == 1 and re.search('\<\/p\>', line):
            state = 2

    # deal the description
    desc = re.sub('\<p\>', '', desc)
    desc = re.sub('\<\/p\>', '', desc)
    desc = re.sub('\r', '', desc)
    desc = desc.strip()
    if len(desc) > 10:
       description = desc

# get Summary from pkgconfig file
def guess_summary_from_pc(pc):
    global summary

    fn = file(pc)
    for line in fn:
        match = re.match('Description:\s*(.*)', line)
        if match and len(summary) < 2:
            summary = match.group(1)
            break

    summary = summary.strip()
    fn.close()

def guess_description(pkgdir):
    global pkgname, description, summary

    readmes = []
    pcs = []

    for subdir in os.listdir(pkgdir):
        if re.match('^README$', subdir):
            readmes.insert(0, os.path.join(pkgdir, subdir))
        elif re.match('README.*', subdir):
            readmes.append(os.path.join(pkgdir, subdir))
        elif re.match('.*\.pc.*', subdir):
            pcs.insert(0, os.path.join(pkgdir, subdir))
        elif re.match(pkgname + '\.pc.*', subdir):
            pcs.insert(0, os.path.join(pkgdir, subdir))
        elif re.match('.*\.pc', subdir):
            pcs.append(os.path.join(pkgdir, subdir))

    for readme in readmes:
        guess_description_from_readme(os.path.join(pkgdir, readme))

    if (len(pkgname) > 2):
        guess_description_from_freecode(pkgname)

    for pc in pcs:
        guess_summary_from_pc(pc)

    # if didn't get summary, use first line of description
    if len(summary) < 2:
        summary = description
        summary = re.sub("\n", " ", summary)
        summary = re.sub("\s+", " ", summary)
        match = re.match("(.*?)\.", summary)
        summary = match.group(1)

# the sha1sum values are from autospectacle
def setup_licenses():
    licenses['06877624ea5c77efe3b7e39b0f909eda6e25a4ec'] =  "GPLv2"
    licenses["075d599585584bb0e4b526f5c40cb6b17e0da35a"] = "GPLv2"
    licenses["10782dd732f42f49918c839e8a5e2894c508b079"] = "GPLv2"
    licenses["2d29c273fda30310211bbf6a24127d589be09b6c"] = "GPLv2"
    licenses["4df5d4b947cf4e63e675729dd3f168ba844483c7"] = "LGPLv2.1"
    licenses["503df7650052cf38efde55e85f0fe363e59b9739"] = "GPLv2"
    licenses["5405311284eab5ab51113f87c9bfac435c695bb9"] = "GPLv2"
    licenses["5fb362ef1680e635fe5fb212b55eef4db9ead48f"] = "LGPLv2"
    licenses["68c94ffc34f8ad2d7bfae3f5a6b996409211c1b1"] = "GPLv2"
    licenses["66c77efd1cf9c70d4f982ea59487b2eeb6338e26"] = "LGPLv2.1"
    licenses["74a8a6531a42e124df07ab5599aad63870fa0bd4"] = "GPLv2"
    licenses["8088b44375ef05202c0fca4e9e82d47591563609"] = "LGPLv2.1"
    licenses["8624bcdae55baeef00cd11d5dfcfa60f68710a02"] = "GPLv3"
    licenses["8e57ffebd0ed4417edc22e3f404ea3664d7fed27"] = "MIT"
    licenses["99b5245b4714b9b89e7584bfc88da64e2d315b81"] = "BSD"
    licenses["aba8d76d0af67d57da3c3c321caa59f3d242386b"] = "MPLv1.1"
    licenses["bf50bac24e7ec325dbb09c6b6c4dcc88a7d79e8f"] = "LGPLv2"
    licenses["caeb68c46fa36651acf592771d09de7937926bb3"] = "LGPLv2.1"
    licenses["dfac199a7539a404407098a2541b9482279f690d"] = "GPLv2"
    licenses["e60c2e780886f95df9c9ee36992b8edabec00bcc"] = "LGPLv2.1"
    licenses["c931aad3017d975b7f20666cde0953234a9efde3"] = "GPLv2"

def guess_licenses_from_file(copying, relname):
    global licenses, license

    sha1 = sha.new()

    fn = open(copying)
    content = fn.read()
    fn.close()

    sha1.update(content)
    digest = sha1.hexdigest()

    if digest in licenses:
        license.append(licenses[digest])
        md_five = md5.new()
        md_five.update(content)
        license_files[relname] = md_five.hexdigest()

def guess_licenses_from_freecode():
    global license

    lic = ''
    state = 0

    html = urllib.urlopen("http://freecode.com/projects/" + pkgname)
    for line in html:
        if state == 1:
            lic += line
        if state == 0 and re.search('<th><span>Licenses</span></th>', line):
            state = 1
        if state == 1 and re.search('</a>', line):
            state = 2

    # deal the license
    lic = lic.strip()
    match = re.search('<a.*?>(.*?)</a>', lic)
    if match:
        license.append(match.group(1))

def guess_license(pkgdir):
    for item in  os.listdir(pkgdir):
        realpath = os.path.join(pkgdir, item)
        if os.path.isfile(realpath) and (re.match('COPY.*', item) \
                or re.match('LICENSE.*', item) or re.match('GPL.*', item)):
            guess_licenses_from_file(realpath, item)

    if len(license) == 0:
        guess_licenses_from_freecode()

def process_configure(pkgdir):
    if os.path.isfile(os.path.join(pkgdir, 'autogen.sh')):
        os.system("cd " + pkgdir + " ; ./autogen.sh &> /dev/null");

    configure = os.path.join(pkgdir, 'configure')
    if os.path.isfile(configure):
        if 'autotools' not in inherits:
            inherits.append('autotools')

        fn = open(configure)
        for line in fn:
            match = re.match('PACKAGE_NAME=\'(.*?)\'', line)
            if match and len(pkgname) == 0:
                pkgname = match.group(1)

            match = re.match('PACKAGE_TARNAME=\'(.*?)\'', line)
            if match:
                pkgname = match.group(1)

            match = re.match('PACKAGE_VERSION=\'(.*?)\'', line)
            if match:
                pkgversion = match.group(1)

            match = re.match('PACKAGE_URL=\'(.*?)\'', line)
            if match:
                homepage = match.group(1)

        fn.close()

def push_buildreq(dep):
    global depends

    # remove collateral ] ) etc damage in the string
    dep = re.sub("\"", "", dep)
    dep = re.sub("\)", "", dep)
    dep = re.sub("\]", "", dep)
    dep = re.sub("\[", "", dep)

    # first, undo the space packing
    dep = re.sub(">=", " >= ", dep)
    dep = re.sub("<=", " <= ", dep)

    items = dep.split(' ')
    dep = items[0]

    # don't show configure variables, we can't deal with them
    if re.search("\$", dep):
        return
    if re.search("AC_SUBST", dep):
        return

    if dep not in depends:
        depends.append(dep)

def parse_configure_ac(ac_file):
    depth = 0
    clause = ""

    fac = file(ac_file)

    for line in fac:
        line = line.strip()
        i = 0
        while i < len(line):
            if line[i] == '(':
                depth += 1
            if line[i] == ')' and depth > 0:
                depth -= 1
            clause += line[i]
            i += 1
        if depth > 0:
            continue

        # remove '\n'
        clause = re.sub('\n', '', clause)
        clause = clause.strip()

        match = re.match('PKG_CHECK_MODULES\((.*)\)', clause)
        if match:
            modules = match.group(1)
            pkg = modules.split(',', 1)[1].strip()
            match2 = re.match('\[(.*)\]', pkg)
            if match2:
                pkg = match2.group(1)

            # split the build dependencies
            pkg = pkg.replace('\s+', ' ')
            pkg = re.sub('\s>\s', '>', pkg)
            pkg = re.sub('\s>=\s', '>=', pkg)
            pkg = re.sub('\s=\s', '=', pkg)
            pkg = re.sub('\s<=\s', '<=', pkg)
            pkg = re.sub('\s<\s', '<', pkg)
            pkglist = pkg.split(' ')
            for dep in pkglist:
                push_buildreq(dep)

        match = re.match('PKG_CHECK_EXISTS\((.*)\)', clause)
        if match:
            exists = match.group(1)
            pkg = exists.split(',', 1)[0].strip()
            match2 = re.match('\[(.*)\]', pkg)
            if match2:
                pkg = match2.group(1)
                pkg = pkg.strip()

            pkg = re.sub('\s+', ' ', pkg)
            pkg = re.sub('\s>\s', '>', pkg)
            pkg = re.sub('\s>=\s', '>=', pkg)
            pkg = re.sub('\s=\s', '=', pkg)
            pkg = re.sub('\s<=\s', '<=', pkg)
            pkg = re.sub('\s<\s', '<', pkg)
            pkglist = pkg.split(' ')
            for dep in pkglist:
                push_buildreq(dep)

        # these items are from autospectacle.pl
        if re.search('_PROG_INTLTOOL', clause):
            push_buildreq("intltool")
        if re.search('GETTEXT_PACKAGE', clause):
            push_buildreq('gettext')
        if re.search('GTK_DOC_CHECK', clause):
            push_buildreq('gtk-doc')
        if re.search('GNOME_DOC_INIT', clause):
            push_buildreq('gnome-doc-utils')
        if re.search('AM_GLIB_GNU_GETTEXT', clause):
            push_buildreq('gettext')

        match = re.search('AC_INIT\((.*)\)', clause)
        if match:
            ac_init = match.group(1)
            ac_init = ac_init.strip()
            ac_init = re.sub('\s+', ' ', ac_init)
            version = ac_init.split(',')[1].strip()
            if re.match('\d+(\.\d+)*', version):
                pkgversion = version

        match = re.search('AM_INIT_AUTOMAKE\((.*)\)', clause)
        if match:
            am_init = match.group(1)
            am_init = re.sub('\s+', ' ', am_init)
            items = am_init.split(',')
            if len(items) >= 2:
                ver = items[1]
                ver = re.sub('\[', '', ver)
                ver = re.sub('\]', '', ver)
                if re.match('\d(\.\d+)*', ver):
                    pkgversion = ver
        clause = ''
    fac.close

def process_configure_ac(pkgdir):
    configure_acs = []
    for root, dirnames, filenames in os.walk(pkgdir):
        for f in filenames:
            if re.match('.*\.ac', f):
                configure_acs.append(os.path.join(root, f))
                if 'autotools' not in inherits:
                    inherits.append('autotools')

    for ac in configure_acs:
        parse_configure_ac(ac)

# check the *.pro files and get build requires from 'PKGCONFIG'
def process_qmake_pro(pkgdir):
    global pkgname

    pro_files = []
    depth = 0
    clause = ''
    pre_char = ''

    if os.path.isfile(os.path.join(pkgdir, pkgname + '.pro')) and 'qmake2' not in inherits:
        inherits.append('qmake2')
    for root, dirnames, filenames in os.walk(pkgdir):
        for f in filenames:
            if re.match('.*\.pro', f):
                pro_files.append(os.path.join(pkgdir, f))

    for pro in pro_files:
        need_next_line = False
        f = file(pro)
        for line in f:
            if line.startswith('#'):
                continue
            line = re.sub('\n', '', line)
            clause += line
            if line.endswith('\\'):
                continue

            clause = clause.strip()
            match =  re.match('PKGCONFIG\s*=(.*)', clause)
            if match:
                dep = match.group(1)
                push_buildreq(dep)
            match = re.match('PKGCONFIG\s*\+=(.*)', clause)
            if match:
                dep = match.group(1)
                dep = dep.strip()
                dep = re.sub('\\\\', '', dep)
                dep = re.sub('\s+', ' ', dep)
                items = dep.split(' ')
                for item in items:
                    push_buildreq(item)

            clause = ''

        f.close()

def parse_spec_file(tmpdir):
    # find the spec file first
    spec_file = ''
    for item in os.listdir(tmpdir):
        if re.match('.*\.spec', item):
            spec_file = os.path.join(tmpdir, item)
            break

    if len(spec_file) == 0:
        return 1

    global pkgname, pkgversion, summary, description, depends
    global src_uri, homepage

    f = file(spec_file)
    for line in f:
        match = re.match('Summary\s*:(.*)', line)
        if match:
            if len(summary) == 0:
                summary = match.group(1).strip()
            continue
        match = re.match('Name\s*:(.*)', line)
        if match:
            pkgname = match.group(1).strip()
            continue
        match = re.match('Version\s*:(.*)', line)
        if match:
            version = match.group(1).strip()
            if re.match('\d+(\.\d+)?', version):
                pkgversion = version
            continue
        match = re.match('License\s*:(.*)', line)
        if match:
            license.append(match.group(1).strip())
            continue
        match = re.match('URL(?i)\s*:(.*)', line)
        if match:
            homepage = match.group(1).strip()
            continue
        match = re.match('Source0*\s*:(.*)', line)
        if match:
            src_uri = match.group(1).strip()
            src_uri = re.sub('\%\{name\}', pkgname, src_uri)
            src_uri = re.sub('\%\{version\}', pkgversion, src_uri)
            continue
        match = re.match('BuildRequires\s*:(.*)', line)
        if match:
            deps = match.group(1).strip()
            dpes = re.sub(',', ' ', deps)
            deps = re.sub('\s+', ' ', deps)
            for item in deps.split(' '):
                push_buildreq(item)

        match = re.match('%description\s*$', line)
        if match:
            for l in f:
                if l.startswith('%'):
                    break
                description += l
            description = re.sub('\n', '', description)

    f.close()

def write_bbfile():
    if len(pkgname) == 0 or len(pkgversion) == 0:
        fail_quit("Can't get package name or version.")

    content = ''
    content += 'DESCRIPTION = "' + description + '"\n'
    content += 'SUMMARY = "' + summary + '"\n'
    if len(homepage) > 0:
        content += 'HOMEPAGE = "' + homepage + '"\n'

    content += 'LICENSE = "'
    for lic in license:
        content += lic + ' '
    content += '"\n'
    content += 'LIC_FILES_CHKSUM = "'
    for key in license_files:
        content += 'file://' + key + ';md5=' + license_files[key] + ' \\\n';
    content += '\t\t"\n\n';

    if len(depends) > 0:
        content += 'DEPENDS = "'
        for dep in depends:
            content += dep + ' '
        content += ' "\n'

    content += 'PR = "r0"\n\n'

    content += 'SRC_URI = "' + src_uri + '"\n'
    content += 'SRC_URI[md5sum] = "' + src_uri_md5sum + '"\n'
    content += 'SRC_URI[sha256sum] = "' + src_uri_sha256sum + '"\n'

    if len(inherits) > 0:
        content += '\ninherit '
        for inherit in inherits:
            content += inherit + ' '
        content += '\n'

    bb_filename = pkgname + '_' + pkgversion + '.bb'
    bb_file = open(bb_filename, 'w')
    bb_file.write(content)
    bb_file.close()
    print 'Create bb file : ', bb_filename

def fail_quit(msg):
    print msg
    clean_up()
    exit(1)

def clean_up():
    shutil.rmtree(tmpdir)
    pass

##########################################################

if __name__ == '__main__':
    if (len(sys.argv) < 2):
        usage()
        exit(1)

    src_uri = sys.argv[1]

    guess_name_by_uri(src_uri)
    if pkgname is None:
        print "Can't get the package name."
        exit(1)

    ret = download(src_uri)
    if ret != 0:
        fail_quit('Download package failed. Make sure the SRC_URI is valid.')

    if unpack_package() != 0:
        fail_quit('Unpack package failed.')

    if pkgtype == 'rpm':
        parse_spec_file(tmpdir)

    get_md5_sha256_sum()

    pkgdir = ''
    for subdir in os.listdir(tmpdir):
        subdir = os.path.join(tmpdir, subdir)
        if os.path.isdir(subdir):
            pkgdir = subdir
            break

    if pkgtype == 'tar':
        parse_spec_file(pkgdir)

    guess_description(pkgdir)
    process_configure(pkgdir)

    setup_licenses()
    guess_license(pkgdir)

    process_configure_ac(pkgdir)
    process_qmake_pro(pkgdir)

    write_bbfile()
    clean_up()


       reply	other threads:[~2012-01-19  9:33 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <4F17C7D9.10506@windriver.com>
2012-01-19  9:33 ` Kang Kai [this message]
2012-01-19 11:57   ` Please help to review the Yocto 1656: Recipe creation/import script Paul Eggleton
2012-01-19 16:33   ` Mark Hatle
2012-01-30  2:21     ` Kang Kai
2012-01-20  1:57   ` Joshua Lock

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=4F17E37B.7030506@windriver.com \
    --to=kai.kang@windriver.com \
    --cc=josh@linux.intel.com \
    --cc=poky@yoctoproject.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.