Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

Add validator to check correct version of extension for PS and AE #2387

Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
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
27 changes: 27 additions & 0 deletions openpype/hosts/aftereffects/plugins/publish/closeAE.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
"""Close AE after publish. For Webpublishing only."""
import pyblish.api

from avalon import aftereffects


class CloseAE(pyblish.api.ContextPlugin):
"""Close AE after publish. For Webpublishing only.
"""

order = pyblish.api.IntegratorOrder + 14
label = "Close AE"
optional = True
active = True

hosts = ["aftereffects"]
targets = ["remotepublish"]

def process(self, context):
self.log.info("CloseAE")

stub = aftereffects.stub()
self.log.info("Shutting down AE")
stub.save()
stub.close()
self.log.info("AE closed")
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
class CollectCurrentFile(pyblish.api.ContextPlugin):
"""Inject the current working file into context"""

order = pyblish.api.CollectorOrder - 0.5
order = pyblish.api.CollectorOrder - 0.49
label = "Current File"
hosts = ["aftereffects"]

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import os
import re
import pyblish.api

from avalon import aftereffects


class CollectExtensionVersion(pyblish.api.ContextPlugin):
""" Pulls and compares version of installed extension.

It is recommended to use same extension as in provided Openpype code.

Please use Anastasiy’s Extension Manager or ZXPInstaller to update
extension in case of an error.

You can locate extension.zxp in your installed Openpype code in
`repos/avalon-core/avalon/aftereffects`
"""
# This technically should be a validator, but other collectors might be
# impacted with usage of obsolete extension, so collector that runs first
# was chosen
order = pyblish.api.CollectorOrder - 0.5
label = "Collect extension version"
hosts = ["aftereffects"]

optional = True
active = True

def process(self, context):
installed_version = aftereffects.stub().get_extension_version()

if not installed_version:
raise ValueError("Unknown version, probably old extension")

manifest_url = os.path.join(os.path.dirname(aftereffects.__file__),
"extension", "CSXS", "manifest.xml")

if not os.path.exists(manifest_url):
self.log.debug("Unable to locate extension manifest, not checking")
return

expected_version = None
with open(manifest_url) as fp:
content = fp.read()
found = re.findall(r'(ExtensionBundleVersion=")([0-9\.]+)(")',
content)
if found:
expected_version = found[0][1]

if expected_version != installed_version:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be in a validator

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but "It should be technically validator, but it could miss some failing collectors which would result in non understandable errors."

msg = "Expected version '{}' found '{}'\n".format(
expected_version, installed_version)
msg += "Please update your installed extension, it might not work "
msg += "properly."
kalisp marked this conversation as resolved.
Show resolved Hide resolved

raise ValueError(msg)
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@ def process(self, instance):
staging_dir = instance.data["stagingDir"]
self.log.info("staging_dir::{}".format(staging_dir))

stub.render(staging_dir)

# pull file name from Render Queue Output module
render_q = stub.get_render_info()
stub.render(staging_dir)
if not render_q:
raise ValueError("No file extension set in Render Queue")
_, ext = os.path.splitext(os.path.basename(render_q.file_name))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
class CollectCurrentFile(pyblish.api.ContextPlugin):
"""Inject the current working file into context"""

order = pyblish.api.CollectorOrder - 0.5
order = pyblish.api.CollectorOrder - 0.49
label = "Current File"
hosts = ["photoshop"]

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import os
import re
import pyblish.api

from avalon import photoshop


class CollectExtensionVersion(pyblish.api.ContextPlugin):
""" Pulls and compares version of installed extension.

It is recommended to use same extension as in provided Openpype code.

Please use Anastasiy’s Extension Manager or ZXPInstaller to update
extension in case of an error.

You can locate extension.zxp in your installed Openpype code in
`repos/avalon-core/avalon/photoshop`
"""
# This technically should be a validator, but other collectors might be
# impacted with usage of obsolete extension, so collector that runs first
# was chosen
order = pyblish.api.CollectorOrder - 0.5
label = "Collect extension version"
hosts = ["photoshop"]

optional = True
active = True

def process(self, context):
installed_version = photoshop.stub().get_extension_version()

if not installed_version:
raise ValueError("Unknown version, probably old extension")

manifest_url = os.path.join(os.path.dirname(photoshop.__file__),
"extension", "CSXS", "manifest.xml")

if not os.path.exists(manifest_url):
self.log.debug("Unable to locate extension manifest, not checking")
return

expected_version = None
with open(manifest_url) as fp:
content = fp.read()

found = re.findall(r'(ExtensionBundleVersion=")([0-10\.]+)(")',
content)
if found:
expected_version = found[0][1]

if expected_version != installed_version:
msg = "Expected version '{}' found '{}'\n".format(
expected_version, installed_version)
msg += "Please update your installed extension, it might not work "
msg += "properly."

raise ValueError(msg)
46 changes: 46 additions & 0 deletions openpype/lib/remote_publish.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,25 @@
from openpype.lib.plugin_tools import parse_json


def headless_publish(log, close_plugin_name=None, is_test=False):
"""Runs publish in a opened host with a context and closes Python process.

Host is being closed via ClosePS pyblish plugin which triggers 'exit'
method in ConsoleTrayApp.
"""
if not is_test:
dbcon = get_webpublish_conn()
_id = os.environ.get("BATCH_LOG_ID")
if not _id:
log.warning("Unable to store log records, "
"batch will be unfinished!")
return

publish_and_log(dbcon, _id, log, close_plugin_name)
else:
publish(log, close_plugin_name)


def get_webpublish_conn():
"""Get connection to OP 'webpublishes' collection."""
mongo_client = OpenPypeMongoConnection.get_mongo_client()
Expand All @@ -37,6 +56,33 @@ def start_webpublish_log(dbcon, batch_id, user):
}).inserted_id


def publish(log, close_plugin_name=None):
"""Loops through all plugins, logs to console. Used for tests.

Args:
log (OpenPypeLogger)
close_plugin_name (str): name of plugin with responsibility to
close host app
"""
# Error exit as soon as any error occurs.
error_format = "Failed {plugin.__name__}: {error} -- {error.traceback}"

close_plugin = _get_close_plugin(close_plugin_name, log)

for result in pyblish.util.publish_iter():
for record in result["records"]:
log.info("{}: {}".format(
result["plugin"].label, record.msg))

if result["error"]:
log.error(error_format.format(**result))
uninstall()
if close_plugin: # close host app explicitly after error
context = pyblish.api.Context()
close_plugin().process(context)
sys.exit(1)


def publish_and_log(dbcon, _id, log, close_plugin_name=None):
"""Loops through all plugins, logs ok and fails into OP DB.

Expand Down
17 changes: 17 additions & 0 deletions openpype/settings/defaults/system_settings/applications.json
Original file line number Diff line number Diff line change
Expand Up @@ -1101,6 +1101,23 @@
"linux": []
},
"environment": {}
},
"2022": {
"enabled": true,
"variant_label": "2022",
"executables": {
"windows": [
"C:\\Program Files\\Adobe\\Adobe After Effects 2022\\Support Files\\AfterFX.exe"
],
"darwin": [],
"linux": []
},
"arguments": {
"windows": [],
"darwin": [],
"linux": []
},
"environment": {}
}
}
},
Expand Down
6 changes: 3 additions & 3 deletions tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ How to run:
----------
- single test class could be run by PyCharm and its pytest runner directly
- OR
- use Openpype command 'runtests' from command line
-- `${OPENPYPE_ROOT}/start.py runtests`
- use Openpype command 'runtests' from command line (`.venv` in ${OPENPYPE_ROOT} must be activated to use configured Python!)
-- `${OPENPYPE_ROOT}/python start.py runtests`

By default, this command will run all tests in ${OPENPYPE_ROOT}/tests.

Specific location could be provided to this command as an argument, either as absolute path, or relative path to ${OPENPYPE_ROOT}.
(eg. `${OPENPYPE_ROOT}/start.py runtests ../tests/integration`) will trigger only tests in `integration` folder.
(eg. `${OPENPYPE_ROOT}/python start.py runtests ../tests/integration`) will trigger only tests in `integration` folder.

See `${OPENPYPE_ROOT}/cli.py:runtests` for other arguments.
101 changes: 101 additions & 0 deletions tests/integration/hosts/aftereffects/test_publish_in_aftereffects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import pytest
import os
import shutil

from tests.lib.testing_classes import PublishTest


class TestPublishInAfterEffects(PublishTest):
"""Basic test case for publishing in AfterEffects

Uses generic TestCase to prepare fixtures for test data, testing DBs,
env vars.

Opens AfterEffects, run publish on prepared workile.

Test zip file sets 3 required env vars:
- HEADLESS_PUBLISH - this triggers publish immediately app is open
- IS_TEST - this differentiate between regular webpublish
- PYBLISH_TARGETS

Then checks content of DB (if subset, version, representations were
created.
Checks tmp folder if all expected files were published.

"""
PERSIST = True

TEST_FILES = [
("1c8261CmHwyMgS-g7S4xL5epAp0jCBmhf",
"test_aftereffects_publish.zip",
"")
]

APP = "aftereffects"
APP_VARIANT = "2022"

APP_NAME = "{}/{}".format(APP, APP_VARIANT)

TIMEOUT = 120 # publish timeout

@pytest.fixture(scope="module")
def last_workfile_path(self, download_test_data):
"""Get last_workfile_path from source data.

Maya expects workfile in proper folder, so copy is done first.
"""
src_path = os.path.join(download_test_data,
"input",
"workfile",
"test_project_test_asset_TestTask_v001.aep")
dest_folder = os.path.join(download_test_data,
self.PROJECT,
self.ASSET,
"work",
self.TASK)
os.makedirs(dest_folder)
dest_path = os.path.join(dest_folder,
"test_project_test_asset_TestTask_v001.aep")
shutil.copy(src_path, dest_path)

yield dest_path

@pytest.fixture(scope="module")
def startup_scripts(self, monkeypatch_session, download_test_data):
"""Points AfterEffects to userSetup file from input data"""
pass

def test_db_asserts(self, dbcon, publish_finished):
"""Host and input data dependent expected results in DB."""
print("test_db_asserts")
assert 3 == dbcon.count_documents({"type": "version"}), \
"Not expected no of versions"

assert 0 == dbcon.count_documents({"type": "version",
"name": {"$ne": 1}}), \
"Only versions with 1 expected"

assert 1 == dbcon.count_documents({"type": "subset",
"name": "imageMainBackgroundcopy"
}), \
"modelMain subset must be present"

assert 1 == dbcon.count_documents({"type": "subset",
"name": "workfileTesttask"}), \
"workfileTesttask subset must be present"

assert 1 == dbcon.count_documents({"type": "subset",
"name": "reviewTesttask"}), \
"reviewTesttask subset must be present"

assert 6 == dbcon.count_documents({"type": "representation"}), \
"Not expected no of representations"

assert 1 == dbcon.count_documents({"type": "representation",
"context.subset": "imageMainBackgroundcopy", # noqa E501
"context.ext": "png"}), \
"Not expected no of representations with ext 'png'"


if __name__ == "__main__":
test_case = TestPublishInAfterEffects()
Loading