-
Notifications
You must be signed in to change notification settings - Fork 430
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Replace bundled conda-index with standalone conda-index package #4690
Changes from 5 commits
3d54dfa
653cb83
5ef0f1f
71eab18
0596350
272caf9
05d7f05
dd76ea6
8eeaec6
f9fffbd
c706da4
c1e57ef
e59b860
ad6c0f7
d9cc516
560ad71
1e8f1d9
74b2d7c
f047145
e27c8fb
1eb6752
2e7c40c
cc63b28
fe3647e
ad31a23
8c0117b
55e5ba2
51ba55a
a6cbec4
f1d52e2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,272 @@ | ||
# Copyright (C) 2014 Anaconda, Inc | ||
# SPDX-License-Identifier: BSD-3-Clause | ||
""" | ||
conda-build's use of conda-index, delegated to now-separate conda-index package. | ||
""" | ||
|
||
import conda_index.index | ||
|
||
import json | ||
import os | ||
from os.path import ( | ||
dirname, | ||
) | ||
import sys | ||
import time | ||
|
||
from functools import partial | ||
import logging | ||
|
||
from concurrent.futures import Executor | ||
|
||
from conda_build import conda_interface, utils | ||
from .conda_interface import context | ||
from .conda_interface import CondaHTTPError, get_index, url_path | ||
from .utils import ( | ||
JSONDecodeError, | ||
get_logger, | ||
) | ||
|
||
log = get_logger(__name__) | ||
|
||
|
||
# use this for debugging, because ProcessPoolExecutor isn't pdb/ipdb friendly | ||
class DummyExecutor(Executor): | ||
def map(self, func, *iterables): | ||
for iterable in iterables: | ||
for thing in iterable: | ||
yield func(thing) | ||
|
||
|
||
try: | ||
from conda.base.constants import NAMESPACES_MAP, NAMESPACE_PACKAGE_NAMES | ||
except ImportError: | ||
NAMESPACES_MAP = { # base package name, namespace | ||
"python": "python", | ||
"r": "r", | ||
"r-base": "r", | ||
"mro-base": "r", | ||
"mro-base_impl": "r", | ||
"erlang": "erlang", | ||
"java": "java", | ||
"openjdk": "java", | ||
"julia": "julia", | ||
"latex": "latex", | ||
"lua": "lua", | ||
"nodejs": "js", | ||
"perl": "perl", | ||
"php": "php", | ||
"ruby": "ruby", | ||
"m2-base": "m2", | ||
"msys2-conda-epoch": "m2w64", | ||
} | ||
NAMESPACE_PACKAGE_NAMES = frozenset(NAMESPACES_MAP) | ||
NAMESPACES = frozenset(NAMESPACES_MAP.values()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this used? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it wouldn't be surprising if conda.base.constants exists in all conda > 4.7 or the oldest one we can depend on. |
||
|
||
local_index_timestamp = 0 | ||
cached_index = None | ||
local_subdir = "" | ||
local_output_folder = "" | ||
cached_channels = [] | ||
channel_data = {} | ||
|
||
|
||
# TODO: support for libarchive seems to have broken ability to use multiple threads here. | ||
# The new conda format is so much faster that it more than makes up for it. However, it | ||
# would be nice to fix this at some point. | ||
try: | ||
_os_cpu_count = os.cpu_count() or 1 # can be None in rare cases | ||
except AttributeError: | ||
_os_cpu_count = 1 | ||
MAX_THREADS_DEFAULT = _os_cpu_count | ||
|
||
if ( | ||
sys.platform == "win32" | ||
): # see https://github.com/python/cpython/commit/8ea0fd85bc67438f679491fae29dfe0a3961900a | ||
MAX_THREADS_DEFAULT = min(48, MAX_THREADS_DEFAULT) | ||
LOCK_TIMEOUT_SECS = 3 * 3600 | ||
LOCKFILE_NAME = ".lock" | ||
|
||
# TODO: this is to make sure that the index doesn't leak tokens. It breaks use of private channels, though. | ||
# os.environ['CONDA_ADD_ANACONDA_TOKEN'] = "false" | ||
|
||
|
||
def get_build_index( | ||
subdir, | ||
bldpkgs_dir, | ||
output_folder=None, | ||
clear_cache=False, | ||
omit_defaults=False, | ||
channel_urls=None, | ||
debug=False, | ||
verbose=True, | ||
locking=None, | ||
timeout=None, | ||
): | ||
global local_index_timestamp | ||
global local_subdir | ||
global local_output_folder | ||
global cached_index | ||
global cached_channels | ||
global channel_data | ||
mtime = 0 | ||
|
||
channel_urls = list(utils.ensure_list(channel_urls)) | ||
|
||
if not output_folder: | ||
output_folder = dirname(bldpkgs_dir) | ||
|
||
# check file modification time - this is the age of our local index. | ||
index_file = os.path.join(output_folder, subdir, "repodata.json") | ||
if os.path.isfile(index_file): | ||
mtime = os.path.getmtime(index_file) | ||
|
||
if ( | ||
clear_cache | ||
or not os.path.isfile(index_file) | ||
or local_subdir != subdir | ||
or local_output_folder != output_folder | ||
or mtime > local_index_timestamp | ||
or cached_channels != channel_urls | ||
): | ||
|
||
# priority: (local as either croot or output_folder IF NOT EXPLICITLY IN CHANNEL ARGS), | ||
# then channels passed as args (if local in this, it remains in same order), | ||
# then channels from condarc. | ||
urls = list(channel_urls) | ||
|
||
loggers = utils.LoggingContext.default_loggers + [__name__] | ||
if debug: | ||
log_context = partial(utils.LoggingContext, logging.DEBUG, loggers=loggers) | ||
elif verbose: | ||
log_context = partial(utils.LoggingContext, logging.WARN, loggers=loggers) | ||
else: | ||
log_context = partial( | ||
utils.LoggingContext, logging.CRITICAL + 1, loggers=loggers | ||
) | ||
with log_context(): | ||
# this is where we add the "local" channel. It's a little smarter than conda, because | ||
# conda does not know about our output_folder when it is not the default setting. | ||
if os.path.isdir(output_folder): | ||
local_path = url_path(output_folder) | ||
# replace local with the appropriate real channel. Order is maintained. | ||
urls = [url if url != "local" else local_path for url in urls] | ||
if local_path not in urls: | ||
urls.insert(0, local_path) | ||
_ensure_valid_channel(output_folder, subdir) | ||
update_index(output_folder, verbose=debug) | ||
|
||
# replace noarch with native subdir - this ends up building an index with both the | ||
# native content and the noarch content. | ||
|
||
if subdir == "noarch": | ||
subdir = conda_interface.subdir | ||
try: | ||
cached_index = get_index( | ||
channel_urls=urls, | ||
prepend=not omit_defaults, | ||
use_local=False, | ||
use_cache=context.offline, | ||
platform=subdir, | ||
) | ||
# HACK: defaults does not have the many subfolders we support. Omit it and | ||
# try again. | ||
except CondaHTTPError: | ||
if "defaults" in urls: | ||
urls.remove("defaults") | ||
cached_index = get_index( | ||
channel_urls=urls, | ||
prepend=omit_defaults, | ||
use_local=False, | ||
use_cache=context.offline, | ||
platform=subdir, | ||
) | ||
|
||
expanded_channels = {rec.channel for rec in cached_index.values()} | ||
|
||
superchannel = {} | ||
# we need channeldata.json too, as it is a more reliable source of run_exports data | ||
for channel in expanded_channels: | ||
if channel.scheme == "file": | ||
location = channel.location | ||
if utils.on_win: | ||
location = location.lstrip("/") | ||
elif not os.path.isabs(channel.location) and os.path.exists( | ||
os.path.join(os.path.sep, channel.location) | ||
): | ||
location = os.path.join(os.path.sep, channel.location) | ||
channeldata_file = os.path.join( | ||
location, channel.name, "channeldata.json" | ||
) | ||
retry = 0 | ||
max_retries = 1 | ||
if os.path.isfile(channeldata_file): | ||
while retry < max_retries: | ||
try: | ||
with open(channeldata_file, "r+") as f: | ||
channel_data[channel.name] = json.load(f) | ||
break | ||
except (OSError, JSONDecodeError): | ||
time.sleep(0.2) | ||
retry += 1 | ||
else: | ||
# download channeldata.json for url | ||
if not context.offline: | ||
try: | ||
channel_data[channel.name] = utils.download_channeldata( | ||
channel.base_url + "/channeldata.json" | ||
) | ||
except CondaHTTPError: | ||
continue | ||
# collapse defaults metachannel back into one superchannel, merging channeldata | ||
if channel.base_url in context.default_channels and channel_data.get( | ||
channel.name | ||
): | ||
packages = superchannel.get("packages", {}) | ||
packages.update(channel_data[channel.name]) | ||
superchannel["packages"] = packages | ||
channel_data["defaults"] = superchannel | ||
local_index_timestamp = os.path.getmtime(index_file) | ||
local_subdir = subdir | ||
local_output_folder = output_folder | ||
cached_channels = channel_urls | ||
return cached_index, local_index_timestamp, channel_data | ||
|
||
|
||
def _ensure_valid_channel(local_folder, subdir): | ||
for folder in {subdir, "noarch"}: | ||
path = os.path.join(local_folder, folder) | ||
if not os.path.isdir(path): | ||
os.makedirs(path) | ||
|
||
|
||
def update_index( | ||
dir_path, | ||
check_md5=False, | ||
channel_name=None, | ||
patch_generator=None, | ||
threads=MAX_THREADS_DEFAULT, | ||
verbose=False, | ||
progress=False, | ||
hotfix_source_repo=None, | ||
subdirs=None, | ||
warn=True, | ||
current_index_versions=None, | ||
debug=False, | ||
index_file=None, | ||
): | ||
return conda_index.index.update_index( | ||
dir_path, | ||
check_md5=check_md5, | ||
channel_name=channel_name, | ||
patch_generator=patch_generator, | ||
threads=threads, | ||
verbose=verbose, | ||
progress=progress, | ||
# hotfix_source_repo=None, # unused | ||
subdirs=subdirs, | ||
warn=warn, | ||
current_index_versions=current_index_versions, | ||
debug=debug, | ||
# index_file=None, # unused | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,9 +6,9 @@ | |
|
||
from conda_build.conda_interface import ArgumentParser | ||
|
||
from conda_build import api | ||
from conda_build.index import MAX_THREADS_DEFAULT | ||
from conda_build.utils import DEFAULT_SUBDIRS | ||
from conda_index import api | ||
from conda_index.index import MAX_THREADS_DEFAULT | ||
from conda_index.utils import DEFAULT_SUBDIRS | ||
|
||
logging.basicConfig(level=logging.INFO) | ||
|
||
|
@@ -77,7 +77,7 @@ def parse_args(args): | |
) | ||
p.add_argument( | ||
"-f", "--file", | ||
help="A file that contains a new line separated list of packages to add to repodata.", | ||
help="A file that contains a new line separated list of packages to add to repodata. Deprecated, will be removed in a future version of conda build", | ||
dholth marked this conversation as resolved.
Show resolved
Hide resolved
|
||
action="store" | ||
) | ||
|
||
|
@@ -90,8 +90,8 @@ def execute(args): | |
|
||
api.update_index(args.dir, check_md5=args.check_md5, channel_name=args.channel_name, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would love to delete this whole file.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this might be able to use the non-api update_index, the api version is basically "call update_index for every dir in a list" |
||
threads=args.threads, subdir=args.subdir, patch_generator=args.patch_generator, | ||
verbose=args.verbose, progress=args.progress, hotfix_source_repo=args.hotfix_source_repo, | ||
current_index_versions=args.current_index_versions_file, index_file=args.file) | ||
verbose=args.verbose, progress=args.progress, | ||
current_index_versions=args.current_index_versions_file) | ||
|
||
|
||
def main(): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,10 +22,9 @@ | |
from .conda_interface import reset_context | ||
from .conda_interface import get_version_from_git_tag | ||
|
||
from conda_build import utils | ||
from conda_build import utils, build_index | ||
from conda_build.exceptions import BuildLockError, DependencyNeedsBuildingError | ||
from conda_build.features import feature_list | ||
from conda_build.index import get_build_index | ||
from conda_build.os_utils import external | ||
from conda_build.utils import ensure_list, prepend_bin_path, env_var | ||
from conda_build.variants import get_default_variant | ||
|
@@ -776,9 +775,10 @@ def get_install_actions(prefix, specs, env, retries=0, subdir=None, | |
|
||
bldpkgs_dirs = ensure_list(bldpkgs_dirs) | ||
|
||
index, index_ts, _ = get_build_index(subdir, list(bldpkgs_dirs)[0], output_folder=output_folder, | ||
channel_urls=channel_urls, debug=debug, verbose=verbose, | ||
locking=locking, timeout=timeout) | ||
bldpkgs_dir = list(bldpkgs_dirs)[0] | ||
index, index_ts, _ = build_index.get_build_index(subdir, bldpkgs_dir, output_folder, False, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. inline function courtesy of pycharm refactor feature |
||
False, channel_urls, debug, verbose, locking=locking, timeout=timeout | ||
) | ||
specs = tuple(utils.ensure_valid_spec(spec) for spec in specs if not str(spec).endswith('@')) | ||
|
||
if ((specs, env, subdir, channel_urls, disable_pip) in cached_actions and | ||
|
@@ -889,14 +889,9 @@ def create_env(prefix, specs_or_actions, env, config, subdir, clear_cache=True, | |
channel_urls=tuple(config.channel_urls)) | ||
else: | ||
actions = specs_or_actions | ||
index, _, _ = get_build_index(subdir=subdir, | ||
bldpkgs_dir=config.bldpkgs_dir, | ||
output_folder=config.output_folder, | ||
channel_urls=config.channel_urls, | ||
debug=config.debug, | ||
verbose=config.verbose, | ||
locking=config.locking, | ||
timeout=config.timeout) | ||
index, _, _ = build_index.get_build_index(subdir, config.bldpkgs_dir, config.output_folder, False, | ||
False, config.channel_urls, config.debug, config.verbose, locking=config.locking, timeout=config.timeout | ||
) | ||
utils.trim_empty_keys(actions) | ||
display_actions(actions, index) | ||
if utils.on_win: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copied to make keeping track of all the changes easier, and to discourage future
conda_build.index
importsThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify, we couldn't continue to have index.py as the place for this code to live? Or asked differently, the get_build_index function, is that something that we have in conda-index? I'm a little worried we're introducing another place where users will end up importing from :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
get_build_index
is only in conda-build. It is mostly about fetching repodata.json instead of generating repodata.json. It would of course be possible to keep it inconda_build.index
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However we might want to raise deprecationwarning when conda_build.index is imported.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this submodule should exist.
Plus, all of
conda.index
should be deprecated with warnings to import fromconda-index
instead.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean
conda.index
orconda_build.index
Reminder that conda-build's
get_build_index
doesn't belong in conda-index.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kenodegard @jezdez I moved this module back to conda_build.index