Esempio n. 1
0
 def execute_in(self, dir, start_time):
   verbose(self.argv)
   self.build_start = time.time()
   rc = subprocess.call(self.argv, cwd=dir, stdin=open(os.devnull))
   self.build_end = time.time()
   atexit.register(GMakeCommand.printstats, self, start_time)
   return rc
Esempio n. 2
0
    def update(self, key, basedir, bldtime, baseurl, replace):
        prereqs = {}
        intermediates = {}
        terminals = {}
        unused = {}
        # Note: do NOT use os.walk here.
        # It has a way of updating symlink atimes.
        def visit(data, parent, files):
            if not parent.startswith(".svn"):
                for fn in files:
                    if fn == self.ref_file:
                        continue
                    path = os.path.join(parent, fn)
                    if not os.path.isdir(path):
                        rpath = os.path.relpath(path, basedir)
                        stats = os.lstat(path)
                        adelta = stats.st_atime - self.reftime
                        mdelta = stats.st_mtime - self.reftime
                        if mdelta >= 0:
                            if adelta >= 0 and rpath in self.pre_existing:
                                prereqs[rpath] = "P"
                            elif adelta > mdelta:
                                intermediates[rpath] = "I"
                            else:
                                terminals[rpath] = "T"
                        elif adelta >= 0 and rpath in self.pre_existing:
                            prereqs[rpath] = "P"
                        else:
                            unused[rpath] = "U"

        os.path.walk(basedir, visit, None)

        self.new_targets.update(intermediates)
        self.new_targets.update(terminals)

        if not prereqs:
            warnings.warn("empty prereq set - check for 'noatime' mount")
        elif replace:
            refstr = "%s (%s)" % (str(self.reftime), time.ctime(self.reftime))
            self.db[key] = {
                "PREREQS": prereqs,
                "INTERMEDIATES": intermediates,
                "TERMINALS": terminals,
                "UNUSED": unused,
                "COMMENT": {"BLDTIME": bldtime, "CMDLINE": sys.argv, "REFTIME": refstr, "BASEURL": baseurl},
            }
            verbose("Updating database for '%s'" % (key))
            with open(self.dbfile, "w") as fp:
                json.dump(self.db, fp, indent=2)
                fp.write("\n")
Esempio n. 3
0
def main(argv):
  """Read a build audit and dump the data in various formats."""

  prog = os.path.basename(argv[0])

  parser = argparse.ArgumentParser()
  parser.add_argument('-a', '--print-all', action='store_true',
          help='Print all involved files for key(s)')
  parser.add_argument('-b', '--build-time', action='store_true',
          help='Print the elapsed time of the specified build(s)')
  parser.add_argument('-D', '--dbname',
          help='Path to a database file')
  parser.add_argument('-d', '--print-directories', action='store_true',
          help='Print directories containing prereqs for key(s)')
  parser.add_argument('-E', '--svn-export-dirs',
          help='Build a tree containing all files from prerequisite dirs in DIR')
  parser.add_argument('-e', '--svn-export-files',
          help='Build a tree containing just the prerequisites in DIR')
  parser.add_argument('-I', '--print-intermediates', action='store_true',
          help='Print intermediates for the given key(s)')
  parser.add_argument('-k', '--keys', action='append',
          help='List of keys to query')
  parser.add_argument('-l', '--list-keys', action='store_true',
          help='List all known keys in the given database')
  parser.add_argument('-p', '--print-prerequisites', action='store_true',
          help='Print prerequisites for the given key(s)')
  parser.add_argument('-s', '--print-sparse-file',
          help='Print a ".sparse" file covering the set of prereqs')
  parser.add_argument('-T', '--print-terminal-targets', action='store_true',
          help='Print terminal targets for the given key(s)')
  parser.add_argument('-t', '--print-targets', action='store_true',
          help='Print all targets for the given key(s)')
  parser.add_argument('-u', '--print-unused', action='store_true',
          help='Print files present but unused for key(s)')
  parser.add_argument('-v', '--verbosity', type=int,
          help='Change the amount of verbosity')
  opts = parser.parse_args(argv[1:])

  if (len(argv) < 2):
    main([argv[0], "-h"])

  if not [o for o in vars(opts) if opts.__dict__[o] is not None]:
    main([argv[0], "-h"])

  shared.verbosity = opts.verbosity if opts.verbosity is not None else 1

  rc = 0

  if opts.dbname:
    audit = BuildAudit(opts.dbname)
  else:
    audit = BuildAudit()

  # Check that a database was found
  with open(audit.dbfile):
    pass

  if opts.keys:
    keylist = opts.keys
    for key in keylist:
      if not audit.has(key):
        print >> sys.stderr, "%s: Error: no such key: %s" % (prog, key)
        sys.exit(2)
  else:
    keylist = audit.all_keys()
    if opts.list_keys:
      for key in keylist:
        print key
      sys.exit(0)
    else:
      verbose("Using keys: %s" % (keylist))
      if not keylist:
        sys.exit(2)

  if opts.build_time:
    for key in keylist:
      print "%s: %s" % (key, audit.bldtime(key))
  elif opts.print_sparse_file is not None:
    if len(opts.print_sparse_file) > 0:
      print '#', opts.print_sparse_file
    print '['
    print "   (%-*s  'files')," % (60, "'./',")
    for dir in sorted(dirnames(audit.old_prereqs(keylist))):
      path = "'" + dir + "/',"
      print "   (%-*s  'files')," % (60, path)
    print ']'
  elif opts.svn_export_dirs:
    rc = svn_export_dirs(audit.baseurl(keylist[0]), opts.svn_export_dirs, audit.old_prereqs(keylist))
  elif opts.svn_export_files:
    rc = svn_export_files(audit.baseurl(keylist[0]), opts.svn_export_files, audit.old_prereqs(keylist))
  elif opts.print_directories:
    for line in sorted(dirnames(audit.old_prereqs(keylist))):
      print line
  else:
    results = {}
    if opts.print_prerequisites:
      results.update(audit.old_prereqs(keylist))
    if opts.print_intermediates:
      results.update(audit.old_intermediates(keylist))
    if opts.print_terminal_targets:
      results.update(audit.old_terminals(keylist))
    if opts.print_targets:
      results.update(audit.old_targets(keylist))
    if opts.print_all:
      results.update(audit.old_prereqs(keylist))
      results.update(audit.old_targets(keylist))
    if opts.print_unused:
      results.update(audit.old_unused(keylist))
    for line in sorted(results):
      print line

  return rc
Esempio n. 4
0
def main(argv):
  """Do an audited GNU make build, optionally copied to a different directory.

  The default build auditing mechanism here is the simplest possible:
  The build is started and the starting time noted. When it finishes,
  each file in the build tree is stat-ed. If its modification time
  (mtime) is newer than the starting time it's considered to be
  a build artifact, aka target. If only the access time (atime)
  is newer, it's considered a source file, aka prerequisite. If
  neither, it was unused. Results are kept in a simple dictionary
  structure keyed by build target.

  A mechanism is included for copying required files into a different
  directory tree, building there, and copying the results back.
  The most common use case would be to offload from an NFS-mounted
  area to a local filesystem, for speed reasons, but that is not
  required. The requirement is that the build filesystem must update
  access times, i.e. not be mounted with the "noatime" option. NFS
  mounts often employ noatime as an optimization.

  """

  start_time = time.time()

  parser = argparse.ArgumentParser()
  parser.add_argument('-b', '--base-of-tree',
          help='Path to root of source tree')
  parser.add_argument('-c', '--clean', action='store_true',
          help='Force a clean ahead of the build')
  parser.add_argument('-D', '--dbname',
          help='Path to a database file')
  parser.add_argument('-E', '--extract-dirs-with-fallback',
          help='Pre-populate the build tree from DB or BOM')
  parser.add_argument('-e', '--edit', action='store_true',
          help='Fix up generated text files: s/<external-base>//')
  parser.add_argument('-f', '--fresh', action='store_true',
          help='Regenerate data for current build from scratch')
  parser.add_argument('-k', '--key',
          help='A key to uniquely describe what was built')
  parser.add_argument('-p', '--prebuild', action='append',
          default=['test ! -d src/include || REUSE_VERSION=1 make -C src/include'],
          help='Setup command(s) to be run prior to the build proper')
  parser.add_argument('-R', '--remove-external-tree', action='store_true',
          help='Remove the external build tree before exiting')
  parser.add_argument('-r', '--retry-in-place', action='store_true',
          help='Retry failed external builds in the current directory')
  parser.add_argument('-U', '--base-url',
          help='The svn URL from which to get files')
  parser.add_argument('-v', '--verbosity', type=int,
          help='Change the amount of verbosity')
  parser.add_argument('-X', '--execute-only', action='store_true',
          help='Skip the auditing and just exec the build command')
  parser.add_argument('-x', '--external-base',
          help='Path of external base directory')

  parser.add_argument('build_command', nargs='+')

  if (len(argv) > 1):
    opts = parser.parse_args(argv[1:])
    if not opts.build_command:
      main([argv[0], "-h"])
  else:
    main([argv[0], "-h"])

  shared.verbosity = opts.verbosity if opts.verbosity is not None else 1

  base_dir = os.path.abspath(opts.base_of_tree if opts.base_of_tree else '.')

  cwd = os.getcwd()

  if opts.external_base or os.getenv('AB_EXTERNAL_BASE') is not None:
    xd = (opts.external_base if opts.external_base else os.getenv('AB_EXTERNAL_BASE'))
    external_base = os.path.abspath(xd)
    build_base = os.path.abspath(external_base + os.sep + base_dir)
    bwd = os.path.abspath(external_base + os.sep + cwd)
  else:
    if opts.edit:
      parser.error("the --edit option makes no sense without --external-base")
    external_base = None
    build_base = base_dir
    bwd = cwd

  bldcmd = GMakeCommand(opts.build_command)
  bldcmd.directory = bwd

  if opts.dbname:
    audit = BuildAudit(opts.dbname)
  else:
    audit = BuildAudit(dbdir=bldcmd.subdir)

  key = opts.key if opts.key else bldcmd.tgtkey

  base_url = opts.base_url if opts.base_url else audit.baseurl(key)
  if not base_url:
    base_url = svn_get_url(base_dir)

  if opts.extract_dirs_with_fallback:
    rc = svn_export_dirs(base_url, base_dir, audit.old_prereqs([key]))
    if rc != 0:
      exfile = os.path.join(base_url, opts.extract_dirs_with_fallback)
      rc = svn_full_extract(exfile, base_dir)
      if rc != 0:
        sys.exit(2)

  if bldcmd.dry_run:
    sys.exit(0)
  elif bldcmd.special_case or opts.execute_only:
    for cmd in opts.prebuild:
      verbose([cmd])
      rc = subprocess.call(cmd, shell=True, cwd=build_base, stdin=open(os.devnull))
      if (rc != 0):
        sys.exit(2)
    rc = bldcmd.execute_in(cwd, start_time)
    sys.exit(rc)

  if audit.has(key):
    if opts.clean:
      for tgt in audit.old_targets([key]):
        try:
          os.remove(os.path.join(build_base, tgt))
        except OSError:
          pass
  else:
    opts.fresh = True

  if external_base:
    copy_out_cmd = ['rsync', '-a']
    if opts.fresh:
      copy_out_cmd.append('--exclude=[.]svn*')
      recreate_dir(bwd)
      feed_to_rsync = []
      if False:
        svnstat = ['svn', 'status', '--no-ignore']
        verbose(svnstat)
        svnstat = subprocess.Popen(svnstat, cwd=base_dir, stdout=subprocess.PIPE, stderr=open(os.devnull))
        privates = svnstat.communicate()[0]
        if svnstat.returncode != 0:
          sys.exit(2)
        for line in privates.splitlines():
          rpath = re.sub(r'^[I?]\s+', '', line)
          if rpath == line:
            continue
          if os.path.isdir(os.path.join(base_dir, rpath)):
            rpath += os.sep
          feed_to_rsync.append(rpath)
        copy_out_cmd.append('--exclude-from=-')
    else:
      if not os.path.exists(build_base):
        os.makedirs(build_base)
      feed_to_rsync = audit.old_prereqs([key])
      copy_out_cmd = ['rsync', '-a', '--files-from=-']

    copy_out_cmd.extend([
        '--delete',
        '--delete-excluded',
        '--exclude=*.swp',
        '--exclude=' + os.path.basename(audit.dbfile),
        base_dir + os.sep,
        build_base])
    run_with_stdin(copy_out_cmd, feed_to_rsync)

  audit.setup(build_base)

  for cmd in opts.prebuild:
    verbose([cmd])
    rc = subprocess.call(cmd, shell=True, cwd=build_base, stdin=open(os.devnull))
    if (rc != 0):
      sys.exit(2)

  rc = bldcmd.execute_in(bwd, start_time)

  if rc != 0 and external_base:
    if opts.retry_in_place:
      rc = bldcmd.execute_in(cwd, start_time)
      sys.exit(rc)
    elif not opts.fresh:
      nargv = argv[:]
      nargv.insert(1, '--fresh')
      verbose(nargv)
      rc = subprocess.call(nargv)
      sys.exit(rc)

  if audit.noatime():
    warnings.warn("audit skipped - build in noatime mount")
    if external_base:
      copy_in_cmd = ['rsync', '-a', '--exclude=*.tmp', build_base + os.sep, base_dir]
      run_with_stdin(copy_in_cmd, [])
  else:
    seconds = bldcmd.build_end - bldcmd.build_start
    bld_time = str(datetime.timedelta(seconds=int(seconds)))
    replace = opts.fresh and rc == 0
    audit.update(key, build_base, bld_time, base_url, replace)
    if external_base:
      if audit.new_targets:
        copy_in_cmd = ['rsync', '-a', '--files-from=-', build_base + os.sep, base_dir]
        run_with_stdin(copy_in_cmd, audit.new_targets)
        if opts.edit:
          # TODO: better to write something like Perl's -T (text) test here
          tgts = [os.path.join(base_dir, t) for t in audit.new_targets if re.search(r'\.(cmd|depend|d|flags)$', t)]
          if tgts:
            mldir = external_base + os.sep
            for line in fileinput.input(tgts, inplace=True):
              sys.stdout.write(line.replace(mldir, '/'))

  if external_base and opts.remove_external_tree:
    verbose("Removing %s/..." % (build_base))
    shutil.rmtree(build_base)

  return rc