Skip to content

Commit 8f3557a

Browse files
[SG-9141] pip installed core can't use the ToolkitManager to bootstrap (#1033)
* move the bootstrap hook to the bootstrap folder in order to be able to bootstrap the engine when sgtk is installed using pip * add test file to cover engine bootstrap when sgtk has been installed using pip * remove blank line * do not return unused value * move and rename hook to his previous name to keep consistancy with existing workflows * update setup.py file to make sure the new hook folder will be included with the packages * improve documentation * run black manually to make hound happy as tests are not included in pre-commit actions * improve code comments * Apply suggestions from code review --------- Co-authored-by: Martin Chesnay <[email protected]>
1 parent 5bfcaa7 commit 8f3557a

File tree

5 files changed

+119
-7
lines changed

5 files changed

+119
-7
lines changed

docs/initializing.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,11 @@ to determine if a bundle is available in Flow Production Tracking or not.
430430
431431
Once the bundles have been uploaded, you can implement the ``core/bootstrap.py`` hook.
432432

433+
.. warning::
434+
In order to be able to bootstrap Toolkit regardless of the installation method used, the bootstrap hook needs
435+
to be stored within the package itself. That's why this core hook is located in ``core/python/tank/bootstrap/hook``
436+
instead of the usual core hooks location.
437+
433438
.. literalinclude:: examples/bootstrap_hook.py
434439
:language: python
435440
:start-after: #documentationStart

python/tank/bootstrap/bundle_downloader.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,9 @@ def __init__(self, connection, pipeline_config_id, descriptor):
3434
# First put our base hook implementation into the array.
3535
base_class_path = os.path.normpath(
3636
os.path.join(
37-
os.path.dirname(__file__), # ./python/tank/bootstrap
38-
"..", # ./python/tank
39-
"..", # ./python
40-
"..", # ./
41-
"hooks", # ./hooks
42-
"bootstrap.py", # ./hooks/bootstrap.py
37+
os.path.dirname(__file__), # ...tk-core/python/tank/bootstrap
38+
"hooks",
39+
"bootstrap.py", # ...tk-core/python/bootstrap/hooks/bootstrap.py
4340
)
4441
)
4542
hook_inheritance_chain = [base_class_path]

hooks/bootstrap.py renamed to python/tank/bootstrap/hooks/bootstrap.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
It will be instantiated only after a configuration has been selected by the :class:`~sgtk.bootstrap.ToolkitManager`.
1515
Therefore, this hook will not be invoked to download a configuration. However, the Toolkit Core,
1616
applications, frameworks and engines can be downloaded through the hook.
17+
18+
.. warning::
19+
In order to be able to bootstrap Toolkit regardless of the installation method used, the bootstrap hook needs
20+
to be stored within the package itself. That's why this core hook is located in ``core/python/tank/bootstrap/hook``
21+
instead of the usual core hooks location.
1722
"""
1823

1924
from sgtk import get_hook_baseclass

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def get_version():
7777
# Additional data which must sit in packages folders
7878
package_data={
7979
# If any package contains data files, include them:
80-
"": ["resources/*", ".txt", "*.*"]
80+
"": ["resources/*", ".txt", "*.*", "hooks/*.py"]
8181
},
8282
# Everything can be found under the python folder, but installed without it
8383
package_dir={"": "python"},
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Copyright 2025 Autodesk, Inc. All rights reserved.
2+
#
3+
# Use of this software is subject to the terms of the Autodesk license agreement
4+
# provided at the time of installation or download, or which otherwise accompanies
5+
# this software in either electronic or hard copy form.
6+
#
7+
8+
import os
9+
import shutil
10+
import sys
11+
import tempfile
12+
import unittest
13+
14+
from sgtk_integration_test import SgtkIntegrationTest
15+
16+
17+
class BootstrapPipTests(SgtkIntegrationTest):
18+
"""
19+
Tests that it is possible to boostrap an engine when the package installation has been done using pip.
20+
21+
Note that because we can't really use pip here, we're simulating the installation process.
22+
"""
23+
24+
PYTHON_PACKAGE_LIST = ["sgtk", "tank", "tank_vendor"]
25+
26+
@classmethod
27+
def setUpClass(cls):
28+
super(BootstrapPipTests, cls).setUpClass()
29+
30+
# Mockup FPTR entities
31+
cls.project = cls.create_or_update_project(
32+
"Pip Install SGTK", {"tank_name": "pip_install_sgtk"}
33+
)
34+
cls.asset = cls.create_or_update_entity(
35+
"Asset", "TestAsset", {"project": cls.project}
36+
)
37+
38+
# Figure out our location
39+
current_dir = os.path.abspath(os.path.dirname(__file__))
40+
python_package_path = os.path.normpath(
41+
os.path.join(current_dir, "..", "..", "python")
42+
)
43+
44+
# Install sgtk in a temporary location pip-liked by manually copying the packages to the temporary location
45+
cls._sgtk_install_location = tempfile.mkdtemp(prefix="sgtk_install_", dir=None)
46+
for package in cls.PYTHON_PACKAGE_LIST:
47+
if os.path.isdir(os.path.join(python_package_path, package)):
48+
shutil.copytree(
49+
os.path.join(python_package_path, package),
50+
os.path.join(cls._sgtk_install_location, package),
51+
ignore=shutil.ignore_patterns("*.pyc", "__pycache__"),
52+
)
53+
sys.path.insert(1, cls._sgtk_install_location)
54+
55+
@classmethod
56+
def tearDownClass(cls):
57+
super(BootstrapPipTests, cls).tearDownClass()
58+
59+
# Make sure to remove our sgtk install folder
60+
if os.path.isdir(cls._sgtk_install_location):
61+
shutil.rmtree(cls._sgtk_install_location)
62+
63+
def __clean_sgtk_modules(self):
64+
"""Helper method to remove previously installed sgtk modules."""
65+
66+
modules_to_remove = []
67+
68+
for module_name in sys.modules:
69+
if module_name.startswith("sgtk") or module_name.startswith("tank"):
70+
modules_to_remove.append(module_name)
71+
72+
for module_name in modules_to_remove:
73+
del sys.modules[module_name]
74+
75+
def test_boostrap_engine(self):
76+
"""Bootstrap the engine when the sgtk module has been installed using pip."""
77+
78+
# Make sure to import the right sgtk module by cleaning all the previously imported modules and reimporting
79+
# the right one
80+
self.__clean_sgtk_modules()
81+
import sgtk
82+
83+
self.assertEqual(
84+
os.path.dirname(sgtk.__file__),
85+
os.path.join(self._sgtk_install_location, "tank"),
86+
)
87+
self.assertEqual(
88+
os.path.dirname(sgtk.bootstrap.__file__),
89+
os.path.join(self._sgtk_install_location, "tank", "bootstrap"),
90+
)
91+
92+
# Bootstrap the engine
93+
manager = sgtk.bootstrap.ToolkitManager(self.user)
94+
manager.plugin_id = "basic.test"
95+
manager.base_configuration = "sgtk:descriptor:path?path={0}".format(
96+
os.path.normpath(
97+
os.path.join(os.path.dirname(__file__), "data", "site_config")
98+
)
99+
)
100+
engine = manager.bootstrap_engine("tk-shell", self.asset)
101+
self.assertEqual(engine.name, "tk-shell")
102+
103+
104+
if __name__ == "__main__":
105+
unittest.main(failfast=True, verbosity=2)

0 commit comments

Comments
 (0)