def _jump_to_fs(self): """ Jump from context to FS """ # launch one window for each location on disk paths = self._engine.context.filesystem_locations for disk_location in paths: # get the setting system = sys.platform # run the app if is_linux(): args = ["xdg-open", disk_location] elif is_macos(): args = ['open "%s"', disk_location] elif is_windows(): args = [ "cmd.exe", "/C", "start", '"Folder %s"' % disk_location ] else: raise Exception("Platform '%s' is not supported." % system) exit_code = subprocess.check_output(args, shell=False) if exit_code != 0: self._engine.logger.error("Failed to launch '%s'!", args)
def test_setup_centralized_project(self, mocked=None): """ Test setting up a Project. """ def mocked_resolve_core_path(core_path): return { "linux2": core_path, "darwin": core_path, "win32": core_path } mocked.side_effect = mocked_resolve_core_path # create new project new_project = { "type": "Project", "id": 1234, "name": "new_project_1234" } self.add_to_sg_mock_db(new_project) # location where the config will be installed new_config_root = os.path.join(self.tank_temp, "new_project_1234_config") # location where the data will be installed os.makedirs(os.path.join(self.tank_temp, "new_project_1234")) command = self.tk.get_command("setup_project") command.set_logger(logging.getLogger("/dev/null")) # Test we can setup a new project and it does not fail. command.execute({ "project_id": new_project["id"], "project_folder_name": "new_project_1234", "config_uri": self.project_config, "config_path_mac": new_config_root if is_macos() else None, "config_path_win": new_config_root if is_windows() else None, "config_path_linux": new_config_root if is_linux() else None, }) # Check we get back our custom primary root name new_pc = tank.pipelineconfig_factory.from_path(new_config_root) self.assertEqual(list(new_pc.get_data_roots().keys()), ["setup_project_root"]) # make sure the fake core didn't get copied across, e.g. that # we didn't localize the setup self.assertFalse( os.path.exists( os.path.join(new_config_root, "install", "core", "bad_path"))) # make sure we have the core location files for this unlocalized setup self.assertTrue( os.path.exists( os.path.join(new_config_root, "install", "core", "core_Darwin.cfg")))
def _test_config_locations(self, pc, autogen_files_root, config_files_root): """ Test locations that are reported by the configuration. """ # Pipeline configuration location tests. self.assertEqual(pc.get_path(), autogen_files_root) self.assertEqual( pc.get_yaml_cache_location(), os.path.join(autogen_files_root, "yaml_cache.pickle"), ) self.assertEqual( pc._get_pipeline_config_file_location(), os.path.join( autogen_files_root, "config", "core", "pipeline_configuration.yml" ), ) self.assertEqual( pc._storage_roots.roots_file, os.path.join(autogen_files_root, "config", "core", "roots.yml"), ) self.assertEqual( pc.get_all_os_paths(), tank.util.ShotgunPath( autogen_files_root if is_windows() else None, autogen_files_root if is_linux() else None, autogen_files_root if is_macos() else None, ), ) # Config folder location test. self.assertEqual(pc.get_config_location(), os.path.join(config_files_root)) self.assertEqual( pc.get_core_hooks_location(), os.path.join(config_files_root, "core", "hooks"), ) self.assertEqual( pc.get_schema_config_location(), os.path.join(config_files_root, "core", "schema"), ) self.assertEqual( pc.get_hooks_location(), os.path.join(config_files_root, "hooks") ) self.assertEqual( pc.get_shotgun_menu_cache_location(), os.path.join(autogen_files_root, "cache"), ) self.assertEqual( pc.get_environment_path("test"), os.path.join(config_files_root, "env", "test.yml"), ) self.assertEqual( pc._get_templates_config_location(), os.path.join(config_files_root, "core", "templates.yml"), )
def test_file(self, subprocess_mock): """ Tests opening a file """ fs.open_file_browser(self.test_file) args, kwargs = subprocess_mock.call_args if is_linux(): self.assertEqual(args[0], ["xdg-open", os.path.dirname(self.test_file)]) elif is_macos(): self.assertEqual(args[0], ["open", "-R", self.test_file]) elif is_windows(): self.assertEqual(args[0], ["explorer", "/select,", self.test_file])
def test_folder(self, subprocess_mock): """ Tests opening a folder """ fs.open_file_browser(self.test_folder) args, kwargs = subprocess_mock.call_args if is_linux(): self.assertEqual(args[0], ["xdg-open", self.test_folder]) elif is_macos(): self.assertEqual(args[0], ["open", self.test_folder]) elif is_windows(): self.assertEqual(args[0], ["cmd.exe", "/C", "start", self.test_folder])
def test_get_shotgun_storage_key(self): """ Tests get_shotgun_storage_key """ gssk = ShotgunPath.get_shotgun_storage_key self.assertEqual(gssk("win32"), "windows_path") self.assertEqual(gssk("linux2"), "linux_path") self.assertEqual(gssk("linux"), "linux_path") self.assertEqual(gssk("linux3"), "linux_path") self.assertEqual(gssk("darwin"), "mac_path") if is_windows(): self.assertEqual(gssk(), "windows_path") if is_macos(): self.assertEqual(gssk(), "mac_path") if is_linux(): self.assertEqual(gssk(), "linux_path")
def test_paths(self): """Test paths match those in roots for current os.""" root_file = open(self.root_file_path, "w") root_file.write(yaml.dump(self.roots)) root_file.close() pc = tank.pipelineconfig_factory.from_path(self.project_root) result = pc.get_data_roots() # Determine platform if is_macos(): platform = "mac_path" elif is_linux(): platform = "linux_path" elif is_windows(): platform = "windows_path" project_name = os.path.basename(self.project_root) for root_name, root_path in result.items(): expected_path = os.path.join(self.roots[root_name][platform], project_name) self.assertEqual(expected_path, root_path)
def test_env_var(self): """ Tests that if the current os is not defined in the shotgun local storage defs, we can override it by setting an env var. """ # add a storage and omit current os self.storage = { "type": "LocalStorage", "id": 2, "code": "home", "mac_path": "/local", "windows_path": "x:\\", "linux_path": "/local", } current_path_field = { "win32": "windows_path", "linux2": "linux_path", "darwin": "mac_path", }[sgsix.platform] self.storage[current_path_field] = None self.add_to_sg_mock_db([self.storage]) # add a publish record sg_dict = { "id": 123, "type": "PublishedFile", "code": "foo", "path": { "content_type": "image/png", "id": 25826, "link_type": "local", "local_path": None, "local_path_linux": "/local/path/to/file.ext", "local_path_mac": "/local/path/to/file.ext", "local_path_windows": r"X:\path\to\file.ext", "local_storage": { "id": 2, "name": "home", "type": "LocalStorage" }, "name": "foo.png", "type": "Attachment", "url": "file:///local/path/to/file.ext", }, } if is_windows(): os.environ["SHOTGUN_PATH_WINDOWS_HOME"] = "Y:\\" local_path = r"Y:\path\to\file.ext" sg_dict["path"]["local_path_windows"] = None elif is_macos(): os.environ["SHOTGUN_PATH_MAC_HOME"] = "/local_override" local_path = "/local_override/path/to/file.ext" sg_dict["path"]["local_path_mac"] = None elif is_linux(): os.environ["SHOTGUN_PATH_LINUX_HOME"] = "/local_override" local_path = "/local_override/path/to/file.ext" sg_dict["path"]["local_path_linux"] = None evaluated_path = sgtk.util.resolve_publish_path(self.tk, sg_dict) self.assertEqual(evaluated_path, local_path)
def _setup_project(self, is_localized): """ Setups a Toolkit centralized pipeline configuration with a localized or not core. """ # Create the project's destination folder. locality = "localized" if is_localized else "studio" project_folder_name = "config_with_%s_core" % locality config_root = os.path.join( self.tank_temp, project_folder_name, "pipeline_configuration" ) os.makedirs(os.path.join(self.tank_temp, project_folder_name)) # Mock a core that will setup the project. core_root = os.path.join(self.tank_temp, "%s_core" % locality) core_install_folder = os.path.join(core_root, "install", "core") os.makedirs(core_install_folder) # Copy the core info.yml, since the config expects a certain version of # core and the setup project needs to be able to compare versions. shutil.copy( os.path.join(os.path.dirname(__file__), "..", "..", "info.yml"), os.path.join(core_install_folder, "info.yml"), ) # Mock a localized core if required. if is_localized: self.create_file( os.path.join(core_root, "config", "core", "interpreter_Darwin.cfg") ) self.create_file( os.path.join(core_root, "config", "core", "interpreter_Windows.cfg") ) self.create_file( os.path.join(core_root, "config", "core", "interpreter_Linux.cfg") ) self.create_file(os.path.join(core_root, "config", "core", "shotgun.yml")) self.create_file(os.path.join(core_root, "config", "core", "roots.yml")) self.create_file(os.path.join(core_install_folder, "_core_upgrader.py")) self.assertEqual(tank.pipelineconfig_utils.is_localized(core_root), True) # We have to patch these methods because the core doesn't actually exist on disk for the tests. with patch( "sgtk.pipelineconfig_utils.get_path_to_current_core", return_value=core_root ): with patch( "sgtk.pipelineconfig_utils.resolve_all_os_paths_to_core", return_value={ "linux2": core_root if is_linux() else None, "win32": core_root if is_windows() else None, "darwin": core_root if is_macos() else None, }, ): command = get_command("setup_project", self.tk) command.set_logger(logging.getLogger("test")) command.execute( dict( config_uri=os.path.join(self.fixtures_root, "config"), project_id=self._project["id"], project_folder_name=project_folder_name, config_path_mac=config_root if is_macos() else None, config_path_win=config_root if is_windows() else None, config_path_linux=config_root if is_linux() else None, check_storage_path_exists=False, ) ) tk = tank.sgtk_from_path(config_root) pc = tk.pipeline_configuration return pc, config_root, core_root
def init_engine(self): """ Initializes the Rumba engine. """ self.logger.debug("%s: Initializing...", self) # check that we are running a supported OS if not any([is_windows(), is_linux(), is_macos()]): raise tank.TankError( "The current platform is not supported!" " Supported platforms " "are Mac, Linux 64 and Windows 64." ) # check that we are running an ok version of Rumba rumba_build_version = rversion.rumba_version rumba_build_version = rumba_build_version.replace("-beta", "") rumba_ver = float(".".join(rumba_build_version.split(".")[:2])) if rumba_ver < MIN_COMPATIBILITY_VERSION: msg = ( "Shotgun integration is not compatible with %s versions older than %s" % ( APPLICATION_NAME, MIN_COMPATIBILITY_VERSION, ) ) show_error(msg) raise tank.TankError(msg) if rumba_ver > MIN_COMPATIBILITY_VERSION + 1: # show a warning that this version of Rumba isn't yet fully tested # with Shotgun: msg = ( "The Shotgun Pipeline Toolkit has not yet been fully " "tested with %s %s. " "You can continue to use Toolkit but you may experience " "bugs or instability." "\n\n" % (APPLICATION_NAME, rumba_ver) ) # determine if we should show the compatibility warning dialog: show_warning_dlg = self.has_ui and SHOW_COMP_DLG not in os.environ if show_warning_dlg: # make sure we only show it once per session os.environ[SHOW_COMP_DLG] = "1" # split off the major version number - accommodate complex # version strings and decimals: major_version_number_str = rumba_build_version.split(".")[0] if major_version_number_str and major_version_number_str.isdigit(): # check against the compatibility_dialog_min_version # setting min_ver = self.get_setting("compatibility_dialog_min_version") if int(major_version_number_str) < min_ver: show_warning_dlg = False if show_warning_dlg: # Note, title is padded to try to ensure dialog isn't insanely # narrow! show_info(msg) # always log the warning to the script editor: self.logger.warning(msg) # In the case of Windows, we have the possibility of locking up if # we allow the PySide shim to import QtWebEngineWidgets. # We can stop that happening here by setting the following # environment variable. # Note that prior PyQt5 v5.12 this module existed, after that it has # been separated and would not cause any issues. Since it is no # harm if the module is not there, we leave it just in case older # versions of Rumba were using previous versions of PyQt # https://www.riverbankcomputing.com/software/pyqtwebengine/intro if is_windows(): self.logger.debug( "This application on Windows can deadlock if QtWebEngineWidgets " "is imported. Setting " "SHOTGUN_SKIP_QTWEBENGINEWIDGETS_IMPORT=1..." ) os.environ["SHOTGUN_SKIP_QTWEBENGINEWIDGETS_IMPORT"] = "1" # check that we can load the GUI libraries self.logger.info("before init pyside") self._init_pyside() self.logger.info("after init pyside") # default menu name is Shotgun but this can be overriden # in the configuration to be Sgtk in case of conflicts self._menu_name = "Shotgun" if self.get_setting("use_sgtk_as_menu_name", False): self._menu_name = "Sgtk"
def _apply_fields(self, fields, ignore_types=None, platform=None, skip_defaults=False): """ Creates path using fields. :param fields: Mapping of keys to fields. Keys must match those in template definition. :param ignore_types: Keys for whom the defined type is ignored as list of strings. This allows setting a Key whose type is int with a string value. :param platform: Optional operating system platform. If you leave it at the default value of None, paths will be created to match the current operating system. If you pass in a sys.platform-style string (e.g. 'win32', 'linux2' or 'darwin'), paths will be generated to match that platform. :param skip_defaults: Optional. If set to True, if a key has a default value and no corresponding value in the fields argument, its default value will be used. If set to False, keys that are not specified in the fields argument are skipped whether they have a default value or not. Defaults to False :returns: Full path, matching the template with the given fields inserted. """ relative_path = super(TemplatePath, self)._apply_fields(fields, ignore_types, platform, skip_defaults=skip_defaults) if platform is None: # return the current OS platform's path return (os.path.join(self.root_path, relative_path) if relative_path else self.root_path) else: platform = sgsix.normalize_platform(platform) # caller has requested a path for another OS if self._per_platform_roots is None: # it's possible that the additional os paths are not set for a template # object (mainly because of backwards compatibility reasons) and in this case # we cannot compute the path. raise TankError( "Template %s cannot resolve path for operating system '%s' - " "it was instantiated in a mode which only supports the resolving " "of current operating system paths." % (self, platform)) platform_root_path = self._per_platform_roots.get(platform) if platform_root_path is None: # either the platform is undefined or unknown raise TankError( "Cannot resolve path for operating system '%s'! Please ensure " "that you have a valid storage set up for this platform." % platform) elif is_windows(platform): # use backslashes for windows if relative_path: return "%s\\%s" % ( platform_root_path, relative_path.replace(os.sep, "\\"), ) else: # not path generated - just return the root path return platform_root_path elif is_macos(platform) or is_linux(platform): # unix-like plaforms - use slashes if relative_path: return "%s/%s" % ( platform_root_path, relative_path.replace(os.sep, "/"), ) else: # not path generated - just return the root path return platform_root_path else: raise TankError( "Cannot evaluate path. Unsupported platform '%s'." % platform)
def test_setup_project_with_external_core( self, resolve_all_os_paths_to_core_mock, get_install_location_mock): """ Test setting up a Project config that has a core/core_api.yml file included. """ def mocked_resolve_core_path(core_path): return { "linux2": core_path, "darwin": core_path, "win32": core_path } resolve_all_os_paths_to_core_mock.side_effect = mocked_resolve_core_path def mocked_get_install_location(): return self._fake_core_install get_install_location_mock.side_effect = mocked_get_install_location # add a core_api.yml to our config that we are installing from, telling the # setup project command to use this when running the localize portion of the setup. core_api_path = os.path.join(self.project_config, "core", "core_api.yml") with open(core_api_path, "wt") as fp: fp.write("location:\n") fp.write(" type: dev\n") fp.write(" path: %s\n" % self.tank_source_path) try: # create new project new_project = { "type": "Project", "id": 1235, "name": "new_project_1235" } self.add_to_sg_mock_db(new_project) new_config_root = os.path.join(self.tank_temp, "new_project_1235_config") # location where the data will be installed os.makedirs(os.path.join(self.tank_temp, "new_project_1235")) command = self.tk.get_command("setup_project") command.set_logger(logging.getLogger("/dev/null")) # Test we can setup a new project and it does not fail. command.execute({ "project_id": new_project["id"], "project_folder_name": "new_project_1235", "config_uri": self.project_config, "config_path_mac": new_config_root if is_macos() else None, "config_path_win": new_config_root if is_windows() else None, "config_path_linux": new_config_root if is_linux() else None, }) # Check we get back our custom primary root name new_pc = tank.pipelineconfig_factory.from_path(new_config_root) self.assertEqual(list(new_pc.get_data_roots().keys()), ["setup_project_root"]) # the 'fake' core that we mocked earlier has a 'bad_path' folder self.assertFalse( os.path.exists( os.path.join(new_config_root, "install", "core", "bad_path"))) # instead we expect a full installl self.assertTrue( os.path.exists( os.path.join( new_config_root, "install", "core", "python", "tank", "errors.py", ))) finally: os.remove(core_api_path)
def init_engine(self): """ Initializes the Blender engine. """ self.logger.debug("%s: Initializing...", self) # check that we are running a supported OS if not any([is_windows(), is_linux(), is_macos()]): raise tank.TankError("The current platform is not supported!" " Supported platforms " "are Mac, Linux 64 and Windows 64.") # check that we are running an ok version of Blender build_version = bpy.app.version app_ver = float(".".join(map(str, build_version[:2]))) if app_ver < MIN_COMPATIBILITY_VERSION: msg = ( "Shotgun integration is not compatible with %s versions older than %s" % ( APPLICATION_NAME, MIN_COMPATIBILITY_VERSION, )) self.show_error(msg) raise tank.TankError(msg) if app_ver > MIN_COMPATIBILITY_VERSION: # show a warning that this version of Blender isn't yet fully tested # with Shotgun: msg = ("The Shotgun Pipeline Toolkit has not yet been fully " "tested with %s %s. " "You can continue to use Toolkit but you may experience " "bugs or instability." "\n\n" % (APPLICATION_NAME, app_ver)) # determine if we should show the compatibility warning dialog: show_warning_dlg = self.has_ui and SHOW_COMP_DLG not in os.environ if show_warning_dlg: # make sure we only show it once per session os.environ[SHOW_COMP_DLG] = "1" min_ver = self.get_setting("compatibility_dialog_min_version") if build_version[0] < min_ver: show_warning_dlg = False if show_warning_dlg: # Note, title is padded to try to ensure dialog isn't insanely # narrow! self.show_info(msg) # always log the warning to the script editor: self.logger.warning(msg) # In the case of Windows, we have the possibility of locking up if # we allow the PySide shim to import QtWebEngineWidgets. # We can stop that happening here by setting the following # environment variable. # Note that prior PyQt5 v5.12 this module existed, after that it has # been separated and would not cause any issues. # https://www.riverbankcomputing.com/software/pyqtwebengine/intro if is_windows(): self.logger.debug( "This application on Windows can deadlock if QtWebEngineWidgets " "is imported. Setting " "SHOTGUN_SKIP_QTWEBENGINEWIDGETS_IMPORT=1...") os.environ["SHOTGUN_SKIP_QTWEBENGINEWIDGETS_IMPORT"] = "1" # default menu name is Shotgun but this can be overriden # in the configuration to be Sgtk in case of conflicts self._menu_name = "Shotgun" if self.get_setting("use_sgtk_as_menu_name", False): self._menu_name = "Sgtk" if self.get_setting("automatic_context_switch", True): # need to watch some scene events in case the engine needs rebuilding: setup_app_handlers() self.logger.debug("Registered open and save callbacks.")
def test_01_setup_legacy_bootstrap_core(self): """ Sets up a site-wide configuration like Shotgun Desktop 1.3.6 used to do so we can make sure it doesn't get broken by more recent versions of tk-core. """ self.remove_files(self.legacy_bootstrap_core, self.site_config_location) if is_macos(): path_param = "config_path_mac" elif is_windows(): path_param = "config_path_win" elif is_linux(): path_param = "config_path_linux" cw = sgtk.bootstrap.configuration_writer.ConfigurationWriter( sgtk.util.ShotgunPath.from_current_os_path( self.legacy_bootstrap_core), self.sg, ) # Activate the core. cw.ensure_project_scaffold() install_core_folder = os.path.join(self.legacy_bootstrap_core, "install", "core") os.makedirs(install_core_folder) cw.write_shotgun_file(Mock(get_path=lambda: "does_not_exist")) cw.write_install_location_file() sgtk.util.filesystem.copy_folder( self.tk_core_repo_root, install_core_folder, skip_list=[".git", "docs", "tests"], ) cw.create_tank_command() # Setup the site config in the legacy auto_path mode that the Desktop used. params = { "auto_path": True, "config_uri": os.path.join(os.path.dirname(__file__), "data", "site_config"), "project_folder_name": "site", "project_id": None, path_param: self.site_config_location, } setup_project = sgtk.get_command("setup_project") setup_project.set_logger(logger) sgtk.set_authenticated_user(self.user) with patch( "tank.pipelineconfig_utils.resolve_all_os_paths_to_core", return_value=sgtk.util.ShotgunPath.from_current_os_path( self.legacy_bootstrap_core).as_system_dict(), ): setup_project.execute(params)
def test_construction(self): """ Tests get_cache_root """ self.assertEqual(ShotgunPath.SHOTGUN_PATH_FIELDS, ["windows_path", "linux_path", "mac_path"]) sg = ShotgunPath.from_shotgun_dict({ "windows_path": "C:\\temp", "mac_path": "/tmp", "linux_path": "/tmp2", "foo": "bar", }) self.assertEqual(sg.windows, "C:\\temp") self.assertEqual(sg.macosx, "/tmp") self.assertEqual(sg.linux, "/tmp2") sg = ShotgunPath.from_shotgun_dict({ "windows_path": "C:\\temp", "mac_path": None, "foo": "bar" }) self.assertEqual(sg.windows, "C:\\temp") self.assertEqual(sg.macosx, None) self.assertEqual(sg.linux, None) sys_paths = ShotgunPath.from_system_dict({ "win32": "C:\\temp", "darwin": "/tmp", "linux2": "/tmp2", "foo": "bar" }) self.assertEqual(sys_paths.windows, "C:\\temp") self.assertEqual(sys_paths.macosx, "/tmp") self.assertEqual(sys_paths.linux, "/tmp2") sys_paths = ShotgunPath.from_system_dict({ "win32": "C:\\temp", "darwin": None, "foo": "bar" }) self.assertEqual(sys_paths.windows, "C:\\temp") self.assertEqual(sys_paths.macosx, None) self.assertEqual(sys_paths.linux, None) if is_windows(): curr = ShotgunPath.from_current_os_path("\\\\server\\mount\\path") self.assertEqual(curr.windows, "\\\\server\\mount\\path") self.assertEqual(curr.macosx, None) self.assertEqual(curr.linux, None) self.assertEqual(curr.current_os, curr.windows) if is_linux(): curr = ShotgunPath.from_current_os_path("/tmp/foo/bar") self.assertEqual(curr.windows, None) self.assertEqual(curr.macosx, None) self.assertEqual(curr.linux, "/tmp/foo/bar") self.assertEqual(curr.current_os, curr.linux) if is_macos(): curr = ShotgunPath.from_current_os_path("/tmp/foo/bar") self.assertEqual(curr.windows, None) self.assertEqual(curr.macosx, "/tmp/foo/bar") self.assertEqual(curr.linux, None) self.assertEqual(curr.current_os, curr.macosx) std_constructor = ShotgunPath("C:\\temp", "/tmp", "/tmp2") self.assertEqual(std_constructor.windows, "C:\\temp") self.assertEqual(std_constructor.macosx, "/tmp2") self.assertEqual(std_constructor.linux, "/tmp")