mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 12:54:40 +01:00
352 lines
9.4 KiB
Python
352 lines
9.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Package for handling AYON command line arguments."""
|
|
import os
|
|
import sys
|
|
import logging
|
|
import code
|
|
import traceback
|
|
from pathlib import Path
|
|
import warnings
|
|
|
|
import click
|
|
|
|
from ayon_core import AYON_CORE_ROOT
|
|
from ayon_core.addon import AddonsManager
|
|
from ayon_core.settings import get_general_environments
|
|
from ayon_core.lib import (
|
|
initialize_ayon_connection,
|
|
is_running_from_build,
|
|
Logger,
|
|
)
|
|
from ayon_core.lib.env_tools import (
|
|
parse_env_variables_structure,
|
|
compute_env_variables_structure,
|
|
merge_env_variables,
|
|
)
|
|
|
|
|
|
@click.group(invoke_without_command=True)
|
|
@click.pass_context
|
|
@click.option(
|
|
"--use-staging",
|
|
is_flag=True,
|
|
expose_value=False,
|
|
help="use staging variants")
|
|
@click.option(
|
|
"--debug",
|
|
is_flag=True,
|
|
expose_value=False,
|
|
help="Enable debug")
|
|
@click.option(
|
|
"--project",
|
|
help="Project name")
|
|
@click.option(
|
|
"--verbose",
|
|
expose_value=False,
|
|
help="Change AYON log level (debug - critical or 0-50)")
|
|
@click.option(
|
|
"--use-dev",
|
|
is_flag=True,
|
|
expose_value=False,
|
|
help="use dev bundle")
|
|
def main_cli(ctx, *_args, **_kwargs):
|
|
"""AYON is main command serving as entry point to pipeline system.
|
|
|
|
It wraps different commands together.
|
|
"""
|
|
if ctx.invoked_subcommand is None:
|
|
# Print help if headless mode is used
|
|
if os.getenv("AYON_HEADLESS_MODE") == "1":
|
|
print(ctx.get_help())
|
|
sys.exit(0)
|
|
else:
|
|
ctx.params.pop("project")
|
|
ctx.forward(tray)
|
|
|
|
|
|
@main_cli.command()
|
|
@click.option(
|
|
"--force",
|
|
is_flag=True,
|
|
help="Force to start tray and close any existing one.")
|
|
def tray(force):
|
|
"""Launch AYON tray.
|
|
|
|
Default action of AYON command is to launch tray widget to control basic
|
|
aspects of AYON. See documentation for more information.
|
|
"""
|
|
from ayon_core.tools.tray import main
|
|
|
|
main(force)
|
|
|
|
|
|
@main_cli.group(help="Run command line arguments of AYON addons")
|
|
@click.pass_context
|
|
def addon(ctx):
|
|
"""Addon specific commands created dynamically.
|
|
|
|
These commands are generated dynamically by currently loaded addons.
|
|
"""
|
|
pass
|
|
|
|
|
|
|
|
@main_cli.command()
|
|
@click.pass_context
|
|
@click.argument("path", required=True)
|
|
@click.option("-t", "--targets", help="Targets", default=None,
|
|
multiple=True)
|
|
def publish(ctx, path, targets):
|
|
"""Start CLI publishing.
|
|
|
|
Publish collects json from path provided as an argument.
|
|
|
|
"""
|
|
from ayon_core.pipeline.publish import main_cli_publish
|
|
|
|
main_cli_publish(path, targets, ctx.obj["addons_manager"])
|
|
|
|
|
|
@main_cli.command(context_settings={"ignore_unknown_options": True})
|
|
def publish_report_viewer():
|
|
from ayon_core.tools.publisher.publish_report_viewer import main
|
|
|
|
sys.exit(main())
|
|
|
|
|
|
@main_cli.command()
|
|
@click.argument("output_path")
|
|
@click.option("--project", help="Define project context")
|
|
@click.option(
|
|
"--folder", help="Define folder in project (project must be set)")
|
|
@click.option(
|
|
"--strict",
|
|
is_flag=True,
|
|
help="Full context must be set otherwise dialog can't be closed."
|
|
)
|
|
def contextselection(
|
|
output_path,
|
|
project,
|
|
folder,
|
|
strict
|
|
):
|
|
"""Show Qt dialog to select context.
|
|
|
|
Context is project name, folder path and task name. The result is stored
|
|
into json file which path is passed in first argument.
|
|
"""
|
|
from ayon_core.tools.context_dialog import main
|
|
|
|
main(output_path, project, folder, strict)
|
|
|
|
|
|
@main_cli.command(
|
|
context_settings=dict(
|
|
ignore_unknown_options=True,
|
|
allow_extra_args=True))
|
|
@click.argument("script", required=True, type=click.Path(exists=True))
|
|
def run(script):
|
|
"""Run python script in AYON context."""
|
|
import runpy
|
|
|
|
if not script:
|
|
print("Error: missing path to script file.")
|
|
return
|
|
|
|
# Remove first argument if it is the same as AYON executable
|
|
# - Forward compatibility with future AYON versions.
|
|
# - Current AYON launcher keeps the arguments with first argument but
|
|
# future versions might remove it.
|
|
first_arg = sys.argv[0]
|
|
if is_running_from_build():
|
|
comp_path = os.getenv("AYON_EXECUTABLE")
|
|
else:
|
|
comp_path = os.path.join(os.environ["AYON_ROOT"], "start.py")
|
|
# Compare paths and remove first argument if it is the same as AYON
|
|
if Path(first_arg).resolve() == Path(comp_path).resolve():
|
|
sys.argv.pop(0)
|
|
|
|
# Remove 'run' command from sys.argv
|
|
sys.argv.remove("run")
|
|
|
|
args_string = " ".join(sys.argv[1:])
|
|
print(f"... running: {script} {args_string}")
|
|
runpy.run_path(script, run_name="__main__")
|
|
|
|
|
|
@main_cli.command()
|
|
def interactive():
|
|
"""Interactive (Python like) console.
|
|
|
|
Helpful command not only for development to directly work with python
|
|
interpreter.
|
|
|
|
Warning:
|
|
Executable 'ayon.exe' on Windows won't work.
|
|
"""
|
|
version = os.environ["AYON_VERSION"]
|
|
banner = (
|
|
f"AYON launcher {version}\nPython {sys.version} on {sys.platform}"
|
|
)
|
|
code.interact(banner)
|
|
|
|
|
|
@main_cli.command()
|
|
@click.option("--build", help="Print only build version",
|
|
is_flag=True, default=False)
|
|
def version(build):
|
|
"""Print AYON launcher version.
|
|
|
|
Deprecated:
|
|
This function has questionable usage.
|
|
"""
|
|
print(os.environ["AYON_VERSION"])
|
|
|
|
|
|
@main_cli.command()
|
|
@click.option(
|
|
"--project",
|
|
type=str,
|
|
help="Project name",
|
|
required=True)
|
|
def create_project_structure(
|
|
project,
|
|
):
|
|
"""Create project folder structure as defined in setting
|
|
`ayon+settings://core/project_folder_structure`
|
|
|
|
Args:
|
|
project (str): The name of the project for which you
|
|
want to create its additional folder structure.
|
|
|
|
"""
|
|
|
|
from ayon_core.pipeline.project_folders import create_project_folders
|
|
|
|
print(f">>> Creating project folder structure for project '{project}'.")
|
|
create_project_folders(project)
|
|
|
|
|
|
def _set_global_environments() -> None:
|
|
"""Set global AYON environments."""
|
|
# First resolve general environment
|
|
general_env = parse_env_variables_structure(get_general_environments())
|
|
|
|
# Merge environments with current environments and update values
|
|
merged_env = merge_env_variables(
|
|
compute_env_variables_structure(general_env),
|
|
dict(os.environ)
|
|
)
|
|
env = compute_env_variables_structure(merged_env)
|
|
os.environ.clear()
|
|
os.environ.update(env)
|
|
|
|
# Hardcoded default values
|
|
# Change scale factor only if is not set
|
|
if "QT_AUTO_SCREEN_SCALE_FACTOR" not in os.environ:
|
|
os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1"
|
|
|
|
|
|
def _set_addons_environments(addons_manager):
|
|
"""Set global environments for AYON addons."""
|
|
|
|
# Merge environments with current environments and update values
|
|
if module_envs := addons_manager.collect_global_environments():
|
|
parsed_envs = parse_env_variables_structure(module_envs)
|
|
env = merge_env_variables(parsed_envs, dict(os.environ))
|
|
os.environ.clear()
|
|
os.environ.update(env)
|
|
|
|
|
|
def _add_addons(addons_manager):
|
|
"""Modules/Addons can add their cli commands dynamically."""
|
|
log = Logger.get_logger("CLI-AddAddons")
|
|
for addon_obj in addons_manager.addons:
|
|
try:
|
|
addon_obj.cli(addon)
|
|
|
|
except Exception:
|
|
log.warning(
|
|
"Failed to add cli command for module \"{}\"".format(
|
|
addon_obj.name
|
|
), exc_info=True
|
|
)
|
|
|
|
|
|
def _cleanup_project_args():
|
|
rem_args = list(sys.argv[1:])
|
|
if "--project" not in rem_args:
|
|
return
|
|
|
|
cmd = None
|
|
current_ctx = None
|
|
parent_name = "ayon"
|
|
parent_cmd = main_cli
|
|
while hasattr(parent_cmd, "resolve_command"):
|
|
if current_ctx is None:
|
|
current_ctx = main_cli.make_context(parent_name, rem_args)
|
|
else:
|
|
current_ctx = parent_cmd.make_context(
|
|
parent_name,
|
|
rem_args,
|
|
parent=current_ctx
|
|
)
|
|
if not rem_args:
|
|
break
|
|
cmd_name, cmd, rem_args = parent_cmd.resolve_command(
|
|
current_ctx, rem_args
|
|
)
|
|
parent_name = cmd_name
|
|
parent_cmd = cmd
|
|
|
|
if cmd is None:
|
|
return
|
|
|
|
param_names = {param.name for param in cmd.params}
|
|
if "project" in param_names:
|
|
return
|
|
idx = sys.argv.index("--project")
|
|
sys.argv.pop(idx)
|
|
sys.argv.pop(idx)
|
|
|
|
|
|
def main(*args, **kwargs):
|
|
logging.basicConfig()
|
|
|
|
initialize_ayon_connection()
|
|
python_path = os.getenv("PYTHONPATH", "")
|
|
split_paths = python_path.split(os.pathsep)
|
|
|
|
additional_paths = [
|
|
# add common AYON vendor
|
|
# (common for multiple Python interpreter versions)
|
|
os.path.join(AYON_CORE_ROOT, "vendor", "python")
|
|
]
|
|
for path in additional_paths:
|
|
if path not in split_paths:
|
|
split_paths.insert(0, path)
|
|
if path not in sys.path:
|
|
sys.path.insert(0, path)
|
|
os.environ["PYTHONPATH"] = os.pathsep.join(split_paths)
|
|
|
|
print(">>> loading environments ...")
|
|
print(" - global AYON ...")
|
|
_set_global_environments()
|
|
print(" - for addons ...")
|
|
addons_manager = AddonsManager()
|
|
_set_addons_environments(addons_manager)
|
|
_add_addons(addons_manager)
|
|
|
|
_cleanup_project_args()
|
|
|
|
try:
|
|
main_cli(
|
|
prog_name="ayon",
|
|
obj={"addons_manager": addons_manager},
|
|
args=(sys.argv[1:]),
|
|
)
|
|
except Exception: # noqa
|
|
exc_info = sys.exc_info()
|
|
print("!!! AYON crashed:")
|
|
traceback.print_exception(*exc_info)
|
|
sys.exit(1)
|