-
Notifications
You must be signed in to change notification settings - Fork 9
/
conda-build-all
executable file
·764 lines (679 loc) · 31.8 KB
/
conda-build-all
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
#!/usr/bin/env python
import os
import re
import sys
import time
import yaml
import shutil
import tarfile
import argparse
import tempfile
import warnings
from glob import glob
from operator import add
from functools import reduce
from collections import defaultdict
from contextlib import contextmanager
from subprocess import call, check_call, CalledProcessError
try:
# Import Conda Build API things
import conda_build
import conda_build.api as build_api
from conda_build.exceptions import DependencyNeedsBuildingError
# This might change in the future, but its functionality was stripped from the Conda API
# at some point with no indication as why nor what replaced it
from conda.exports import get_index
from conda_build.variants import find_config_files as fcf
from conda_build.config import Config
from conda_build.metadata import MetaData
cb_version = conda_build.__version__
if cb_version < "3":
raise ImportError("Conda version too low: {}".format(cb_version))
if sys.version_info[0] < 3 or (sys.version_info[0] == 3 and sys.version_info[1] < 6):
raise SystemError("This script must run with Python >= 3.6, conda-build will take care of the lesser versions "
"Python when building.")
except ImportError as e:
print('ImportError encountered:')
print(str(e))
print('')
print('''This script requires conda-build >= 3 and anaconda-client. It also
must run in the root conda environment.
$ conda deactivate
$ conda install conda-build anaconda-client
''', file=sys.stderr)
raise
BINSTAR_TOKEN = os.environ.get('BINSTAR_TOKEN')
# these labels will be checked by default for existent packages.
STANDARD_LABELS = ('main',
'dev',
'rc',
'beta',
)
@contextmanager
def temporary_directory():
"""
Create a temporary directory which will be cleaned up on close
Python 2 compatible. Python 3.2+ can use tempfile.TemporaryDirectory to same effect
"""
name = tempfile.mkdtemp()
try:
yield name
finally:
shutil.rmtree(name)
def is_pre_release(pkg_meta):
"""Helper function to figure out if pre-released package"""
from pkg_resources import parse_version
parsed = parse_version(pkg_meta.version())
return parsed.is_prerelease
def pre_black_listed(pkg_meta):
"""Helper function to blacklist files"""
filename = '.pre_black_listed'
if os.path.exists(filename):
with open(filename) as f:
for line in f:
if not line.startswith('#') and pkg_meta.name() in line:
return True
return False
def list_package_contents(filename, protocol='bz2'):
"""
Helper function for looking at contents of tarball
"""
if not os.path.exists(filename):
print('ERROR: File does not exist: {}'.format(filename))
return
tarf = tarfile.open(filename, 'r:{}'.format(protocol))
print('Package contents:')
for i, name in enumerate(tarf.getnames()):
print(' {}'.format(name))
if i > 20:
print('...')
break
def clean_builds():
"""Clean up build artifacts"""
# Conda clean --source-cache is deprecated, this is its replacement
# Replacing "purge" with "purge-all" will also remove tarballs
cmd = ['conda', 'build', 'purge', '--quiet']
print("Cleaning up source builds...")
call(cmd)
def upload_to_anaconda(meta, username, max_retries=5,
force=False, dev=False):
"""
Upload a compiled meta package to Anaconda
Parameters
----------
meta : conda_build.metadata.MetaData
Meta data to construct path from
username
max_retries
force
dev
Returns
-------
Raises
------
FileNotFoundError
Raised when file does not exist
"""
# Get file path
filename = build_api.get_output_file_paths(meta)[0]
if not os.path.exists(filename):
raise FileNotFoundError('File does not exist: {}'.format(filename))
cmd = ['anaconda', '-v', '--show-traceback']
if BINSTAR_TOKEN is not None:
print('Using authentication token starting with', BINSTAR_TOKEN[:4], '...')
cmd.extend(['-t', BINSTAR_TOKEN])
cmd.extend(['upload', '--no-progress', '-u', username, filename])
if force:
cmd.extend(['--force'])
elif meta.get_section('extra') and ('force_upload' in meta.get_section('extra')) and (
meta.get_section('extra')['force_upload'] == 'True'):
cmd.extend(['--force'])
if not pre_black_listed(meta) and is_pre_release(meta):
cmd.extend(['--label', 'dev'])
elif dev:
assert force, "Dev packages require forced uploads."
cmd.extend(['--label', 'dev'])
elif meta.get_section('extra') and 'upload' in meta.get_section('extra'):
labels = meta.get_section('extra')['upload']
if labels is not None: # empty `upload:` entry will be None, not ""
for label in labels.split(','):
if label:
cmd.extend(['--label', label])
else:
cmd.extend(['--label', 'main'])
err = None
for i in range(max_retries):
try:
print("Uploading package '{}' to anaconda user '{}'...".format(filename, username))
check_call(cmd)
return
except CalledProcessError as call_err:
err = call_err
err.cmd = [part.replace(str(BINSTAR_TOKEN), '<secret>') for part in err.cmd]
print("Retrying after {} seconds...".format((i+1)))
time.sleep(i+1)
# final raise error to client
raise err
def render_metadatas(paths, variant_config_files=(), channel_urls=(), cycle_packages=False, verbose=0,
jinja_include=None, jinja_exclude=None,
debug=False):
"""
Build out the incomplete Metadata's for all possible recipes we will be constructing
Parameters
----------
paths : iterable of str
Flat iterable of paths as strings to search for conda-build directories
variant_config_files : list or tuple of str
Variant files to consider for building
channel_urls : iterable of str
Channel URLs or Conda-style short names to search against to see if
they are on the Anaconda.org cloud already
Formatted as formal URLs or as shorthand such as 'omnia/label/main'
cycle_packages : bool, default False
Output sorted by cycling through packages instead off all variants of one package listed first
verbose: int, default 0
Verbosity level
jinja_include: iterable of str, optional
A list of Jinja strings to search for, if a recipe's meta.yaml includes ANY of the strings, it will be built
otherwise will be skipped
jinja_exclude: iterable of str, optional
A list of Jinja strings to search for, if a recipe's meta.yaml includes ANY of the strings, it will NOT be
built.
debug: bool
A debug flag for recipes. If set, the first recipe to throw an error will stop the whole build process.
Returns
-------
metas : List of conda_build.api.metadata
errors : Dict of paths which failed the build along with the error
"""
metas = []
meta_lookup = {}
errors = {}
check_jinja = jinja_include or jinja_exclude
if check_jinja:
jinja_include = set([j.strip() for j in jinja_include] if jinja_include is not None else [])
jinja_exclude = set([j.strip() for j in jinja_exclude] if jinja_exclude is not None else [])
# Do some pre-setup magic...
# The variant system has 2 fatal flaws:
# 1. If 1 variant fails for a package, they all fail
# 2. Source files have to be downloaded to finish rendering the package
# Solve 2 first here
# We (Omnia) don't care about the source material, we just want the output name.
# So long as the `build: string` does not need the git hash or something, we'd like to bypass to save space and time
# However, setting no_download_source fails if the "work_dir" is empty as CB assumes it needs this info in case
# someone makes a build string/jinja template a function of source material.
# Omnia won't allow that so we can do this:
# The work_dir = croot (Conda Build's build root) + build ID (package + epoch) + "work"
# So we first start by tricking the initial config check that there is a populated work dir, we'll clean later
# Caveat: This is conda_build.config:Config trickery, so this may break in the future since its not "API"
for possible_recipe in paths:
try:
if os.path.isdir(possible_recipe):
# Skip if no meta.yaml file
if not os.path.exists(os.path.join(possible_recipe, 'meta.yaml')):
print(f"Skipping {possible_recipe} because no meta.yaml file found")
continue
elif possible_recipe[0] == '.':
if verbose > 0:
print(f"Skipping {possible_recipe} because its a hidden folder starting with \".\"")
continue
# Skip from jinja includes/excludes
elif check_jinja:
with open(os.path.join(possible_recipe, 'meta.yaml'), 'r') as meta_file:
meta_text = meta_file.read()
# Strip commneted lines out
meta_stripped = re.sub(r"^[\s]*?#.*\n", "", meta_text, flags=re.MULTILINE)
# Find all the Jinja
# This will correctly match the jinja template inside the {{ }} and inside {% %}
meta_jinja = re.findall(r"(?<={{).*?(?=}})|(?<={%).*?(?=%})", meta_stripped)
meta_jinja_set = set([match.strip() for match in meta_jinja])
if jinja_include and (not meta_jinja_set & jinja_include):
if verbose > 0:
print(f"Skipping {possible_recipe} because it does not have any of the "
f"Jinja keys in {jinja_include}")
continue
if jinja_exclude and (meta_jinja_set & jinja_exclude):
if verbose > 0:
print(f"Skipping {possible_recipe} because it has been excluded by the "
f"Jinja keys in {jinja_exclude}")
continue
else:
if verbose > 2:
print(f"Skipping {possible_recipe} because its not a directory")
continue
# Recreate the temp directory for each package because the act of cleaning up from the package removes
# the placeholder work directory
meta_lookup[possible_recipe] = []
with temporary_directory() as temp_dir: # Make a faux `croot` temporary directory
# Make a faux build_ID, but it has to be the package name
# Scrape by making a dirty, throwaway metadata just to get the correct name.
# The Config.compute_build_id function only looks for the old folder at specifically packagename
# Has been in since 3.21.6
dirty_meta = MetaData(possible_recipe, config=Config())
package_name = dirty_meta.name()
build_id = f"{package_name}" # Make a faux build_ID
work_dir = "work" # Make the "work_dir" directory
work_path = os.path.join(temp_dir, build_id, work_dir)
# Faux work dir will get deleted very quickly, even before the temporary directory collapses
os.makedirs(work_path)
with open(os.path.join(work_path, 'a_touched_file.magic'), 'w') as _:
# Touch a random file so the "work_dir" is not empty
pass
try:
# This is the fastest metabuild and does 0 checks against jinja and finalization
meta_bundle = build_api.render(
possible_recipe, # Path
finalize=False, # Speeds up render
# variants=variants, # Parses all variants
bypass_env_check=True, # Skip the env check (dont resolve anything)
filename_hashing=False, # Uses old build string (--old-build-string)
channel_urls=channel_urls, # Config pass-through
variant_config_files=variant_config_files, # Config pass-through
# Config-pass through no source downloading trickery (see above)
# all of this must be undone before leaving this function
# this is okay since we dont *build* any of these recipes... yet...
# I think this is the minimum source
no_download_source=True, # Don't download anything (assume we have it)
keep_old_work=True, # Don't move the work folder around, its just a placeholder for now
build_id=build_id, # Force build ID to start set to this
croot=temp_dir, # Force croot to start set to this
)
# Flatten metadata for export
for variant_index, meta_tuple in enumerate(meta_bundle):
meta = meta_tuple[0]
meta.final = False # Ensure meta is not final
# Revert all Config trickery changes we made to speed render
meta.config.keep_old_work = False # Let work get moved around
meta.config.croot = None # This resets croot to default
meta.config.no_download_source = False
# Solve Conda Build fatal problem 1: Treat each "variant" as its own "build"
# Because we have now undone all the Config trickery, this will ensure we get
# Unique, proper build ID's for each variant , treating them as independent builds
time.sleep(0.05) # Add the small delay to help uniqueness (names are time.time()*1000)
meta.config.compute_build_id(meta.name(), reset=True)
# This process will make several empty build directories in your (true) conda build
# croot, there may be a way around that, but they are all empty and go unused.
# Running `conda build purge-all` will remove all build artifacts from everywhere (not
# just those made from this script), or just run clean_build() function
if not cycle_packages:
metas.append(meta)
else:
meta_lookup[possible_recipe].append(meta)
except (RuntimeError, IOError, DependencyNeedsBuildingError):
print('Failed to load recipe: {}'.format(possible_recipe))
raise
except SystemExit:
print("Critical ERROR: Recipe {} has a fatal error that prevents processing".format(
possible_recipe))
raise
except (Exception, SystemExit) as err:
print(err)
errors[possible_recipe] = str(err)
if debug:
raise
if cycle_packages:
while meta_lookup: # While dict not empty
for possible_recipe in paths: # Cycle through recipes
# If the recipe is in the dict and not empty
if possible_recipe in meta_lookup and meta_lookup[possible_recipe]:
metas.append(meta_lookup[possible_recipe].pop(0))
if not meta_lookup[possible_recipe]: # Remove recipe if empty
meta_lookup.pop(possible_recipe)
elif not meta_lookup[possible_recipe]: # Covers case where recipe present from start, but no metas
meta_lookup.pop(possible_recipe)
return metas, errors
def required_builds(metas, channel_urls, verbose=0, force=False, rebuild=False, scheduled_only=False):
"""
Generator to determine which builds need to be uploaded based on existing builds on Anaconda
Checks against the following conditions to return package:
* Package has not already been queued by this generator
* Will force return if Package has force_upload in extra section of meta.yaml
* Package tarball has not already been built locally (will not overwrite), unless rebuild=True
* Package has not indicated it should be skipped
* Package is not already on Anaconda by same name (overridden by force keyword)
Parameters
----------
metas : List of conda_build.metadata.MetaData
Flat list of rendered conda_build metas
channel_urls : list or tuple of string
Channel URLs or Conda-style short names to search against to see if
they are on the Anaconda.org cloud already
Formatted as formal URLs or as shorthand such as 'omnia/label/main'
verbose : int, Default: 0
Verbosity level, higher is more verbose
3 or greater is maximum currently
force : bool, Default: False
If package is on Anaconda already, force package to be built
Distinct from ``rebuild``
rebuild : bool, Default False
If package is already found built locally, force it to be built
Distinct from ``force``
scheduled_only : bool, Default False
Will only build package if its marked as a package which undergoes scheduled builds.
Even if `force` or `rebuild` is set, this supersedes it
Returns
-------
metas : Generator of conda_build.metadata.MetaData
Yields Metadata objects which should be built without duplication
"""
# Get the package list from the channel URLs
index = get_index(channel_urls=channel_urls,
prepend=False)
# Sift the packages by both package name, and channel
index_by_pkgname = defaultdict(dict)
for k, v in index.items():
channel = k.channel
pkgname = k.to_filename()
index_by_pkgname[pkgname][channel] = v
queued_packages = {} # Tracking already queued packages
for meta in metas:
# Determine package output path
output_package = build_api.get_output_file_paths(meta)[0]
package_base = os.path.basename(output_package)
if verbose > 2:
print('Evaluating whether to build package {}'.format(package_base))
# Make sure we have not put the package in the to-build section yet
if output_package in queued_packages:
if verbose > 1:
print("Skipping {} as its already been built".format(package_base))
# Sanity check
ref_package = queued_packages[output_package]
if ref_package != meta and verbose > 2: # Not sure equality differs from `is` on conda_build Metapackages
print("Warning: Two packages were queued to build to {} but do not have the same Metas. "
"\nThis can happen, for example, if you have multiple NumPys, "
"but NumPy is not pinned so may be harmless."
"\nPlease check that you do not have two meta.yaml files with the same package specs in "
"different folders:\n"
"\t-{}\n"
"\t-{}".format(package_base, ref_package.path, meta.path))
continue
# Skip if the package is not scheduled and we only are building scheduled
if scheduled_only and not (meta.get_section('extra') and meta.get_section('extra').get('scheduled', False)):
if verbose > 1:
print(f'Package {package_base} skipped because we are only building packages which are build on a '
f'schedule.')
continue
# Allow package recipe to force rebuild and upload in recipe.
if meta.get_section('extra') and 'force_upload' in meta.get_section('extra') and (
meta.get_section('extra')['force_upload'] in (True, 'True', 'true')):
if verbose > 1:
print('Queueing package due to force_upload={}: {}'.format(
str(meta.get_section('extra')['force_upload']), meta.name()))
print(' full_name: {}'.format(package_base))
yield meta
queued_packages[output_package] = meta
continue # We forced package, loop now
# Ensure its not already queued
if not rebuild and os.path.isfile(output_package):
if verbose > 1:
print('Skipping because package exists locally: {}'.format(output_package))
continue
# The metapackage itself has indicated it should be skipped, remove from build stack
if meta.skip():
if verbose > 1:
print('Skipping {}'.format(package_base))
continue
# Make sure package is not already on Anaconda (if we are not forcing)
if package_base in index_by_pkgname and not force:
if verbose > 2:
print('Package exists on anaconda.org: {}'.format(meta.name()))
print(' full_name: {}'.format(package_base))
print(' channel(s): {}'.format(tuple(index_by_pkgname[package_base].keys())))
print(' md5: {}'.format(tuple([v['md5'] for v in index_by_pkgname[package_base].values()])))
continue
if verbose > 1:
print('Queueing package: {}'.format(meta.name()))
print(' full_name: {}'.format(package_base))
print(' build variant Python: {}'.format(meta.config.variants[0].get('python', 'undefined')))
yield meta
queued_packages[output_package] = meta
def build_package(meta, args):
"""
Build a new package and optionally upload to Anaconda from metadata
If metadata is flagged as final, then the raw meta will be used, otherwise
it will be reconstructed from the path.
Parameters
----------
meta : conda_build.metadata.MetaData
Metadata to construct render from
args : ArgumentParser
Parsed arguments from input to set flags
Returns
-------
path : string
Absolute path to the output tarball
"""
# Check if there is an omnia label to build against
if meta.get_section('extra') and 'include_omnia_label' in meta.get_section('extra'):
omnia_label = meta.get_section('extra')['include_omnia_label']
if omnia_label not in STANDARD_LABELS:
print('Cannot build package {} against label "{}", must be one of the standard labels: {}.\n'
'Falling back to using "main" only.'.format(meta.path, omnia_label, STANDARD_LABELS))
else:
# Add the new channel to the front (repetition does not mater, order does)
meta.config.channel_urls = ('omnia/label/{}'.format(omnia_label),) + tuple(meta.config.channel_urls)
# Finalize channel URLs
meta.config.channel_urls = ('conda-forge',) + tuple(meta.config.channel_urls) # + ('defaults',)
# Build package
kwarg_bundle = {'notest': args.notest} # Common flags
if meta.final:
arg_bundle = [meta]
# No changes to keyword arguments
else:
# Non-finalized, rebuild from path
arg_bundle = [meta.path]
# kwarg_bundle['variants'] = meta.config.variants[0]
kwarg_bundle['config'] = meta.config
build_paths = build_api.build(*arg_bundle, **kwarg_bundle)
for build in build_paths:
list_package_contents(build)
if args.upload is not None:
if len(build_paths): # Length 1 or larger is good
upload_to_anaconda(meta, username=args.upload,
force=args.force, dev=args.dev)
else:
print('Package {} failed to build; will not upload.'.format(meta.dist()))
# Clean up conda-build artifacts
clean_builds()
# Remove tarballs if specified
if args.clean_tarballs:
os.remove(build_api.get_output_file_paths(meta)[0])
sys.stdout.flush()
def execute(args):
"""Execute the building"""
# Parse the pythons, numpys, and cudas into long and short form
try:
variant_config_files = fcf('.')
except TypeError: # More modern conda-build expectations, defaults for this function got rolled into Config.
variant_config_files = fcf('.', Config())
if args.variant_config_files:
variant_config_files.extend(args.variant_config_files)
py_sets = []
for index, variant_config_file in enumerate(variant_config_files):
# A bit of sanitation
variant_config_file = os.path.abspath(os.path.expanduser(variant_config_file))
variant_config_files[index] = variant_config_file
with open(variant_config_file, 'r') as config:
variant = yaml.safe_load(config)
py_sets.append(set(variant.get('python', [])))
if not py_sets:
# Case of No pythons found at all in any config anywhere
warnings.warn("There do not appear to be any Python's to build against in the configs, falling back"
f"to current version of {sys.version_info[0]}.{sys.version_info[1]}")
py_sets.append({float(f'{sys.version_info[0]}' + '.' + f'{sys.version_info[1]}'), })
pythons = set.intersection(*py_sets)
if not pythons:
raise RuntimeError(f"The Union of Python versions found among the config files of {variant_config_files} is "
f"the null set! Cannot make builds against no python versions!")
channel_urls = tuple(args.check_against) if args.check_against else ()
if args.verbose > 0:
print('EXECUTE:')
print('Verbosity: {}'.format(str(args.verbose)))
print(f'Pythons: {pythons}')
print('channel_urls: {}'.format(str(channel_urls))) # DEBUG
# Flatten all paths in target
recipe_paths = reduce(add, (glob(recipe) for recipe in args.recipe))
if args.verbose > 0:
print('Possible recipes provided (in random order):')
print(recipe_paths)
metas, errors = render_metadatas(recipe_paths,
variant_config_files=variant_config_files,
channel_urls=channel_urls,
cycle_packages=args.cycle,
jinja_include=set(args.build_only_jinja) if args.build_only_jinja else None,
jinja_exclude=set(args.no_build_jinja) if args.no_build_jinja else None,
verbose=args.verbose,
debug=args.debug
)
if args.verbose > 0:
all_paths = []
for meta in metas:
base_name = os.path.basename(meta.path)
if base_name not in all_paths:
all_paths.append(base_name)
print('Considering recipes in the following order:')
print(', '.join(all_paths))
print()
sys.stdout.flush()
if args.verbose > 1 and errors:
print("The following metas had these errors:")
for path, err in errors.items():
print(f"{path}:\n\t{err}")
queued_metas = list(required_builds(metas, channel_urls,
verbose=args.verbose,
force=args.force,
rebuild=args.rebuild,
scheduled_only=args.scheduled_only))
# Helpful data
print('\n[conda-build-all] Scheduling the following {} builds:'.format(len(queued_metas)))
for meta in queued_metas:
print(' {}'.format(os.path.basename(build_api.get_output_file_paths(meta)[0])))
sys.stdout.flush()
if args.dry_run:
print("--dry-run flag set! No builds will be executed. "
"See return code for number of builds queued.")
sys.exit(len(queued_metas))
print()
# Start builds and uploads
failed_builds = []
for meta in queued_metas:
try:
build_package(meta, args)
except (Exception, SystemExit) as build_err:
failed_builds.append((meta.dist(), build_err))
if len(failed_builds) > 0:
print(('[conda-build-all] Error: one or more packages '
'failed to build'), file=sys.stderr)
for build, error in failed_builds:
print(' {}: {}'.format(build, error), file=sys.stderr)
sys.stderr.flush()
print() # One blank line for good measure
return len(failed_builds)
def main():
p = argparse.ArgumentParser(
'Build conda packages',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
p.add_argument(
'recipe',
action="store",
metavar='RECIPE_PATH',
nargs='+',
help="path to recipe directory"
)
p.add_argument(
'--check-against',
nargs="+",
metavar='ANACONDA_USER/label/$name',
default=['omnia/label/{}'.format(label) for label in STANDARD_LABELS],
)
p.add_argument(
'--dry-run',
action='store_true',
help=('Do not perform any builds. The exit status indicates the '
'number of build to-be-performed'),
)
p.add_argument(
'--upload',
help='Automatically upload to Conda under this username',
)
p.add_argument(
'--force',
action='store_true',
dest='force',
help='Force a package upload regardless of errors',
)
p.add_argument(
'--rebuild',
action='store_true',
dest='rebuild',
help='Force a package to rebuild, even if it exists locally. Has no effect if --dry-run is set',
)
p.add_argument(
'--dev',
action='store_true',
dest='dev',
help=('Push package to the dev label. This requires '
'--force to be enabled.'),
)
p.add_argument(
'--no-test',
action='store_true',
dest='notest',
help="do not test the package"
)
p.add_argument(
'--cycle-packages',
action="store_true",
dest='cycle',
help="Order builds by rotating through packages. By default all variants for one package will be attempted "
"before any other package is attempted"
)
p.add_argument(
'--variant-config-files', '-m',
action='append',
help="Point at a specific conda config file(s), otherwise rely on system default",
metavar="CONDA_CONFIG"
)
p.add_argument(
'--build-only-jinja',
action='append',
help="Build ONLY recipes which have ANY of these Jinja2 templating in them. A recipe can have any number of "
"these and pass."
)
p.add_argument(
'--no-build-jinja',
action='append',
help="EXCLUDE recipes which have ANY of these Jinja2 templates in them."
)
p.add_argument(
'--scheduled-only',
action='store_true',
help="Specify only to build packages which set the {extra: {scheduled: True}} flag in their meta"
)
p.add_argument(
'-c', '--clean',
help='Remove the compiled tarballs once done. Helps keep build sizes down',
action='store_true',
dest='clean_tarballs'
)
p.add_argument(
'-v', '--verbose',
help='Give more output. Option is additive, and can be used up to 3 times.',
dest='verbose',
action='count',
)
p.add_argument(
'-d', '--debug',
help="Causes this script to fail out immediately if a build throws an error. Meant for debugging the script "
"itself",
action="store_true"
)
args = p.parse_args()
if args.verbose is None:
args.verbose = 0
if args.verbose > 2:
print('command-line arguments:')
print(args)
return execute(args)
if __name__ == '__main__':
sys.exit(main())