def cli(path, ignore, exit): """conda-verify is a tool for validating conda packages and recipes. To validate a package:\n $ conda-verify path/to/package.tar.bz2 To validate a recipe:\n $ conda-verify path/to/recipe_directory/ """ verifier = Verify() if ignore: ignore = ignore.split(',') meta_file = os.path.join(path, 'meta.yaml') if os.path.isfile(meta_file): print('Verifying {}...'.format(meta_file)) for cfg in iter_cfgs(): meta = render_metadata(path, cfg) if meta.get('build', {}).get('skip', '').lower() != 'true': verifier.verify_recipe(rendered_meta=meta, recipe_dir=path, checks_to_ignore=ignore, exit_on_error=exit) elif path.endswith(('.tar.bz2', '.tar')): print('Verifying {}...'.format(path)) verifier.verify_package(path_to_package=path, checks_to_ignore=ignore, exit_on_error=exit)
def main(): p = OptionParser( usage="usage: %prog [options] <path to recipes or packages>", description="tool for (passively) verifying conda recipes and conda " "packages for the Anaconda distribution") p.add_option('-e', "--exit", help="on error exit", action="store_true") p.add_option('-p', "--pedantic", action="store_true") p.add_option('-q', "--quiet", action="store_true") p.add_option('-V', '--version', help="display the version being used and exit", action="store_true") opts, args = p.parse_args() verbose = not opts.quiet if opts.version: from conda_verify import __version__ print('conda-verify version:', __version__) return verifier = Verify() for path in args: if isfile(join(path, 'meta.yaml')): if verbose: print("==> %s <==" % path) for cfg in iter_cfgs(): meta = render_metadata(path, cfg) try: verifier.verify_recipe(pedantic=opts.pedantic, rendered_meta=meta, recipe_dir=path) except RecipeError as e: sys.stderr.write("RecipeError: %s\n" % e) if opts.exit: sys.exit(1) elif path.endswith('.tar.bz2'): if verbose: print("==> %s <==" % path) try: verifier.verify_package(pedantic=opts.pedantic, path_to_package=path, verbose=verbose) except PackageError as e: sys.stderr.write("PackageError: %s\n" % e) if opts.exit: sys.exit(1) else: if verbose: print("Ignoring: %s" % path)
def test_invalid_test_files(recipe_dir): recipe = os.path.join(recipe_dir, 'invalid_test_files') metadata = utilities.render_metadata(recipe, None) with pytest.raises(RecipeError): Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=True) package, error = Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=False) assert '[C2124] Found file "test-data.txt" in meta.yaml that doesn\'t exist' in error
def test_invalid_license_family(recipe_dir): recipe = os.path.join(recipe_dir, 'invalid_license_family') metadata = utilities.render_metadata(recipe, None) with pytest.raises(RecipeError): Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=True) package, error = Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=False) assert '[C2122] Found invalid license family "The Extra License"' in error
def test_invalid_source_hash(recipe_dir): recipe = os.path.join(recipe_dir, 'invalid_source_hash') metadata = utilities.render_metadata(recipe, None) with pytest.raises(RecipeError): Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=True) package, error = Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=False) assert '[C2119] Found invalid hash "abc123" in meta.yaml' in error
def test_invalid_about_url(recipe_dir): recipe = os.path.join(recipe_dir, 'invalid_about_url') metadata = utilities.render_metadata(recipe, None) with pytest.raises(RecipeError): Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=True) package, error = Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=False) assert '[C2118] Found invalid URL "www.continuum.io" in meta.yaml' in error
def test_invalid_about_summary(recipe_dir): recipe = os.path.join(recipe_dir, 'invalid_about_summary') metadata = utilities.render_metadata(recipe, None) with pytest.raises(RecipeError): Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=True) package, error = Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=False) assert '[C2117] Found summary with length greater than 80 characters' in error
def test_invalid_build_number_negative(recipe_dir): recipe = os.path.join(recipe_dir, 'invalid_build_number_negative') metadata = utilities.render_metadata(recipe, None) with pytest.raises(RecipeError): Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=True) package, error = Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=False) assert '[C2108] Build number in info/index.json cannot be a negative integer' in error
def test_invalid_package_version_sequence(recipe_dir): recipe = os.path.join(recipe_dir, 'invalid_package_version_sequence') metadata = utilities.render_metadata(recipe, None) with pytest.raises(RecipeError): Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=True) package, error = Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=False) assert '[C2106] Found invalid sequence "._" in package version' in error
def test_invalid_package_version_prefix(recipe_dir): recipe = os.path.join(recipe_dir, 'invalid_package_version_prefix') metadata = utilities.render_metadata(recipe, None) with pytest.raises(RecipeError): Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=True) package, error = Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=False) assert '[C2105] Found invalid package version "_1.0.0rc3" in meta.yaml' in error
def test_invalid_package_name(recipe_dir): recipe = os.path.join(recipe_dir, 'invalid_package_name') metadata = utilities.render_metadata(recipe, None) with pytest.raises(RecipeError): Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=True) package, error = Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=False) assert '[C2102] Found invalid package name "some_package." in meta.yaml' in error
def test_invalid_package_field(recipe_dir): recipe = os.path.join(recipe_dir, 'invalid_package_field') metadata = utilities.render_metadata(recipe, None) with pytest.raises(RecipeError): Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=True) package, error = Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=False) assert '[C2109] Found invalid section "extra_field"' in error
def test_invalid_test_file_path(recipe_dir): recipe = os.path.join(recipe_dir, 'invalid_test_file_path') metadata = utilities.render_metadata(recipe, None) with pytest.raises(RecipeError): Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=True) package, error = Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=False) assert '[C2123] Found file "../test-data.txt" listed outside recipe directory' in error
def test_invalid_multiple_source_field_key(recipe_dir): recipe = os.path.join(recipe_dir, 'invalid_multiple_sources') metadata = utilities.render_metadata(recipe, None) with pytest.raises(RecipeError): Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=True) package, error = Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=False) assert '[C2110] Found invalid field "gti_url" in section "source"' in error
def test_invalid_requirements_field_key(recipe_dir): recipe = os.path.join(recipe_dir, 'invalid_requirements_field_key') metadata = utilities.render_metadata(recipe, None) with pytest.raises(RecipeError): Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=True) package, error = Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=False) assert '[C2110] Found invalid field "extra_field" in section "requirements"' in error
def test_invalid_run_requirement_name(recipe_dir): recipe = os.path.join(recipe_dir, 'invalid_run_requirement_name') metadata = utilities.render_metadata(recipe, None) with pytest.raises(RecipeError): Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=True) package, error = Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=False) assert '[C2112] Found invalid run requirement "python@#"' in error
def test_no_package_name(recipe_dir): recipe = os.path.join(recipe_dir, 'no_package_name') metadata = utilities.render_metadata(recipe, None) with pytest.raises(RecipeError): Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=True) package, error = Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=False) assert '[C2101] Missing package name in meta.yaml' in error
def test_invalid_sources(recipe_dir): recipe = os.path.join(recipe_dir, 'invalid_sources') metadata = utilities.render_metadata(recipe, None) with pytest.raises(RecipeError): Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=True) package, error = Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=False) assert '[C2121] Found both git_branch and git_tag in meta.yaml source field' in error
def test_invalid_build_requirement_version(recipe_dir): recipe = os.path.join(recipe_dir, 'invalid_build_requirement_version') metadata = utilities.render_metadata(recipe, None) with pytest.raises(RecipeError): Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=True) package, error = Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=False) assert '[C2114] Found invalid dependency "setuptools >= 3.4 < 3045" in meta.yaml' in error
def test_invalid_outputs(recipe_dir): recipe = os.path.join(recipe_dir, 'invalid_output') metadata = utilities.render_metadata(recipe, None) with pytest.raises(RecipeError): Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=True) package, error = Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=False) assert '[C2110] Found invalid field "srcitp" in section "outputs"' in error
def test_conda_forge_example_recipe(recipe_dir): recipe = os.path.join(recipe_dir, 'conda_forge') metadata = utilities.render_metadata(recipe, None) with pytest.raises(RecipeError): Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=True) package, error = Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=False) assert '[C2126] Found conda-forge comment in meta.yaml file' in error
def test_duplicate_version_specifications(recipe_dir): recipe = os.path.join(recipe_dir, 'duplicate_version_specs') metadata = utilities.render_metadata(recipe, None) with pytest.raises(RecipeError): Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=True) package, error = Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=False) assert "[C2116] Found duplicate run requirements: ['python', 'python']" in error
def test_invalid_dir_content_filesize(recipe_dir): recipe = os.path.join(recipe_dir, 'invalid_dir_content_filesize') metadata = utilities.render_metadata(recipe, None) with pytest.raises(RecipeError): Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=True) package, error = Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=False) assert any('[C2125] Found disallowed file with extension' in e for e in error) assert any('test.tar.bz2' in e for e in error)
def test_duplicate_build_requirements(recipe_dir): recipe = os.path.join(recipe_dir, 'duplicate_build_requirements') metadata = utilities.render_metadata(recipe, None) with pytest.raises(RecipeError): Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=True) package, error = Verify.verify_recipe(rendered_meta=metadata, recipe_dir=recipe, exit_on_error=False) assert ( "[C2115] Found duplicate build requirements: ['python', 'python']" in error or "[C2115] Found duplicate build requirements: ['python 3.6.*', 'python 3.6.*']" in error)
def build_tree(recipe_list, config, build_only=False, post=False, notest=False, need_source_download=True, need_reparse_in_env=False): to_build_recursive = [] recipe_list = deque(recipe_list) if on_win: trash_dir = os.path.join(os.path.dirname(sys.executable), 'pkgs', '.trash') if os.path.isdir(trash_dir): # We don't really care if this does a complete job. # Cleaning up some files is better than none. subprocess.call('del /s /q "{0}\\*.*" >nul 2>&1'.format(trash_dir), shell=True) # delete_trash(None) already_built = set() extra_help = "" while recipe_list: # This loop recursively builds dependencies if recipes exist if build_only: post = False notest = True config.anaconda_upload = False elif post: post = True notest = True config.anaconda_upload = False else: post = None recipe = recipe_list.popleft() if hasattr(recipe, 'config'): metadata = recipe recipe_config = metadata.config # this code is duplicated below because we need to be sure that the build id is set # before downloading happens - or else we lose where downloads are if recipe_config.set_build_id: recipe_config.compute_build_id(metadata.name(), reset=True) recipe_parent_dir = "" to_build_recursive.append(metadata.name()) else: recipe_parent_dir = os.path.dirname(recipe) recipe = recipe.rstrip("/").rstrip("\\") recipe_config = config to_build_recursive.append(os.path.basename(recipe)) # before downloading happens - or else we lose where downloads are if recipe_config.set_build_id: recipe_config.compute_build_id(os.path.basename(recipe), reset=True) metadata, need_source_download, need_reparse_in_env = render_recipe(recipe, config=recipe_config) if not getattr(config, "noverify", False): verifier = Verify() ignore_scripts = context.ignore_recipe_verify_scripts if \ context.ignore_recipe_verify_scripts else None run_scripts = context.run_recipe_verify_scripts if \ context.run_recipe_verify_scripts else None verifier.verify_recipe(ignore_scripts=ignore_scripts, run_scripts=run_scripts, rendered_meta=metadata.meta, recipe_dir=metadata.path) try: with recipe_config: ok_to_test = build(metadata, post=post, need_source_download=need_source_download, need_reparse_in_env=need_reparse_in_env, config=recipe_config) if not notest and ok_to_test: test(metadata, config=recipe_config) except (NoPackagesFound, NoPackagesFoundError, Unsatisfiable, CondaValueError) as e: error_str = str(e) skip_names = ['python', 'r'] add_recipes = [] # add the failed one back in at the beginning - but its deps may come before it recipe_list.extendleft([recipe]) for line in error_str.splitlines(): if not line.startswith(' - '): continue pkg = line.lstrip(' - ').split(' -> ')[-1] pkg = pkg.strip().split(' ')[0] if pkg in to_build_recursive: raise RuntimeError("Can't build {0} due to unsatisfiable dependencies:\n" .format(recipe) + error_str + "\n" + extra_help) if pkg in skip_names: to_build_recursive.append(pkg) extra_help = """Typically if a conflict is with the Python or R packages, the other package needs to be rebuilt (e.g., a conflict with 'python 3.5*' and 'x' means 'x' isn't build for Python 3.5 and needs to be rebuilt.""" recipe_glob = glob(os.path.join(recipe_parent_dir, pkg)) if recipe_glob: for recipe_dir in recipe_glob: print(error_str) print(("Missing dependency {0}, but found" + " recipe directory, so building " + "{0} first").format(pkg)) add_recipes.append(recipe_dir) else: raise RuntimeError("Can't build {0} due to unsatisfiable dependencies:\n" .format(recipe) + error_str + "\n\n" + extra_help) recipe_list.extendleft(add_recipes) # outputs message, or does upload, depending on value of args.anaconda_upload if post in [True, None]: output_file = bldpkg_path(metadata, config=recipe_config) handle_anaconda_upload(output_file, config=recipe_config) already_built.add(output_file)