Skip to content

Commit 4cccc8a

Browse files
committed
Merge pull request #741 from conda/pin-versions
[WIP] pin dependencies when building a package
2 parents 3c495b3 + f5e1075 commit 4cccc8a

File tree

3 files changed

+66
-33
lines changed

3 files changed

+66
-33
lines changed

conda_build/build.py

+41-18
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@
4141
else:
4242
shell_path = '/bin/bash'
4343

44+
channel_urls=()
45+
override_channels = False
46+
verbose = True
4447

4548
def prefix_files():
4649
'''
@@ -129,6 +132,14 @@ def rewrite_file_with_new_prefix(path, data, old_prefix, new_prefix):
129132
os.chmod(path, stat.S_IMODE(st.st_mode) | stat.S_IWUSR) # chmod u+w
130133
return data
131134

135+
136+
def get_run_dists(m):
137+
prefix = join(cc.envs_dirs[0], '_run')
138+
rm_rf(prefix)
139+
create_env(prefix, [ms.spec for ms in m.ms_depends('run')])
140+
return sorted(linked(prefix))
141+
142+
132143
def create_info_files(m, files, include_recipe=True):
133144
'''
134145
Creates the metadata files that will be stored in the built package.
@@ -173,10 +184,29 @@ def create_info_files(m, files, include_recipe=True):
173184
print("WARNING: anaconda.org only recognizes about/readme as README.md and README.rst",
174185
file=sys.stderr)
175186

187+
info_index = m.info_index()
188+
pin_depends = m.get_value('build/pin_depends')
189+
if pin_depends:
190+
dists = get_run_dists(m)
191+
with open(join(config.info_dir, 'requires'), 'w') as fo:
192+
fo.write("""\
193+
# This file as created when building:
194+
#
195+
# %s.tar.bz2 (on '%s')
196+
#
197+
# It can be used to create the runtime environment of this package using:
198+
# $ conda create --name <env> --file <this file>
199+
""" % (m.dist(), cc.subdir))
200+
for dist in sorted(dists + [m.dist()]):
201+
fo.write('%s\n' % '='.join(dist.rsplit('-', 2)))
202+
if pin_depends == 'strict':
203+
info_index['depends'] = [' '.join(dist.rsplit('-', 2))
204+
for dist in dists]
205+
176206
# Deal with Python 2 and 3's different json module type reqs
177207
mode_dict = {'mode': 'w', 'encoding': 'utf-8'} if PY3 else {'mode': 'wb'}
178208
with open(join(config.info_dir, 'index.json'), **mode_dict) as fo:
179-
json.dump(m.info_index(), fo, indent=2, sort_keys=True)
209+
json.dump(info_index, fo, indent=2, sort_keys=True)
180210

181211
if include_recipe:
182212
with open(join(config.info_dir, 'recipe.json'), **mode_dict) as fo:
@@ -250,25 +280,23 @@ def create_info_files(m, files, include_recipe=True):
250280
shutil.copyfile(join(m.path, m.get_value('app/icon')),
251281
join(config.info_dir, 'icon.png'))
252282

253-
def get_build_index(clear_cache=True, channel_urls=(), override_channels=False):
283+
def get_build_index(clear_cache=True):
254284
if clear_cache:
255285
# remove the cache such that a refetch is made,
256286
# this is necessary because we add the local build repo URL
257287
fetch_index.cache = {}
258288
return get_index(channel_urls=[url_path(config.croot)] + list(channel_urls),
259-
prepend=not override_channels)
289+
prepend=not override_channels)
260290

261-
def create_env(prefix, specs, clear_cache=True, verbose=True, channel_urls=(),
262-
override_channels=False):
291+
def create_env(prefix, specs, clear_cache=True):
263292
'''
264293
Create a conda envrionment for the given prefix and specs.
265294
'''
266295
if not isdir(config.bldpkgs_dir):
267296
os.makedirs(config.bldpkgs_dir)
268297
update_index(config.bldpkgs_dir)
269298
if specs: # Don't waste time if there is nothing to do
270-
index = get_build_index(clear_cache=True, channel_urls=channel_urls,
271-
override_channels=override_channels)
299+
index = get_build_index(clear_cache=True)
272300

273301
warn_on_old_conda_build(index)
274302

@@ -304,7 +332,6 @@ def warn_on_old_conda_build(index):
304332
""" % (vers_inst[0], pkgs[-1].version), file=sys.stderr)
305333

306334

307-
308335
def rm_pkgs_cache(dist):
309336
'''
310337
Removes dist from the package cache.
@@ -320,8 +347,7 @@ def bldpkg_path(m):
320347
'''
321348
return join(config.bldpkgs_dir, '%s.tar.bz2' % m.dist())
322349

323-
def build(m, get_src=True, verbose=True, post=None, channel_urls=(),
324-
override_channels=False, include_recipe=True):
350+
def build(m, get_src=True, post=None, include_recipe=True):
325351
'''
326352
Build the package with the specified metadata.
327353
@@ -368,14 +394,12 @@ def build(m, get_src=True, verbose=True, post=None, channel_urls=(),
368394
# Version number could be missing due to dependency on source info.
369395
print("BUILD START:", m.dist())
370396
create_env(config.build_prefix,
371-
[ms.spec for ms in m.ms_depends('build')],
372-
verbose=verbose, channel_urls=channel_urls,
373-
override_channels=override_channels)
397+
[ms.spec for ms in m.ms_depends('build')])
374398

375399
if m.name() in [i.rsplit('-', 2)[0] for i in linked(config.build_prefix)]:
376400
print("%s is installed as a build dependency. Removing." %
377401
m.name())
378-
index = get_build_index(clear_cache=False, channel_urls=channel_urls, override_channels=override_channels)
402+
index = get_build_index(clear_cache=False)
379403
actions = plan.remove_actions(config.build_prefix, [m.name()], index=index)
380404
assert not plan.nothing_to_do(actions), actions
381405
plan.display_actions(actions, index)
@@ -483,7 +507,7 @@ def build(m, get_src=True, verbose=True, post=None, channel_urls=(),
483507
print("STOPPING BUILD BEFORE POST:", m.dist())
484508

485509

486-
def test(m, verbose=True, channel_urls=(), override_channels=False, move_broken=True):
510+
def test(m, move_broken=True):
487511
'''
488512
Execute any test scripts for the given package.
489513
@@ -533,8 +557,7 @@ def test(m, verbose=True, channel_urls=(), override_channels=False, move_broken=
533557
# as the tests are run by perl, we need to specify it
534558
specs += ['perl %s*' % environ.get_perl_ver()]
535559

536-
create_env(config.test_prefix, specs, verbose=verbose,
537-
channel_urls=channel_urls, override_channels=override_channels)
560+
create_env(config.test_prefix, specs)
538561

539562
env = dict(os.environ)
540563
env.update(environ.get_dict(m, prefix=config.test_prefix))
@@ -595,6 +618,6 @@ def tests_failed(m, move_broken):
595618
if not isdir(config.broken_dir):
596619
os.makedirs(config.broken_dir)
597620

598-
if move_broken:
621+
if move_broken:
599622
shutil.move(bldpkg_path(m), join(config.broken_dir, "%s.tar.bz2" % m.dist()))
600623
sys.exit("TESTS FAILED: " + m.dist())

conda_build/main_build.py

+9-11
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,9 @@ def execute(args, parser):
266266
from conda_build.metadata import MetaData
267267

268268
check_external()
269-
channel_urls = args.channel or ()
269+
build.channel_urls = args.channel or ()
270+
build.override_channels = args.override_channels
271+
build.verbose = not args.quiet
270272

271273
if on_win:
272274
# needs to happen before any c extensions are imported that might be
@@ -322,9 +324,7 @@ def execute(args, parser):
322324
if not isdir(config.bldpkgs_dir):
323325
makedirs(config.bldpkgs_dir)
324326
update_index(config.bldpkgs_dir)
325-
index = build.get_build_index(clear_cache=True,
326-
channel_urls=channel_urls,
327-
override_channels=args.override_channels)
327+
index = build.get_build_index(clear_cache=True)
328328

329329
already_built = []
330330
to_build_recursive = []
@@ -378,8 +378,7 @@ def execute(args, parser):
378378
print(build.bldpkg_path(m))
379379
continue
380380
elif args.test:
381-
build.test(m, verbose=not args.quiet,
382-
channel_urls=channel_urls, override_channels=args.override_channels, move_broken=False)
381+
build.test(m, move_broken=False)
383382
elif args.source:
384383
source.provide(m.path, m.get_section('source'))
385384
print('Source tree in:', source.get_dir())
@@ -396,9 +395,8 @@ def execute(args, parser):
396395
else:
397396
post = None
398397
try:
399-
build.build(m, verbose=not args.quiet, post=post,
400-
channel_urls=channel_urls,
401-
override_channels=args.override_channels, include_recipe=args.include_recipe)
398+
build.build(m, post=post,
399+
include_recipe=args.include_recipe)
402400
except (RuntimeError, SystemExit) as e:
403401
error_str = str(e)
404402
if error_str.startswith('No packages found') or error_str.startswith('Could not find some'):
@@ -461,8 +459,8 @@ def execute(args, parser):
461459
continue
462460

463461
if not args.notest:
464-
build.test(m, verbose=not args.quiet,
465-
channel_urls=channel_urls, override_channels=args.override_channels)
462+
build.test(m)
463+
466464
binstar_upload = True
467465

468466
if need_cleanup:

conda_build/metadata.py

+16-4
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,14 @@ def ensure_valid_license_family(meta):
128128
"about/license_family '%s' not allowed. Allowed families are %s." %
129129
(license_family, comma_join(sorted(allowed_license_families)))))
130130

131+
def ensure_valid_fields(meta):
132+
try:
133+
pin_depends = meta['build']['pin_depends']
134+
except KeyError:
135+
pin_depends = ''
136+
if pin_depends not in ('', 'record', 'strict'):
137+
raise RuntimeError("build/pin_depends cannot be '%s'" % pin_depends)
138+
131139
def parse(data):
132140
data = select_lines(data, ns_cfg())
133141
res = yamlize(data)
@@ -154,10 +162,11 @@ def parse(data):
154162
res[section] = {}
155163
if res[section].get(key, None) is None:
156164
res[section][key] = []
165+
157166
# ensure those are strings
158-
for field in ('package/version', 'build/string', 'source/svn_rev',
159-
'source/git_tag', 'source/git_branch', 'source/md5',
160-
'source/git_rev', 'source/path'):
167+
for field in ('package/version', 'build/string', 'build/pin_depends',
168+
'source/svn_rev', 'source/git_tag', 'source/git_branch',
169+
'source/md5', 'source/git_rev', 'source/path'):
161170
section, key = field.split('/')
162171
if res.get(section) is None:
163172
res[section] = {}
@@ -188,6 +197,7 @@ def parse(data):
188197
elif val in falses:
189198
res[section][key] = False
190199

200+
ensure_valid_fields(res)
191201
ensure_valid_license_family(res)
192202
return sanitize(res)
193203

@@ -256,7 +266,9 @@ def _git_clean(source_meta):
256266
'no_link', 'binary_relocation', 'script', 'noarch_python',
257267
'has_prefix_files', 'binary_has_prefix_files', 'script_env',
258268
'detect_binary_files_with_prefix', 'rpaths',
259-
'always_include_files', 'skip', 'msvc_compiler'],
269+
'always_include_files', 'skip', 'msvc_compiler',
270+
'pin_depends' # pin_depends is experimental still
271+
],
260272
'requirements': ['build', 'run', 'conflicts'],
261273
'app': ['entry', 'icon', 'summary', 'type', 'cli_opts',
262274
'own_environment'],

0 commit comments

Comments
 (0)