3 maintainer <- 'cran2deb buildbot <cran2deb@example.org>'
4 root <- '/home/cb/work/gsoc/cran2deb'
5 pbuilder_results <- file.path(root,'var/results')
6 pbuilder_config <- file.path(root,'etc/pbuilderrc')
7 dput_config <- file.path(root,'etc/dput.cf')
8 dinstall_config <- file.path(root,'etc/mini-dinstall.conf')
9 dinstall_archive <- file.path(root,'var/archive')
10 r_depend_fields <- c('Depends','Imports') # Suggests, Enhances
12 # we cache the list of available packages
13 load(file.path(root,'var/cache/available.cache.Rd'))
15 version.new <- function(rver,debian_revision=1, debian_epoch=0) {
16 # generate a string representation of the Debian version of an
17 # R version of a package
20 # ``Writing R extensions'' says that the version consists of at least two
21 # non-negative integers, separated by . or -
22 if (!length(grep('^([0-9]+[.-])+[0-9]+$',rver))) {
23 stop(paste('Not a valid R package version',rver))
26 # Debian policy says that an upstream version should start with a digit and
27 # may only contain ASCII alphanumerics and '.+-:~'
28 if (!length(grep('^[0-9][A-Za-z0-9.+:~-]*$',rver))) {
29 stop(paste('R package version',rver
30 ,'does not obviously translate into a valid Debian version.'))
33 # if rver contains a : then the Debian version must also have a colon
34 if (debian_epoch == 0 && length(grep(':',pkgver)))
37 # if the epoch is non-zero then include it
38 if (debian_epoch != 0)
39 pkgver = paste(debian_epoch,':',pkgver,sep='')
41 # always add the '-1' Debian release; nothing is lost and rarely will R
42 # packages be Debian packages without modification.
43 return(paste(pkgver,'-',debian_revision,sep=''))
46 version.epoch <- function(pkgver) {
47 # return the Debian epoch of a Debian package version
48 if (!length(grep(':',pkgver)))
50 return(as.integer(sub('^([0-9]+):.*','\\1',pkgver)))
52 # version.epoch . version.new(x,y) = id
53 # version.epoch(version.new(x,y)) = 0
55 version.revision <- function(pkgver) {
56 # return the Debian revision of a Debian package version
57 return(as.integer(sub('.*-([0-9]+)$','\\1',pkgver)))
59 # version.revision . version.new(x) = id
60 # version.revision(version.new(x)) = 1
62 version.upstream <- function(pkgver) {
63 # return the upstream version of a Debian package version
64 return(sub('-[0-9]+$','',sub('^[0-9]+:','',pkgver)))
66 # version.upstream . version.new = id
68 version.update <- function(rver, prev_pkgver) {
69 # return the next debian package version
70 prev_rver <- version.upstream(prev_pkgver)
71 if (prev_rver == rver) {
72 # increment the Debian revision
73 return(version.new(rver
74 ,debian_revision = version.revision(prev_pkgver)+1
75 ,debian_epoch = version.epoch(prev_pkgver)
79 # TODO: implement Debian ordering over version and then autoincrement
80 # Debian epoch when upstream version does not increment.
81 return(version.new(rver
82 ,debian_epoch = version.epoch(prev_pkgver)
86 # sudo pbuilder --execute r -e 'rownames(installed.packages())'
87 # XXX: has to be a better way of doing this
88 base_pkgs=c('base', 'datasets','grDevices','graphics','grid', 'methods'
89 ,'splines','stats', 'stats4', 'tcltk', 'tools','utils')
90 # found in R source directory:
91 # 'profile', 'datasets'
93 repourl.as.debian <- function(url) {
94 # map the url to a repository onto its name in debian package naming
95 if (length(grep('cran',url))) {
98 if (length(grep('bioc',url))) {
101 stop(paste('unknown repository',url))
104 pkgname.as.debian <- function(name,repopref=NULL,version=NULL,binary=T) {
105 # generate the debian package name corresponding to the R package name
106 if (name %in% base_pkgs) {
112 debname='r-base-core'
117 # XXX: data.frame rownames are unique, so always override repopref for
119 if (!(name %in% rownames(available))) {
120 bundle <- r.bundle.of(name)
122 stop(paste('package',name,'is not available'))
126 repopref <- repourl.as.debian(available[name,'Repository'])
127 debname = paste('r',tolower(repopref),tolower(name),sep='-')
129 if (!is.null(version) && length(version) > 1) {
130 debname = paste(debname,' (',version,')',sep='')
135 setup <- function() {
136 # set up the working directory
137 tmp <- tempfile('cran2deb')
142 cleanup <- function(dir) {
143 # remove the working directory
144 unlink(dir,recursive=T)
148 r.bundle.of <- function(pkgname) {
149 # returns the bundle containing pkgname or NA
150 bundles <- names(available[!is.na(available[, 'Bundle']), 'Contains'])
151 # use the first bundle
152 for (bundle in bundles) {
153 if (pkgname %in% r.bundle.contains(bundle)) {
160 r.bundle.contains <- function(bundlename) {
161 return(strsplit(available[bundlename,'Contains'],'[[:space:]]+')[[1]])
164 prepare.pkg <- function(dir, pkgname) {
165 # download and extract an R package named pkgname
166 # OR the bundle containing pkgname
168 # based loosely on library/utils/R/packages2.R::install.packages
169 # should do nothing Debian specific
171 # first a little trick; change pkgname if pkgname is contained in a bundle
172 if (!(pkgname %in% rownames(available))) {
173 bundle <- r.bundle.of(pkgname)
175 stop(paste('package',pkgname,'is unavailable'))
179 archive <- download.packages(pkgname, dir, available=available, repos='', type="source")[1,2]
180 if (length(grep('\\.\\.',archive)) || normalizePath(archive) != archive) {
181 stop(paste('funny looking path',archive))
185 if (length(grep('\\.zip$',archive))) {
186 cmd = paste('unzip',shQuote(archive))
187 } else if (length(grep('\\.tar\\.gz$',archive))) {
188 cmd = paste('tar','xzf',shQuote(archive))
190 stop(paste('Type of archive',archive,'is unknown.'))
195 stop(paste('Extraction of archive',archive,'failed.'))
199 pkg$archive = archive
200 pkg$path = sub("_\\.(zip|tar\\.gz)", ""
201 ,gsub(.standard_regexps()$valid_package_version, ""
203 if (!file.info(pkg$path)[,'isdir']) {
204 stop(paste(pkg$path,'is not a directory and should be.'))
206 pkg$description = read.dcf(file.path(pkg$path,'DESCRIPTION'))
207 pkg$repoURL = available[pkgname,'Repository']
208 pkg$version = pkg$description[1,'Version']
209 pkg$is_bundle = 'Bundle' %in% names(pkg$description[1,])
210 # note subtly of short circuit operators (no absorption)
211 if ((!pkg$is_bundle && pkg$description[1,'Package'] != pkg$name) ||
212 ( pkg$is_bundle && pkg$description[1,'Bundle'] != pkg$name)) {
213 stop(paste('package name mismatch'))
218 debian_ok_licenses=c('GPL','LGPL','AGPL','ARTISTIC' #,'UNLIMITED'
219 ,'BSD','MIT','APACHE','X11','MPL')
221 is_acceptable_license <- function(license) {
222 # determine if license is acceptable
224 # compress spaces into a single space
225 license = gsub('[[:blank:]]+',' ',license)
226 # make all characters upper case
227 license = toupper(license)
228 # don't care about versions of licenses
229 license = chomp(sub('\\( ?[<=>!]+ ?[0-9.-]+ ?\\)',''
230 ,sub('-[0-9.-]+','',license)))
231 if (license %in% debian_ok_licenses) {
235 license = gsub('HTTP://WWW.GNU.ORG/[A-Z/._-]*','',license)
236 license = gsub('HTTP://WWW.X.ORG/[A-Z/._-]*','',license)
237 license = gsub('HTTP://WWW.OPENSOURCE.ORG/[A-Z/._-]*','',license)
238 # remove all punctuation
239 license = gsub('[[:punct:]]+','',license)
240 # remove any extra space introduced
241 license = chomp(gsub('[[:space:]]+',' ',license))
243 license = gsub('THE','',license)
244 license = gsub('SEE','',license)
245 license = gsub('STANDARD','',license)
246 license = gsub('LICEN[SC]E','',license)
247 license = gsub('(GNU )?(GPL|GENERAL PUBLIC)','GPL',license)
248 license = gsub('(MOZILLA )?(MPL|MOZILLA PUBLIC)','MPL',license)
249 # remove any extra space introduced
250 license = chomp(gsub('[[:space:]]+',' ',license))
251 if (license %in% debian_ok_licenses) {
252 message(paste('W: Accepted wild license as',license,'. FIX THE PACKAGE!'))
255 # remove everything that looks like a version specification
256 license = gsub('(VER?SION|V)? *[0-9.-]+ *(OR *(HIGHER|LATER|NEWER|GREATER|ABOVE))?',''
258 # remove any extra space introduced
259 license = chomp(gsub('[[:space:]]+',' ',license))
260 if (license %in% debian_ok_licenses) {
261 message(paste('W: Accepted wild license as',license,'. FIX THE PACKAGE!'))
264 # TODO: put debian_ok_licenses in DB
265 # TODO: file {LICENSE,LICENCE} (+ maybe COPYING?)
266 message(paste('E: Wild license',license,'did not match'))
270 iterate <- function(xs,z,fun) {
277 chomp <- function(x) {
278 # remove leading and trailing spaces
279 return(sub('^[[:space:]]+','',sub('[[:space:]]+$','',x)))
282 host.arch <- function() {
283 # return the host system architecture
284 system('dpkg-architecture -qDEB_HOST_ARCH',intern=T)
287 r.requiring <- function(names) {
288 for (name in names) {
289 if (!(name %in% base_pkgs) && !(name %in% rownames(available))) {
290 bundle <- r.bundle.of(name)
292 stop(paste('package',name,'is not available'))
294 names <- c(names,r.bundle.contains(bundle))
297 # approximately prune first into a smaller availability
298 candidates <- available[sapply(rownames(available)
300 length(grep(paste(names,sep='|')
301 ,available[name,r_depend_fields])) > 0)
304 if (length(candidates) == 0) {
307 # find a logical index into available of every package/bundle
308 # whose dependency field contains at least one element of names.
309 # (this is not particularly easy to read---sorry---but is much faster than
310 # the alternatives i could think of)
312 dep_matches <- function(dep) chomp(gsub('\\([^\\)]+\\)','',dep)) %in% names
313 any_dep_matches <- function(name,field=NA)
314 any(sapply(strsplit(chomp(candidates[name,field])
315 ,'[[:space:]]*,[[:space:]]*')
318 for (field in r_depend_fields) {
319 matches = sapply(rownames(candidates), any_dep_matches, field=field)
320 if (length(matches) > 0) {
321 prereq = c(prereq,rownames(candidates[matches,]))
324 return(unique(prereq))
327 r.dependencies.of <- function(name=NULL,description=NULL) {
328 # find the immediate dependencies (children in the dependency graph) of an
330 if (!is.null(name) && (name == 'R' || name %in% base_pkgs)) {
333 if (is.null(description) && is.null(name)) {
334 stop('must specify either a description or a name.')
336 if (is.null(description)) {
337 if (!(name %in% rownames(available))) {
338 bundle <- r.bundle.of(name)
340 stop(paste('package',name,'is not available'))
344 description <- data.frame()
345 # keep only the interesting fields
346 for (field in r_depend_fields) {
347 if (!(field %in% names(available[name,]))) {
350 description[1,field] = available[name,field]
353 # extract the dependencies from the description
355 for (field in r_depend_fields) {
356 if (!(field %in% names(description[1,]))) {
359 new_deps <- lapply(strsplit(chomp(description[1,field])
360 ,'[[:space:]]*,[[:space:]]*')[[1]]
362 deps <- iterate(lapply(new_deps[!is.na(new_deps)],rbind),deps,rbind)
367 r.parse.dep.field <- function(dep) {
371 # remove other comments
372 dep = gsub('(\\(\\)|\\([[:space:]]*[^<=>!].*\\))','',dep)
374 dep = chomp(gsub('[[:space:]]+',' ',dep))
376 pat = '^([^ ()]+) ?(\\( ?([<=>!]+ ?[0-9.-]+) ?\\))?$'
377 if (!length(grep(pat,dep))) {
378 stop(paste('R dependency',dep,'does not appear to be well-formed'))
380 version = sub(pat,'\\3',dep)
381 dep = sub(pat,'\\1',dep)
382 if (!(dep %in% rownames(available))) {
383 depb <- r.bundle.of(dep)
388 return(list(name=dep,version=version))
391 r.dependency.closure <- function(fringe, forward_arcs=T) {
392 # find the transitive closure of the dependencies/prerequisites of some R
395 if (is.data.frame(fringe)) {
396 fringe <- levels(fringe$name)
398 fun = function(x) levels(r.dependencies.of(name=x)$name)
402 while(length(fringe) > 0) {
405 if (length(fringe) > 1) {
406 fringe <- fringe[2:length(fringe)]
410 src <- pkgname.as.debian(top,binary=F)
411 if (!length(grep('^r-',src)) || length(grep('^r-base',src))) {
415 closure=c(closure,top)
416 fringe=c(fringe,newdeps)
419 return(rev(unique(closure,fromLast=T)))
422 accept.license <- function(pkg) {
424 if (!('License' %in% names(pkg$description[1,]))) {
425 stop('package has no License: field in description!')
428 for (license in strsplit(chomp(pkg$description[1,'License'])
429 ,'[[:space:]]*\\|[[:space:]]*')[[1]]) {
430 if (is_acceptable_license(license)) {
435 if (is.null(accept)) {
436 stop(paste('No acceptable license:',pkg$description[1,'License']))
438 message(paste('N: Auto-accepted license',accept))
440 if (accept == 'Unlimited') {
441 # definition of Unlimited from ``Writing R extensions''
442 accept=paste('Unlimited (no restrictions on distribution or'
443 ,'use other than those imposed by relevant laws)')
448 get.dependencies <- function(pkg,extra_deps) {
449 if ('SystemRequirements' %in% colnames(pkg$description)) {
450 stop(paste('Unsupported SystemRequirements:',pkg$description[1,'SystemRequirements']))
453 # determine dependencies
454 dependencies <- r.dependencies.of(description=pkg$description)
456 # these are used for generating the Depends fields
457 as.deb <- function(r,binary) {
458 return(pkgname.as.debian(paste(dependencies[r,]$name)
459 ,version=dependencies[r,]$version
463 depends$bin <- lapply(rownames(dependencies), as.deb, binary=T)
464 depends$build <- lapply(rownames(dependencies), as.deb, binary=F)
465 # add the command line dependencies
466 depends$bin = c(extra_deps$deb,depends$bin)
467 depends$build = c(extra_deps$deb,depends$build)
469 # make sure we depend upon R in some way...
470 if (!length(grep('^r-base',depends$build))) {
471 depends$build = c(depends$build,pkgname.as.debian('R',version='>= 2.7.0',binary=F))
472 depends$bin = c(depends$bin, pkgname.as.debian('R',version='>= 2.7.0',binary=T))
474 # also include stuff to allow tcltk to build (suggested by Dirk)
475 depends$build = c(depends$build,'xvfb','xauth','xfont-base')
478 depends <- lapply(depends,unique)
480 # append the Debian dependencies
481 depends$build=c(depends$build,'debhelper (>> 4.1.0)','cdbs')
483 depends$bin=c(depends$bin,'${shlibs:Depends}')
486 # the names of dependent source packages (to find the .changes file to
487 # upload via dput). these can be found recursively.
488 depends$r = lapply(r.dependency.closure(dependencies)
490 # append command line dependencies
491 depends$r = c(extra_deps$r, depends$r)
495 generate.changelog <- function(pkg) {
496 # construct a dummy changelog
497 # TODO: ``Writing R extensions'' mentions that a package may also have
498 # {NEWS,ChangeLog} files.
499 cat(paste(paste(pkg$srcname,' (',pkg$debversion,') unstable; urgency=low',sep='')
500 ,'' ,' * Initial release.',''
501 ,paste(' --',maintainer,'',format(Sys.time(),'%a, %d %b %Y %H:%M:%S %z'))
502 ,'',sep='\n'),file=pkg$debfile('changelog.in'))
505 generate.rules <- function(pkg) {
506 cat(paste('#!/usr/bin/make -f'
507 ,paste('debRreposname :=',pkg$repo)
508 ,'include /usr/share/R/debian/r-cran.mk'
510 ,file=pkg$debfile('rules'))
511 Sys.chmod(pkg$debfile('rules'),'0700')
514 generate.copyright <- function(pkg) {
515 # generate copyright file; we trust DESCRIPTION
517 paste('This Debian package of the GNU R package',pkg$name
518 ,'was generated automatically using cran2deb by'
519 ,paste(maintainer,'.',sep='')
521 ,'The original GNU R package is Copyright (C) '
522 # TODO: copyright start date, true copyright date
523 ,format(Sys.time(),'%Y')
524 ,pkg$description[1,'Author']
525 ,'and possibly others.'
527 ,'The original GNU R package is maintained by'
528 ,pkg$description[1,'Maintainer'],'and was obtained from:'
533 ,'The GNU R package DESCRIPTION offers a'
534 ,'Copyright licenses under the terms of the',pkg$license
535 ,'license. On a Debian GNU/Linux system, common'
536 ,'licenses are included in the directory'
537 ,'/usr/share/common-licenses/.'
539 ,'The DESCRIPTION file for the original GNU R package '
541 ,file.path('/usr/lib/R/site-library'
545 ,sep='\n'), width=72), con=pkg$debfile('copyright.in'))
548 generate.control <- function(pkg) {
549 # construct control file
550 control = data.frame()
551 control[1,'Source'] = pkg$srcname
552 control[1,'Section'] = 'math'
553 control[1,'Priority'] = 'optional'
554 control[1,'Maintainer'] = maintainer
555 control[1,'Build-Depends'] = paste(pkg$depends$build,collapse=', ')
556 control[1,'Standards-Version'] = '3.8.0'
558 control[2,'Package'] = pkg$debname
559 control[2,'Architecture'] = 'all'
561 control[2,'Architecture'] = 'any'
563 control[2,'Depends'] = paste(pkg$depends$bin,collapse=', ')
565 # bundles provide virtual packages of their contents
567 control[2,'Provides'] = paste(
568 lapply(r.bundle.contains(pkg$name)
569 ,function(name) return(pkgname.as.debian(paste(name)
575 # generate the description
576 descr = 'GNU R package "'
577 if ('Title' %in% colnames(pkg$description)) {
578 descr = paste(descr,pkg$description[1,'Title'],sep='')
580 descr = paste(descr,pkg$name,sep='')
583 long_descr <- pkg$description[1,'BundleDescription']
585 long_descr <- pkg$description[1,'Description']
587 # using \n\n.\n\n is not very nice, but is necessary to make sure
588 # the longer description does not begin on the synopsis line --- R's
589 # write.dcf does not appear to have a nicer way of doing this.
590 descr = paste(descr,'"\n\n', long_descr, sep='')
591 if ('URL' %in% colnames(pkg$description)) {
592 descr = paste(descr,'\n\nURL: ',pkg$description[1,'URL'],sep='')
594 control[2,'Description'] = descr
596 # Debian policy says 72 char width; indent minimally
597 write.dcf(control,file=pkg$debfile('control.in'),indent=1,width=72)
598 write.dcf(control,indent=1,width=72)
601 prepare.new.debian <- function(pkg,extra_deps) {
602 # generate Debian version and name
603 pkg$repo = repourl.as.debian(pkg$repoURL)
604 pkg$debversion = version.new(pkg$version)
605 if (!length(grep('^[A-Za-z0-9][A-Za-z0-9+.-]+$',pkg$name))) {
606 stop(paste('Cannot convert package name into a Debian name',pkg$name))
608 pkg$srcname = tolower(pkg$name)
609 pkg$debname = pkgname.as.debian(pkg$name,repo=pkg$repo)
611 if (!length(grep('\\.tar\\.gz',pkg$archive))) {
612 stop('archive is not tarball')
615 # re-pack into a Debian-named archive with a Debian-named directory.
616 debpath = file.path(dirname(pkg$archive)
617 ,paste(pkg$srcname,'-'
620 file.rename(pkg$path, debpath)
622 debarchive = file.path(dirname(pkg$archive)
623 ,paste(pkg$srcname,'_'
624 ,pkg$version,'.orig.tar.gz'
627 setwd(dirname(pkg$path))
628 # remove them pesky +x files
629 system(paste('find',shQuote(basename(pkg$path))
630 ,'-type f -exec chmod -x {} \\;'))
632 system(paste('tar -czf',shQuote(debarchive),shQuote(basename(pkg$path))))
634 file.remove(pkg$archive)
635 pkg$archive = debarchive
637 # make the debian/ directory
638 debdir <- file.path(pkg$path,'debian')
639 pkg$debfile <- function(x) { file.path(debdir,x) }
640 unlink(debdir,recursive=T)
643 # see if this is an architecture-dependent package.
644 # heuristic: if /src/ exists in pkg$path, then this is an
645 # architecture-dependent package.
646 # CRAN2DEB.pm is a bit fancier about this but ``Writing R extensions''
647 # says: ``The sources and headers for the compiled code are in src, plus
648 # optionally file Makevars or Makefile.'' It seems unlikely that
649 # architecture independent code would end up here.
651 # if it's a bundle, check each of the packages
653 for (pkgname in r.bundle.contains(pkg$name)) {
654 pkg$archdep = file.exists(file.path(pkg$path,pkgname,'src'))
660 pkg$archdep = file.exists(file.path(pkg$path,'src'))
664 pkg$arch <- host.arch()
667 pkg$license <- accept.license(pkg)
668 pkg$depends <- get.dependencies(pkg,extra_deps)
669 generate.changelog(pkg)
671 generate.copyright(pkg)
672 generate.control(pkg)
674 # TODO: debian/watch from pkg$repoURL
676 # convert text to utf8 (who knows what the original character set is --
677 # let's hope iconv DTRT).
678 for (file in c('control','changelog','copyright')) {
679 system(paste('iconv -o ',shQuote(pkg$debfile(file))
681 ,shQuote(pkg$debfile(paste(file,'in',sep='.')))))
682 file.remove(pkg$debfile(paste(file,'in',sep='.')))
687 build.debian <- function(pkg) {
690 message(paste('N: building Debian package'
692 ,paste('(',pkg$debversion,')',sep='')
694 ret = system(paste('pdebuild --configfile',pbuilder_config))
697 stop('Failed to build package.')
701 changesfile <- function(srcname,version='*') {
702 return(file.path(pbuilder_results
703 ,paste(srcname,'_',version,'_'
704 ,host.arch(),'.changes',sep='')))
707 go <- function(name,extra_deps) {
709 pkg <- try((function() {
710 pkg <- prepare.new.debian(prepare.pkg(dir,name),extra_deps)
711 if (file.exists(changesfile(pkg$srcname,pkg$debversion))) {
712 message(paste('N: already built',pkg$srcname,'version',pkg$debversion))
716 # delete the current archive (XXX: assumes mini-dinstall)
717 for (subdir in c('mini-dinstall','unstable')) {
718 path = file.path(dinstall_archive,subdir)
719 if (file.exists(path)) {
720 unlink(path,recursive=T)
724 # delete notes of upload
725 file.remove(Sys.glob(file.path(pbuilder_results,'*.upload')))
727 # make mini-dinstall generate the skeleton of the archive
728 ret = system(paste('umask 022;mini-dinstall --batch -c',dinstall_config))
730 stop('failed to create archive')
733 # pull in all the R dependencies
734 message(paste('N: dependencies:',paste(pkg$depends$r,collapse=', ')))
735 for (dep in pkg$depends$r) {
736 message(paste('N: uploading',dep))
737 ret = system(paste('umask 022;dput','-c',shQuote(dput_config),'local'
740 stop('upload of dependency failed! maybe you did not build it first?')
746 ret = system(paste('umask 022;dput','-c',shQuote(dput_config),'local'
747 ,changesfile(pkg$srcname,pkg$debversion)))
749 stop('upload failed!')
755 if (inherits(pkg,'try-error')) {
761 if (exists('argv')) { # check for littler
768 if (!(argv[i] %in% opts)) {
778 message('E: missing argument')
781 if (argv[i] == '-D') {
782 extra_deps$deb = c(extra_deps$deb,strsplit(chomp(argv[i+1]),',')[[1]])
784 if (argv[i] == '-R') {
785 extra_deps$r = c(extra_deps$r,strsplit(chomp(argv[i+1]),',')[[1]])
786 extra_deps$deb = c(extra_deps$deb,lapply(extra_deps$r,pkgname.as.debian))
790 message('E: usage: cran2deb [-D extra_dep1,extra_dep2,...] package package ...')
793 build_order <- r.dependency.closure(c(extra_deps$r,argv))
794 message(paste('N: build order',paste(build_order,collapse=', ')))
795 for (pkg in build_order) {