Change --spec to use only base file name
authorHuang Hao <hao.h.huang@intel.com>
Mon, 12 Nov 2012 10:05:48 +0000 (18:05 +0800)
committerHuang Hao <hao.h.huang@intel.com>
Tue, 20 Nov 2012 07:19:53 +0000 (15:19 +0800)
Detail of discussion is in #485

* --spec accept only base file name
* only check spec file in working copy if --include-all is given,
  otherwise check in commit
* if gbs want to parse spec file, it must parse the correct
  version that in --commit(default HEAD)
* unify --spec for all subcommands
  - ch: use the spec given first
  - rb: --include-all means to use work copy, otherwise checkout
    spec in some revision to parse
  - lb: check only one project is about to build and
    pass --spec directly to depanneur
* add several helper functions
  - show_file_from_rev: cat file content from given rev
  - file_exists_in_rev: check whether file exist in given rev
  - glob_in_rev: glob file pattern in given rev

Change-Id: I0621634b50ddedf65eab3c41db4d6f537194b345

gitbuildsys/cmd_build.py
gitbuildsys/cmd_changelog.py
gitbuildsys/cmd_export.py
gitbuildsys/cmd_remotebuild.py
gitbuildsys/parsing.py
gitbuildsys/utils.py
tools/gbs

index 34704dd1a56dcbd5b2eb2015ec865a2a6a429411..f244feca4c2f9307cf1ca8ba7f1245ad65527b5a 100644 (file)
@@ -234,13 +234,9 @@ def main(args):
         repo = RpmGitRepository(workdir)
         workdir = repo.path
     except GitRepositoryError:
-        pass
-
-    if args.spec:
-        try:
-            RpmGitRepository(os.path.dirname(args.spec))
-        except GitRepositoryError:
-            msger.error('spec file should reside in git directory')
+        if args.spec:
+            msger.error("git project can't be found for --spec, "
+                        "give it in argument or cd into it")
 
     hostarch = get_hostarch()
     if args.arch:
@@ -317,7 +313,7 @@ def main(args):
     if args.define:
         cmd += [('--define="%s"' % i) for i in args.define]
     if args.spec:
-        cmd += [args.spec]
+        cmd += ['--spec=%s' % args.spec]
 
     msger.debug("running command: %s" % ' '.join(cmd))
     retcode = os.system(' '.join(cmd))
index fbcab0f24fb132642de9671e3c1df692a9be1632..8f0ee4e42e26bc22ae7f0d73af9485f048b7a8b8 100644 (file)
@@ -94,15 +94,17 @@ def main(args):
     changes_file_list = glob.glob("%s/%s/*.changes" % (project_root_dir,
                                                        packaging_dir))
 
-    if changes_file_list:
+    if args.spec or not changes_file_list:
+        # Create .changes file with the same name as a spec
+        specfile = os.path.basename(utils.guess_spec(project_root_dir,
+                                                     packaging_dir, args.spec))
+        fn_changes = os.path.splitext(specfile)[0] + ".changes"
+        fn_changes = os.path.join(project_root_dir, packaging_dir, fn_changes)
+    else:
         fn_changes = changes_file_list[0]
         if len(changes_file_list) > 1:
             msger.warning("Found more than one changes files, %s is taken " \
                            % (changes_file_list[0]))
-    else:
-        # Create .changes file with the same name as a spec
-        specfile = utils.guess_spec(project_root_dir, packaging_dir, args.spec)
-        fn_changes = os.path.splitext(specfile)[0] + ".changes"
 
     # get the commit start from the args.since
     if args.since:
index 056297198f20f6112ed5df68cd8c3ccf636b3cde..349b570bfc35ff4aa8ba2c365ca3725df0d292ea 100644 (file)
@@ -186,7 +186,13 @@ def main(args):
 
     # Only guess spec filename here, parse later when we have the correct
     # spec file at hand
-    specfile = utils.guess_spec(workdir, packaging_dir, args.spec)
+    if args.commit:
+        commit = args.commit
+    elif args.include_all:
+        commit = 'WC.UNTRACKED'
+    else:
+        commit = 'HEAD'
+    relative_spec = utils.guess_spec(workdir, packaging_dir, args.spec, commit)
 
     outdir = "%s/packaging" % workdir
     if args.outdir:
@@ -199,18 +205,11 @@ def main(args):
     export_dir = tempd.path
 
     with utils.Workdir(workdir):
-        if args.commit:
-            commit = args.commit
-        elif args.include_all:
-            commit = 'WC.UNTRACKED'
-        else:
-            commit = 'HEAD'
-        relative_spec = specfile.replace('%s/' % workdir, '')
         export_sources(repo, commit, export_dir, relative_spec, args)
 
+    specfile = os.path.basename(relative_spec)
     try:
-        spec = rpm.parse_spec(os.path.join(export_dir,
-                                           os.path.basename(specfile)))
+        spec = rpm.parse_spec(os.path.join(export_dir, specfile))
     except GbpError, err:
         msger.error('%s' % err)
 
index 998937830123a0d3ba0e8eaf0f937233e1298f98..d8d18fcbe2274fc283677da842d6a52cb7ca9211 100644 (file)
@@ -82,14 +82,32 @@ def main(args):
 
     # TODO: check ./packaging dir at first
     packaging_dir = get_packaging_dir(args)
-    specs = glob.glob('%s/%s/*.spec' % (workdir, packaging_dir))
-    if not specs:
-        msger.error("no spec file found under './%s'" % packaging_dir)
 
-    specfile = utils.guess_spec(workdir, packaging_dir, args.spec)
+    if args.commit:
+        commit = args.commit
+    elif args.include_all:
+        commit = 'WC.UNTRACKED'
+    else:
+        commit = 'HEAD'
+
+    relative_spec = utils.guess_spec(workdir, packaging_dir, args.spec, commit)
+
+    if args.include_all:
+        # include_all means to use work copy,
+        # otherwise use the reversion in git history
+        spec_to_parse = os.path.join(workdir, relative_spec)
+    else:
+        content = utils.show_file_from_rev(workdir, relative_spec, commit)
+        if content is None:
+            msger.error('failed to checkout %s '
+                        'from commit: %s' % (relative_spec, commit))
+
+        tmp_spec = utils.Temp(content=content)
+        spec_to_parse = tmp_spec.path
+
     # get 'name' and 'version' from spec file
     try:
-        spec = gbp.rpm.parse_spec(specfile)
+        spec = gbp.rpm.parse_spec(spec_to_parse)
     except GbpError, err:
         msger.error('%s' % err)
 
@@ -165,13 +183,6 @@ def main(args):
         msger.error(str(err))
 
     with utils.Workdir(workdir):
-        if args.commit:
-            commit = args.commit
-        elif args.include_all:
-            commit = 'WC.UNTRACKED'
-        else:
-            commit = 'HEAD'
-        relative_spec = specfile.replace('%s/' % workdir, '')
         export_sources(repo, commit, exportdir, relative_spec, args)
 
     try:
index 5c29da9c1bcb7728fe302a682b8a12930f84007d..776d44c4cd969604223311969d75fb7c33ecafef 100644 (file)
 
 """Local additions to commandline parsing."""
 
+import os
 import re
 import functools
 
-from argparse import RawDescriptionHelpFormatter
+from argparse import RawDescriptionHelpFormatter, ArgumentTypeError
 
 class GbsHelpFormatter(RawDescriptionHelpFormatter):
     """Changed default argparse help output by request from cmdln lovers."""
@@ -86,3 +87,10 @@ def subparser(func):
         subpar.set_defaults(module="cmd_%s" % name)
         return func(subpar)
     return wrapper
+
+
+def basename_type(path):
+    '''validate function for base file name argument'''
+    if os.path.basename(path) != path:
+        raise ArgumentTypeError('should be a file name rather than a path')
+    return path
index b2c5385e132c4ac217e4b45a4dba1cf5ee189013..04a06ed59b0716ba816342c7a85ce0053fe423dd 100644 (file)
@@ -22,7 +22,9 @@ import tempfile
 import shutil
 import pycurl
 import hashlib
+import fnmatch
 import signal
+import subprocess
 import xml.etree.ElementTree as ET
 from collections import defaultdict
 
@@ -43,31 +45,34 @@ class Workdir(object):
     def __exit__(self, _type, _value, _tb):
         os.chdir(self._cwd)
 
-def guess_spec(workdir, packaging_dir, default_spec):
-    workdir = os.path.abspath(workdir)
-    git_project =  os.path.basename(workdir)
-
-    if default_spec:
-        default_spec = os.path.abspath(default_spec)
-        if not default_spec.startswith(workdir):
-            msger.error("spec file doesn't belong to the project %s: "
-                        "%s" % (git_project, default_spec))
-        if not os.path.exists(default_spec):
-            msger.error('%s does not exit' % default_spec)
-        return default_spec
-
-    specfile = os.path.join(workdir, packaging_dir, '%s.spec' % git_project)
-    if not os.path.exists(specfile):
-        specs = glob.glob(os.path.join(workdir, packaging_dir, '*.spec'))
-        if not specs:
-            msger.error('no spec file found under %s/%s' % (workdir,
-                                                            packaging_dir))
-
-        if len(specs) > 1:
-            msger.error("Can't decide which spec file to use.")
-        else:
-            specfile = specs[0]
-    return specfile
+def guess_spec(git_path, packaging_dir, given_spec, commit_id='WC.UNTRACKED'):
+    '''guess spec file from project name if not given'''
+    git_path = os.path.abspath(git_path)
+
+    if commit_id == 'WC.UNTRACKED':
+        check = lambda fname: os.path.exists(os.path.join(git_path, fname))
+        glob_ = lambda pattern: [ name.replace(git_path+'/', '')
+            for name in glob.glob(os.path.join(git_path, pattern)) ]
+        msg = 'No such spec file %s'
+    else:
+        check = lambda fname: file_exists_in_rev(git_path, fname, commit_id)
+        glob_ = lambda pattern: glob_in_rev(git_path, pattern, commit_id)
+        msg = "No such spec file %%s in %s" % commit_id
+
+    if given_spec:
+        spec = os.path.join(packaging_dir, given_spec)
+        if not check(spec):
+            msger.error(msg % spec)
+        return spec
+
+    specs = glob_(os.path.join(packaging_dir, '*.spec'))
+    if not specs:
+        msger.error("can't find any spec file")
+
+    project_name =  os.path.basename(git_path)
+    spec = os.path.join(packaging_dir, '%s.spec' % project_name)
+    return spec if spec in specs else specs[0]
+
 
 class Temp(object):
     """
@@ -460,3 +465,45 @@ def hexdigest(fhandle, block_size=4096):
         md5obj.update(data)
     return md5obj.hexdigest()
 
+
+def show_file_from_rev(git_path, relative_path, commit_id):
+    '''return a single file content from given rev.'''
+
+    cmd = 'cd %s; git show %s:%s' % (git_path, commit_id, relative_path)
+    try:
+        return subprocess.check_output(cmd, shell=True)
+    except (subprocess.CalledProcessError, OSError), err:
+        msger.debug('failed to checkout %s from %s:%s' % (relative_path,
+                                                          commit_id, str(err)))
+    return None
+
+
+def file_exists_in_rev(git_path, relative_path, commit_id):
+    '''return True if file exists in given rev'''
+
+    cmd = 'cd %s; git ls-tree --name-only %s %s' % (
+        git_path, commit_id, relative_path)
+
+    try:
+        output = subprocess.check_output(cmd, shell=True)
+    except (subprocess.CalledProcessError, OSError), err:
+        raise errors.GbsError('failed to check existence of %s in %s:%s' % (
+            relative_path, commit_id, str(err)))
+
+    return output != ''
+
+
+def glob_in_rev(git_path, pattern, commit_id):
+    '''glob pattern in given rev'''
+
+    path = os.path.dirname(pattern)
+    cmd = 'cd %s; git ls-tree --name-only %s %s/' % (
+        git_path, commit_id, path)
+
+    try:
+        output = subprocess.check_output(cmd, shell=True)
+    except (subprocess.CalledProcessError, OSError), err:
+        raise errors.GbsError('failed to glob %s in %s:%s' % (
+            pattern, commit_id, str(err)))
+
+    return fnmatch.filter(output.splitlines(), pattern)
index 1faeb63e74447d3066a59269ce130fbdaada73ef..3ac2de93e6098d9615bfc8e1d016106e3b2a330f 100755 (executable)
--- a/tools/gbs
+++ b/tools/gbs
 import sys
 import os
 
-from argparse import ArgumentParser, ArgumentTypeError
+from argparse import ArgumentParser
 
 from gitbuildsys import __version__
 from gitbuildsys import msger, errors
-from gitbuildsys.parsing import subparser, GbsHelpFormatter
-
-
-def abspath_arg(arg):
-    '''validate path argument and convert to absolute format'''
-    path = os.path.abspath(arg)
-    if not os.path.exists(path):
-        raise ArgumentTypeError('no such %s' % path)
-    return path
+from gitbuildsys.parsing import subparser, GbsHelpFormatter, basename_type
 
 
 @subparser
@@ -69,7 +61,7 @@ def import_parser(parser):
 def export_parser(parser):
     """export files and prepare for build
     Examples:
-      $ gbs export --spec packaging/my.spec --commit d64065c
+      $ gbs export --spec my.spec --commit d64065c
       $ gbs export --source-rpm -o /tmp/
       $ gbs export --include-all
     """
@@ -79,7 +71,9 @@ def export_parser(parser):
                         help='path to git repository')
 
     parser.add_argument('-o', '--outdir', help='output directory')
-    parser.add_argument('--spec', help='specify a spec file to use')
+    parser.add_argument('--spec', type=basename_type,
+                        help='specify a spec file to use. It should be a file '
+                        'name that GBS will find it in packaging dir')
     parser.add_argument('-c', '--commit', help='specify a commit ID to export')
     parser.add_argument('--include-all', action='store_true',
                         help='uncommitted changes and untracked files '
@@ -152,8 +146,9 @@ def build_parser(parser):
     parser.add_argument('--skip-conf-repos', action="store_true",
                         help='skip repositories mentioned in config file')
     parser.add_argument('-c', '--commit', help='specify a commit ID to build')
-    parser.add_argument('--spec', type=abspath_arg,
-                        help='specify a spec file to use')
+    parser.add_argument('--spec', type=basename_type,
+                        help='specify a spec file to use. It should be a file '
+                        'name that GBS will find it in packaging dir')
     parser.add_argument('--extra-packs',
                         help='specify extra packages to install to build root '
                         'multiple packages can be separated by comma')
@@ -244,7 +239,9 @@ def remotebuild_parser(parser):
     parser.add_argument('-P', '--profile',
                         help='profile to be used for building, can be given '
                              'without the "profile." prefix')
-    parser.add_argument('--spec', help='specify a spec file to use')
+    parser.add_argument('--spec', type=basename_type,
+                        help='specify a spec file to use. It should be a file '
+                        'name that GBS will find it in packaging dir')
     parser.add_argument('-c', '--commit', help='specify a commit ID to build')
     parser.add_argument('--buildlog', action='store_true',
                         help='get buildlog from build sever')
@@ -307,7 +304,9 @@ def changelog_parser(parser):
                         default=os.getcwd(),
                         help='path to git repository')
 
-    parser.add_argument('--spec', help='specify a spec file to use')
+    parser.add_argument('--spec', type=basename_type,
+                        help='specify a spec file to use. It should be a file '
+                        'name that GBS will find it in packaging dir')
     parser.add_argument('-s', '--since',
                         help='commit to start from')
     parser.add_argument('-m', '--message',