Skip to content
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

[WIP]cran support #423

Merged
merged 31 commits into from
Jan 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0e34c4e
take bits from conda-skeleton code and add cran subparser
ForgottenProgramme Mar 7, 2022
008675d
get index
ForgottenProgramme Mar 15, 2022
9b4eb39
add cranstrategy abstract class
ForgottenProgramme Mar 15, 2022
e938e69
add create_r_recipe_from_list method
ForgottenProgramme Mar 15, 2022
8be936f
add conditions in parser
ForgottenProgramme Mar 16, 2022
4567d72
Fix fetch_data interface
marcelotrevisani Mar 18, 2022
f333a5b
remove pip
ForgottenProgramme Mar 23, 2022
75dfc80
populate get_cran_metadata func
ForgottenProgramme Mar 23, 2022
92fabcd
download tarball
ForgottenProgramme Mar 24, 2022
f4b68ef
Write downloaded tarball to disk.
jezdez Mar 24, 2022
2a5d627
return recipe
ForgottenProgramme Mar 24, 2022
d51358c
format requirements list
ForgottenProgramme Mar 25, 2022
4f0888d
add comment
ForgottenProgramme Mar 28, 2022
0e35e12
Recipe adjustments for constrains in run requirements
marcelotrevisani Mar 28, 2022
24c760f
add 'r-base' to host and run req everytime
ForgottenProgramme Jul 5, 2022
62bfaa6
sort the package list
ForgottenProgramme Jul 5, 2022
3954133
commit errorneous code with comment
ForgottenProgramme Jul 6, 2022
15f166c
add cran metadata as comment at the top of the recipe
ForgottenProgramme Jul 7, 2022
58d3340
add posix and native at the top of the recipe
ForgottenProgramme Jul 13, 2022
c0bd076
add cli test for cran
ForgottenProgramme Jul 13, 2022
970e5b3
Update grayskull/__main__.py
ForgottenProgramme Jul 20, 2022
efe21ab
Update grayskull/strategy/cran.py
ForgottenProgramme Jul 20, 2022
f8d4d27
Refactoring structure
marcelotrevisani Jan 13, 2023
2b2b75c
Return with config.yaml
marcelotrevisani Jan 14, 2023
e0b9624
Improvements related to the scrapping of cran webpage. Add tests to s…
marcelotrevisani Jan 16, 2023
7733647
Improvements in recipe format
marcelotrevisani Jan 17, 2023
7b943cd
Improvements in recipe order
marcelotrevisani Jan 17, 2023
6afdeac
Add script instructions to R recipes
marcelotrevisani Jan 17, 2023
f67eacc
Strip mamba version from workflow
marcelotrevisani Jan 17, 2023
52f5341
Remove typehint for now
marcelotrevisani Jan 17, 2023
eaea455
Mark gh test as xfail
marcelotrevisani Jan 17, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ jobs:
- uses: conda-incubator/setup-miniconda@v2
with:
auto-update-conda: true
mamba-version: "*"
channels: conda-forge,defaults
channel-priority: true
python-version: ${{ matrix.py_ver }}
Expand Down
12 changes: 4 additions & 8 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
exclude: docs
repos:
- repo: https://github.com/psf/black
rev: 22.10.0
rev: 22.12.0
hooks:
- id: black
args: [--safe, --quiet]
Expand All @@ -11,7 +11,7 @@ repos:
- id: blacken-docs
additional_dependencies: [black==22.6.0]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
Expand All @@ -29,17 +29,13 @@ repos:
- id: debug-statements
language_version: python3
- repo: https://github.com/PyCQA/isort
rev: 5.10.1
rev: 5.11.4
hooks:
- id: isort
exclude: tests/data
- repo: https://github.com/PyCQA/flake8
rev: 5.0.4
rev: 6.0.0
hooks:
- id: flake8
exclude: tests/data
language_version: python3
additional_dependencies:
- flake8-typing-imports==1.9.0
- flake8-builtins==1.5.3
- flake8-bugbear==20.1.4
1 change: 1 addition & 0 deletions environment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ dependencies:
- tomli
- tomli-w
- libcblas
- beautifulsoup4
159 changes: 137 additions & 22 deletions grayskull/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,83 @@ def main(args=None):
if not args:
args = sys.argv[1:] or ["--help"]

# create the top-level parser
parser = argparse.ArgumentParser(description="Grayskull - Conda recipe generator")
pypi_parser = parser.add_subparsers(help="Options to generate PyPI recipes")
pypi_cmds = pypi_parser.add_parser("pypi", help="Generate recipes based on PyPI")
pypi_cmds.add_argument(
subparsers = parser.add_subparsers(help="sub-command help")
# create parser for cran
cran_parser = subparsers.add_parser("cran", help="Options to generate CRAN recipes")
cran_parser.add_argument(
"cran_packages", nargs="+", help="Specify the CRAN packages name.", default=[]
)
cran_parser.add_argument(
"--stdout",
dest="stdout",
default=True,
help="Disable or enable stdout, if it is False, Grayskull"
" will disable the prints. Default is True",
)
cran_parser.add_argument(
"--list-missing-deps",
default=False,
action="store_true",
dest="list_missing_deps",
help="After the execution Grayskull will print all the missing dependencies.",
)
cran_parser.add_argument(
"--download",
"-d",
dest="download",
action="store_true",
default=False,
help="Download the sdist package and PyPI information in the same folder"
" the recipe is located.",
)
cran_parser.add_argument(
"--maintainers",
"-m",
dest="maintainers",
nargs="+",
help="List of maintainers which will be added to the recipe.",
)
cran_parser.add_argument(
"--output",
"-o",
dest="output",
default=".",
help="Path to where the recipe will be created",
)
cran_parser.add_argument(
"--strict-conda-forge",
default=False,
action="store_true",
dest="is_strict_conda_forge",
help="It will generate the recipes strict for the conda-forge channel.",
)
cran_parser.add_argument(
"--sections",
"-s",
default=None,
required=False,
choices=(
"package",
"source",
"build",
"requirements",
"test",
"about",
"extra",
),
nargs="+",
dest="sections_populate",
help="If sections are specified, grayskull will populate just the sections "
"informed.",
)
# create parser for pypi
pypi_parser = subparsers.add_parser("pypi", help="Options to generate PyPI recipes")
pypi_parser.add_argument(
"pypi_packages", nargs="+", help="Specify the PyPI packages name.", default=[]
)
pypi_cmds.add_argument(
pypi_parser.add_argument(
"--download",
"-d",
dest="download",
Expand All @@ -40,7 +110,7 @@ def main(args=None):
help="Download the sdist package and PyPI information in the same folder"
" the recipe is located.",
)
pypi_cmds.add_argument(
pypi_parser.add_argument(
"--maintainers",
"-m",
dest="maintainers",
Expand All @@ -63,49 +133,49 @@ def main(args=None):
dest="grayskull_power",
help=argparse.SUPPRESS,
)
pypi_cmds.add_argument(
pypi_parser.add_argument(
"--output",
"-o",
dest="output",
default=".",
help="Path to where the recipe will be created",
)
pypi_cmds.add_argument(
pypi_parser.add_argument(
"--stdout",
dest="stdout",
default=True,
help="Disable or enable stdout, if it is False, Grayskull"
" will disable the prints. Default is True",
)
pypi_cmds.add_argument(
pypi_parser.add_argument(
"--list-missing-deps",
default=False,
action="store_true",
dest="list_missing_deps",
help="After the execution Grayskull will print all the missing dependencies.",
)
pypi_cmds.add_argument(
pypi_parser.add_argument(
"--strict-conda-forge",
default=False,
action="store_true",
dest="is_strict_conda_forge",
help="It will generate the recipes strict for the conda-forge channel.",
)
pypi_cmds.add_argument(
pypi_parser.add_argument(
"--pypi-url",
default="https://pypi.org/pypi/",
dest="url_pypi_metadata",
help="Pypi url server",
)
pypi_cmds.add_argument(
pypi_parser.add_argument(
"--recursive",
"-r",
default=False,
action="store_true",
dest="is_recursive",
help="Recursively run grayskull on missing dependencies.",
)
pypi_cmds.add_argument(
pypi_parser.add_argument(
"--sections",
"-s",
default=None,
Expand All @@ -124,51 +194,51 @@ def main(args=None):
help="If sections are specified, grayskull will populate just the sections "
"informed.",
)
pypi_cmds.add_argument(
pypi_parser.add_argument(
"--extras-require-test",
default=None,
dest="extras_require_test",
help="Extra requirements to run tests.",
)
pypi_cmds.add_argument(
pypi_parser.add_argument(
"--tag",
"-t",
default=None,
dest="github_release_tag",
help="If tag is specified, grayskull will build from release tag",
)
pypi_cmds.add_argument(
pypi_parser.add_argument(
"--extras-require-all",
default=False,
action="store_true",
dest="extras_require_all",
help="Include all extra requirements.",
)
pypi_cmds.add_argument(
pypi_parser.add_argument(
"--extras-require-include",
default=tuple(),
type=str,
nargs="*",
dest="extras_require_include",
help="Include these extra requirements.",
)
pypi_cmds.add_argument(
pypi_parser.add_argument(
"--extras-require-exclude",
default=tuple(),
type=str,
nargs="*",
dest="extras_require_exclude",
help="Exclude these extra requirements (overrides include).",
)
pypi_cmds.add_argument(
pypi_parser.add_argument(
"--extras-require-split",
default=False,
action="store_true",
dest="extras_require_split",
help="Create a separate conda package for each extra requirements."
" Ignored when no extra requirements are selected.",
)
pypi_cmds.add_argument(
pypi_parser.add_argument(
"--licence-exclude-folders",
default=tuple(),
nargs="*",
Expand Down Expand Up @@ -197,7 +267,10 @@ def main(args=None):
print_msg(Style.RESET_ALL)
print_msg(clear_screen())

generate_recipes_from_list(args.pypi_packages, args)
if getattr(args, "pypi_packages", None):
generate_recipes_from_list(args.pypi_packages, args)
elif getattr(args, "cran_packages", None):
generate_r_recipes_from_list(args.cran_packages, args)


def generate_recipes_from_list(list_pkgs, args):
Expand All @@ -215,8 +288,7 @@ def generate_recipes_from_list(list_pkgs, args):
f"#### Initializing recipe for "
f"{Fore.BLUE}{pkg_name}{pypi_label} {Fore.GREEN}####\n"
)
is_pkg_file = Path(pkg_name).is_file() and (not from_local_sdist)
if is_pkg_file:
if Path(pkg_name).is_file() and (not from_local_sdist):
args.output = pkg_name
try:
recipe, config = create_python_recipe(
Expand Down Expand Up @@ -261,6 +333,49 @@ def create_python_recipe(pkg_name, sections_populate=None, **kwargs):
)


def generate_r_recipes_from_list(list_pkgs, args):
cran_label = " (cran)"
for pkg_name in list_pkgs:
logging.debug(f"Starting grayskull for pkg: {pkg_name}")
from_local_sdist = origin_is_local_sdist(pkg_name)
print_msg(
f"{Fore.GREEN}\n\n"
f"#### Initializing recipe for "
f"{Fore.BLUE}{pkg_name}{cran_label} {Fore.GREEN}####\n"
)
if Path(pkg_name).is_file() and (not from_local_sdist):
args.output = pkg_name
try:
recipe, config = create_r_recipe(
pkg_name,
is_strict_cf=args.is_strict_conda_forge,
download=args.download,
sections_populate=args.sections_populate,
)
except requests.exceptions.HTTPError as err:
print_msg(f"{Fore.RED}Package seems to be missing.\nException: {err}\n\n")
continue

if args.sections_populate is None or "extra" in args.sections_populate:
add_extra_section(recipe, args.maintainers)

generate_recipe(recipe, config, args.output)
print_msg(
f"\n{Fore.GREEN}#### Recipe generated on "
f"{os.path.realpath(args.output)} for {pkg_name} ####\n\n"
)


def create_r_recipe(pkg_name, sections_populate=None, **kwargs):
config = Configuration(name=pkg_name, **kwargs)
return (
GrayskullFactory.create_recipe(
"cran", config, sections_populate=sections_populate
),
config,
)


def add_extra_section(recipe, maintainers: Optional[List] = None):
maintainers = maintainers or [get_git_current_user()]
if "extra" in recipe:
Expand Down
24 changes: 21 additions & 3 deletions grayskull/base/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@
from abc import ABC
from pathlib import Path

from souschef.ingredient import Ingredient
from souschef.jinja_expression import get_global_jinja_var
from souschef.recipe import Recipe

from grayskull.strategy.cran import CranStrategy
from grayskull.strategy.pypi import PypiStrategy


class GrayskullFactory(ABC):
REGISTERED_STRATEGY = {
"pypi": PypiStrategy,
"cran": CranStrategy,
}

@staticmethod
Expand All @@ -23,11 +26,23 @@ def create_recipe(repo_type: str, config, pkg_name=None, sections_populate=None)
)
if Path(config.name).is_file() and not config.from_local_sdist:
recipe = Recipe(load_file=config.name)
config.name = _get_name(recipe)
config.version = _get_version(recipe)
recipe_name = _get_name(recipe)
recipe_version = _get_version(recipe)
config.name = (
recipe_name.value
if isinstance(recipe_name, Ingredient)
else recipe_name
)
config.version = (
recipe_version.value
if isinstance(recipe_version, Ingredient)
else recipe_version
)
else:
pkg_name = pkg_name or config.name
recipe = Recipe(name=pkg_name, version=config.version)
if config.name.startswith(("<{", "{{", "r-{{", "r-<{")):
config.name = get_global_jinja_var(recipe, "name")
GrayskullFactory.REGISTERED_STRATEGY[repo_type.lower()].fetch_data(
recipe, config, sections=sections_populate
)
Expand All @@ -49,7 +64,10 @@ def __get_var(recipe, val):
re_jinja_var = re.match(r"\s*<{\s*(\w+)", recipe["package"][val].value)
if re_jinja_var:
jinja_var = re_jinja_var.groups()[0]
return get_global_jinja_var(recipe, jinja_var)
try:
return get_global_jinja_var(recipe, jinja_var)
except ValueError:
return None
return recipe["package"][val]


Expand Down
Loading