def _process_configuration(sg_connection, config_uri_str): """ Resolve and download the given Toolkit configuration. :param sg_connection: Shotgun connection. :param config_uri_str: Toolkit config descriptor as a string. :returns: Resolved config descriptor object. :raises: ValueError for "baked" descriptors. """ logger.info("Analyzing configuration") config_uri_dict = descriptor_uri_to_dict(config_uri_str) if config_uri_dict["type"] == bootstrap_constants.BAKED_DESCRIPTOR_TYPE: raise ValueError("The given config is already baked") # If the config descriptor does not contain a version number, get the # latest. using_latest_config = is_descriptor_version_missing(config_uri_dict) if using_latest_config: logger.info( "Your configuration definition does not contain a version number. " "Retrieving the latest version..." ) cfg_descriptor = create_descriptor( sg_connection, Descriptor.CONFIG, config_uri_dict, resolve_latest=using_latest_config, ) cfg_descriptor.ensure_local() logger.info("Resolved config %r" % cfg_descriptor) return cfg_descriptor
def _bake_configuration(sg_connection, manifest_data): """ Bake the given configuration by ensuring it is locally cached and by modifying the manifest_data. :param sg_connection: Shotgun connection :param manifest_data: Manifest data as a dictionary :returns: The baked descriptor dictionary issued from configuration descriptor. """ logger.info( "Baking your configuration definition into an immutable state. " "This means that the plugin will be frozen and no automatic updates " "will be performed at startup." ) base_config_def = manifest_data["base_configuration"] if isinstance(base_config_def, str): # convert to dict so we can introspect base_config_uri_dict = descriptor_uri_to_dict(base_config_def) else: base_config_uri_dict = base_config_def using_latest_config = is_descriptor_version_missing(base_config_uri_dict) if using_latest_config: logger.info( "Your configuration definition does not contain a version number. " "Retrieving the latest version of the configuration for baking." ) cfg_descriptor = create_descriptor( sg_connection, Descriptor.CONFIG, base_config_uri_dict, resolve_latest=using_latest_config ) cfg_descriptor.ensure_local() local_path = cfg_descriptor.get_path() if not local_path: raise ValueError("Unable to get a local copy of %s" % cfg_descriptor) baked_descriptor = { "type": bootstrap_constants.BAKED_DESCRIPTOR_TYPE, "path": local_path, "name": cfg_descriptor.system_name, "version": cfg_descriptor.version } manifest_data["base_configuration"] = baked_descriptor return baked_descriptor
def _bake_configuration(sg_connection, manifest_data): """ Bake the given configuration by ensuring it is locally cached and by modifying the manifest_data. :param sg_connection: Shotgun connection :param manifest_data: Manifest data as a dictionary :returns: The baked descriptor dictionary issued from configuration descriptor. """ logger.info( "Baking your configuration definition into an immutable state. " "This means that the plugin will be frozen and no automatic updates " "will be performed at startup.") base_config_def = manifest_data["base_configuration"] if isinstance(base_config_def, str): # convert to dict so we can introspect base_config_uri_dict = descriptor_uri_to_dict(base_config_def) else: base_config_uri_dict = base_config_def using_latest_config = is_descriptor_version_missing(base_config_uri_dict) if using_latest_config: logger.info( "Your configuration definition does not contain a version number. " "Retrieving the latest version of the configuration for baking.") cfg_descriptor = create_descriptor( sg_connection, Descriptor.CONFIG, base_config_uri_dict, resolve_latest=using_latest_config, ) cfg_descriptor.ensure_local() local_path = cfg_descriptor.get_path() if not local_path: raise ValueError("Unable to get a local copy of %s" % cfg_descriptor) baked_descriptor = { "type": bootstrap_constants.BAKED_DESCRIPTOR_TYPE, "path": local_path, "name": cfg_descriptor.system_name, "version": cfg_descriptor.version, } manifest_data["base_configuration"] = baked_descriptor return baked_descriptor
def main(): """ Main entry point for script. Handles argument parsing and validation and then calls the script payload. """ usage = "%prog [options] config_descriptor target_path" desc = "Bake a self contained Toolkit config from a descriptor" epilog = """ Details and Examples -------------------- In its simplest form, just provide a local path and target folder for the build. > python bake_config.py ~/dev/tk-config-myconfig /tmp/baked_configurations Or you can specify a version with a Toolkit config descriptor uri. > python bake_config.py "sgtk:descriptor:dev?version=v1.0.9&path=../tk-config-myconfig" /tmp/baked_configurations Any type of Toolkit config descriptor uri can be used, if a version is not specified, the latest for the descriptor is resolved. > python bake_config.py "sgtk:descriptor:app_store?name=tk-config-basic" /tmp/baked_configurations By default, all bundle types are cached. If you want to omit certain types, simply provide a comma seperated list of bundle types to skip, e.g. --skip-bundle-types=app_store,shotgun,github_release. {automated_setup_documentation} For information about the various descriptors that can be used, see http://developer.shotgridsoftware.com/tk-core/descriptor """.format( automated_setup_documentation=automated_setup_documentation ).format( script_name="bake_config.py" ) parser = OptionParserLineBreakingEpilog( usage=usage, description=desc, epilog=epilog ) parser.add_option( "-d", "--debug", default=False, action="store_true", help="Enable debug logging" ) parser.add_option( "-z", "--zip", default=False, action="store_true", help="Zip archive the config" ) parser.add_option( "--skip-bundle-types", # You can't have an empty default optional value, so we'll pick something # and treat it accordingly. default="none", help="Comma separated list of bundle types to skip. Possible values are 'app_store', " "'git', 'git_branch', 'github_release', 'shotgun'. Empty by default.", ) add_authentication_options(parser) # parse cmd line (options, remaining_args) = parser.parse_args() logger.info("Welcome to the Toolkit config baker.") logger.info("") if options.debug: LogManager().global_debug = True if len(remaining_args) != 2: parser.print_help() return 2 # Get config descriptor config_descriptor = remaining_args[0] # Try to parse it, check if it is a local path if it fails try: descriptor_uri_to_dict(config_descriptor) except TankDescriptorError: # Check if it is a local path path = os.path.abspath( os.path.expanduser(os.path.expandvars(config_descriptor)) ) if os.path.isdir(path): logger.info("Using a dev descriptor for local path %s" % path) # Forge a dev descriptor, using "latest" for the version. # TODO: try to retrieve a valid version from the folder, e.g. with a # git tag from the folder. config_descriptor = "sgtk:descriptor:dev?name=%s&path=%s&version=latest" % ( os.path.basename(path), path, ) else: logger.error( "%s is not a valid descriptor nor a local path." % config_descriptor ) raise # Get output path target_path = remaining_args[1] target_path = os.path.expanduser(os.path.expandvars(target_path)) sg_user = authenticate(options) sg_connection = sg_user.create_sg_connection() # make sure we are properly connected try: sg_connection.find_one("HumanUser", []) except Exception as e: logger.error("Could not communicate with ShotGrid: %s" % e) return 3 # Strip any extra whitespaces and make sure every bundle type exists. skip_bundle_types = options.skip_bundle_types.split(",") skip_bundle_types = [bundle_type.strip() for bundle_type in skip_bundle_types] for bundle_type in skip_bundle_types: if bundle_type not in [ "app_store", "git", "git_branch", "github_release", "shotgun", "none", ]: logger.error("Unknown bundle type: %s" % bundle_type) return 4 # we are all set. bake_config( sg_connection, config_descriptor, target_path, options.zip, skip_bundle_types, ) # all good! return 0
def _process_configuration(sg_connection, source_path, target_path, bundle_cache_root, manifest_data, buildable): """ Given data in the plugin manifest, download resolve and cache the configuration. :param sg_connection: Shotgun connection :param source_path: Root path of plugin source. :param target_path: Build target path :param bundle_cache_root: Bundle cache root :param manifest_data: Manifest data as a dictionary :param buildable: True if the generated build should be buildable :return: (Resolved config descriptor object, config descriptor uri to use at runtime) """ logger.info("Analyzing configuration") # get config def from info yml and generate both # dict and string uris. base_config_def = manifest_data["base_configuration"] if isinstance(base_config_def, str): # convert to dict so we can introspect base_config_uri_dict = descriptor_uri_to_dict(base_config_def) base_config_uri_str = base_config_def else: base_config_uri_dict = base_config_def base_config_uri_str = descriptor_dict_to_uri(base_config_def) # Special case - check for the 'baked' descriptor type # and process it. A baked descriptor is a special concept # that only exists in the build script. The baked descriptor # takes a single path parameter which can be a local or absolute # path. The path is copied across by the build script into a # manual descriptor, with a version number based on the current date. # This ensures that the manual descriptor will be correctly # re-cached at bootstrap time. if base_config_uri_dict[ "type"] == bootstrap_constants.BAKED_DESCRIPTOR_TYPE: logger.info("Baked descriptor detected.") baked_path = os.path.expanduser( os.path.expandvars(base_config_uri_dict["path"])) # if it's a relative path, expand it if not os.path.isabs(baked_path): full_baked_path = os.path.abspath( os.path.join(source_path, baked_path)) # if it's a relative path, we have already copied it to the build # target location. In this case, attempt to locate it and remove it. baked_target_path = os.path.abspath( os.path.join(target_path, baked_path)) if baked_target_path.startswith( baked_target_path) and not buildable: logger.debug("Removing '%s' from build" % baked_target_path) shutil.rmtree(baked_target_path) else: # path is absolute full_baked_path = os.path.abspath(baked_path) logger.info("Will bake an immutable config into the plugin from '%s'" % full_baked_path) install_path = os.path.join( bundle_cache_root, bootstrap_constants.BAKED_DESCRIPTOR_FOLDER_NAME, BAKED_BUNDLE_NAME, BAKED_BUNDLE_VERSION) cfg_descriptor = create_descriptor(sg_connection, Descriptor.CONFIG, { "type": "path", "path": full_baked_path }) BakedConfiguration.bake_config_scaffold(install_path, sg_connection, manifest_data["plugin_id"], cfg_descriptor) # now lastly, base_config_uri_str = descriptor_dict_to_uri({ "type": bootstrap_constants.BAKED_DESCRIPTOR_TYPE, "name": BAKED_BUNDLE_NAME, "version": BAKED_BUNDLE_VERSION }) else: # if the descriptor in the config contains a version number # we will go into a fixed update mode. if "version" in base_config_uri_dict: logger.info( "Your configuration definition contains a version number. " "This means that the plugin will be frozen and no automatic updates " "will be performed at startup.") using_latest_config = False else: logger.info( "Your configuration definition does not contain a version number. " "This means that the plugin will attempt to auto update at startup." ) using_latest_config = True cfg_descriptor = create_descriptor(sg_connection, Descriptor.CONFIG, base_config_uri_dict, resolve_latest=using_latest_config) logger.info("Resolved config %r" % cfg_descriptor) logger.info("Runtime config descriptor uri will be %s" % base_config_uri_str) return cfg_descriptor, base_config_uri_str
def bake_config(sg_connection, config_uri, target_path, do_zip=False, sparse_caching=False): """ Bake a Toolkit Pipeline configuration. This will ensure a local copy of the configuration, copy it over into target path and then establish a bundle cache containing a reflection of all items required by the config. :param sg_connection: Shotgun connection :param config_uri: A TK config descriptor uri. :param target_path: Path to build :param do_zip: Optionally zip up config once it's baked. Defaults to False. :param sparse_caching: Don't cache app_store bundles into the config. Defaults to False. """ logger.info("Your Toolkit config '%s' will be processed." % config_uri) logger.info("Baking into '%s'" % (target_path)) config_uri_dict = descriptor_uri_to_dict(config_uri) config_descriptor = _process_configuration(sg_connection, config_uri) # Control the output path by adding a folder based on the # configuration descriptor and version. target_path = os.path.join( target_path, "%s-%s" % ( config_descriptor.system_name, config_descriptor.version, )) # Check that target path doesn't exist if os.path.exists(target_path): logger.info("The folder '%s' already exists on disk. Removing it" % target_path) wipe_folder(target_path) # Create target path filesystem.ensure_folder_exists(target_path) # Copy the config data logger.info("Copying config data across...") filesystem.copy_folder(config_descriptor.get_path(), target_path) # Create bundle cache and cache all apps, engines and frameworks logger.info("Creating bundle cache folder...") bundle_cache_root = os.path.join(target_path, BUNDLE_CACHE_ROOT_FOLDER_NAME) filesystem.ensure_folder_exists(bundle_cache_root) logger.info("Downloading and caching config...") config_descriptor.clone_cache(bundle_cache_root) # If sparse_caching is True, we use our own descriptor filter which skips # app_store descriptors to keep our bundle cache small and lets Toolkit # download the bundles from the app store at runtime. if sparse_caching: logger.info( "Performing sparse caching. Will not cache standard app_store bundles." ) cache_apps(sg_connection, config_descriptor, bundle_cache_root, _should_skip_caching_sparse) else: cache_apps(sg_connection, config_descriptor, bundle_cache_root) # Now analyze what core the config needs and cache it if needed. core_descriptor = config_descriptor.associated_core_descriptor if core_descriptor: logger.info( "Config defines a specific core in config/core/core_api.yml.") logger.info("This will be used when the config is executing.") # If sparse_caching is True, check if we need to cache tk-core or not if not sparse_caching or not _should_skip_caching_sparse( core_descriptor): logger.info("Ensuring this core (%s) is cached..." % core_descriptor) associated_core_desc = create_descriptor( sg_connection, Descriptor.CORE, core_descriptor, bundle_cache_root_override=bundle_cache_root) associated_core_desc.ensure_local() else: logger.info( "No need to cache this core (%s), it will be cached at runtime." % config_descriptor.associated_core_descriptor) # Remove unwanted files, e.g. git history. cleanup_bundle_cache(bundle_cache_root) logger.info("") logger.info("Bake complete") logger.info("") logger.info("- Your configuration %r is ready in '%s'" % (config_descriptor, target_path)) logger.info( "- All dependencies have been baked out into the bundle_cache folder") logger.info("") logger.info("") logger.info("") if do_zip: logger.info("Zip archiving the baked configuration...") archive_path = shutil.make_archive(target_path, "zip", root_dir=target_path) logger.info("Zip archive available here: %s" % archive_path)
def main(): """ Main entry point for script. Handles argument parsing and validation and then calls the script payload. """ usage = "%prog [options] config_descriptor target_path" desc = "Bake a self contained Toolkit config from a descriptor" epilog = """ Details and Examples -------------------- In its simplest form, just provide a local path and target folder for the build. > python bake_config.py ~/dev/tk-config-myconfig /tmp/baked_configurations Or you can specify a version with a Toolkit config descriptor uri. > python bake_config.py "sgtk:descriptor:dev?version=v1.0.9&path=../tk-config-myconfig" /tmp/baked_configurations Any type of Toolkit config descriptor uri can be used, if a version is not specified, the latest for the descriptor is resolved. > python bake_config.py "sgtk:descriptor:app_store?name=tk-config-basic" /tmp/baked_configurations {automated_setup_documentation} For information about the various descriptors that can be used, see http://developer.shotgunsoftware.com/tk-core/descriptor """.format(automated_setup_documentation=automated_setup_documentation) parser = OptionParserLineBreakingEpilog(usage=usage, description=desc, epilog=epilog) parser.add_option("-d", "--debug", default=False, action="store_true", help="Enable debug logging") parser.add_option("-z", "--zip", default=False, action="store_true", help="Zip archive the config") parser.add_option("-r", "--sparse", default=False, action="store_true", help="Don't cache any app_store bundles") add_authentication_options(parser) # parse cmd line (options, remaining_args) = parser.parse_args() logger.info("Welcome to the Toolkit config baker.") logger.info("") if options.debug: LogManager().global_debug = True if len(remaining_args) != 2: parser.print_help() return 2 # Get config descriptor config_descriptor = remaining_args[0] # Try to parse it, check if it is a local path if it fails try: descriptor_uri_to_dict(config_descriptor) except TankDescriptorError as e: # Check if it is a local path path = os.path.abspath( os.path.expanduser(os.path.expandvars(config_descriptor))) if os.path.isdir(path): logger.info("Using a dev descriptor for local path %s" % path) # Forge a dev descriptor, using "latest" for the version. # TODO: try to retrieve a valid version from the folder, e.g. with a # git tag from the folder. config_descriptor = "sgtk:descriptor:dev?name=%s&path=%s&version=latest" % ( os.path.basename(path), path) else: logger.error("%s is not a valid descriptor nor a local path." % config_descriptor) raise # Get output path target_path = remaining_args[1] target_path = os.path.expanduser(os.path.expandvars(target_path)) sg_user = authenticate(options) sg_connection = sg_user.create_sg_connection() # make sure we are properly connected try: sg_connection.find_one("HumanUser", []) except Exception, e: logger.error("Could not communicate with Shotgun: %s" % e) return 3
def _process_configuration(sg_connection, source_path, target_path, bundle_cache_root, manifest_data, buildable): """ Given data in the plugin manifest, download resolve and cache the configuration. :param sg_connection: Shotgun connection :param source_path: Root path of plugin source. :param target_path: Build target path :param bundle_cache_root: Bundle cache root :param manifest_data: Manifest data as a dictionary :param buildable: True if the generated build should be buildable :return: (Resolved config descriptor object, config descriptor uri to use at runtime) """ logger.info("Analyzing configuration") # get config def from info yml and generate both # dict and string uris. base_config_def = manifest_data["base_configuration"] if isinstance(base_config_def, str): # convert to dict so we can introspect base_config_uri_dict = descriptor_uri_to_dict(base_config_def) base_config_uri_str = base_config_def else: base_config_uri_dict = base_config_def base_config_uri_str = descriptor_dict_to_uri(base_config_def) # Special case - check for the 'baked' descriptor type # and process it. A baked descriptor is a special concept # that only exists in the build script. The baked descriptor # takes a single path parameter which can be a local or absolute # path. The path is copied across by the build script into a # manual descriptor, with a version number based on the current date. # This ensures that the manual descriptor will be correctly # re-cached at bootstrap time. if base_config_uri_dict["type"] == bootstrap_constants.BAKED_DESCRIPTOR_TYPE: logger.info("Baked descriptor detected.") baked_path = os.path.expanduser(os.path.expandvars(base_config_uri_dict["path"])) # if it's a relative path, expand it if not os.path.isabs(baked_path): full_baked_path = os.path.abspath(os.path.join(source_path, baked_path)) # if it's a relative path, we have already copied it to the build # target location. In this case, attempt to locate it and remove it. baked_target_path = os.path.abspath(os.path.join(target_path, baked_path)) if baked_target_path.startswith(baked_target_path) and not buildable: logger.debug("Removing '%s' from build" % baked_target_path) shutil.rmtree(baked_target_path) else: # path is absolute full_baked_path = os.path.abspath(baked_path) logger.info("Will bake an immutable config into the plugin from '%s'" % full_baked_path) install_path = os.path.join( bundle_cache_root, bootstrap_constants.BAKED_DESCRIPTOR_FOLDER_NAME, BAKED_BUNDLE_NAME, BAKED_BUNDLE_VERSION ) cfg_descriptor = create_descriptor( sg_connection, Descriptor.CONFIG, {"type": "path", "path": full_baked_path} ) BakedConfiguration.bake_config_scaffold( install_path, sg_connection, manifest_data["plugin_id"], cfg_descriptor ) # now lastly, base_config_uri_str = descriptor_dict_to_uri( { "type": bootstrap_constants.BAKED_DESCRIPTOR_TYPE, "name": BAKED_BUNDLE_NAME, "version": BAKED_BUNDLE_VERSION } ) else: # if the descriptor in the config contains a version number # we will go into a fixed update mode. if "version" in base_config_uri_dict: logger.info( "Your configuration definition contains a version number. " "This means that the plugin will be frozen and no automatic updates " "will be performed at startup." ) using_latest_config = False else: logger.info( "Your configuration definition does not contain a version number. " "This means that the plugin will attempt to auto update at startup." ) using_latest_config = True cfg_descriptor = create_descriptor( sg_connection, Descriptor.CONFIG, base_config_uri_dict, resolve_latest=using_latest_config ) logger.info("Resolved config %r" % cfg_descriptor) logger.info("Runtime config descriptor uri will be %s" % base_config_uri_str) return cfg_descriptor, base_config_uri_str
def _process_configuration(sg_connection, source_path, target_path, bundle_cache_root, manifest_data, use_system_core): """ Given data in the plugin manifest, download resolve and cache the configuration. :param sg_connection: Shotgun connection :param source_path: Root path of plugin source. :param target_path: Build target path :param bundle_cache_root: Bundle cache root :param manifest_data: Manifest data as a dictionary :param bool use_system_core: If True, use a globally installed tk-core instead of the one specified in the configuration. :return: (Resolved config descriptor object, config descriptor uri to use at runtime, install path) """ logger.info("Analyzing configuration") install_path = None # get config def from info yml and generate both # dict and string uris. base_config_def = manifest_data["base_configuration"] if isinstance(base_config_def, str): # convert to dict so we can introspect base_config_uri_dict = descriptor_uri_to_dict(base_config_def) base_config_uri_str = base_config_def else: base_config_uri_dict = base_config_def base_config_uri_str = descriptor_dict_to_uri(base_config_def) # Special case - check for the 'baked' descriptor type # and process it. A baked descriptor is a special concept # that only exists in the build script. The baked descriptor # takes a single path parameter which can be a local or absolute # path. The path is copied across by the build script into a # manual descriptor, with a version number based on the current date. # This ensures that the manual descriptor will be correctly # re-cached at bootstrap time. if base_config_uri_dict[ "type"] == bootstrap_constants.BAKED_DESCRIPTOR_TYPE: logger.info("Baked descriptor detected.") baked_path = os.path.expanduser( os.path.expandvars(base_config_uri_dict["path"])) # if it's a relative path, expand it if not os.path.isabs(baked_path): full_baked_path = os.path.abspath( os.path.join(source_path, baked_path)) # if it's a relative path, we have already copied it to the build # target location. In this case, attempt to locate it and remove it. baked_target_path = os.path.abspath( os.path.join(target_path, baked_path)) if baked_target_path.startswith(baked_target_path): logger.debug("Removing '%s' from build" % baked_target_path) shutil.rmtree(baked_target_path) else: # path is absolute full_baked_path = os.path.abspath(baked_path) logger.info("Will bake an immutable config into the plugin from '%s'" % full_baked_path) # A baked config descriptor does not require a name nor a version, so # if these keys are not available, use the current date time for the # version and an arbitrary name for the config. Please note that this # only happens if the baked descriptor was set in the original config. # When baking a plugin with the --bake option, which is the recommended # workflow, these values are automatically set. baked_name = base_config_uri_dict.get("name") or "tk-config-plugin" baked_version = (base_config_uri_dict.get("version") or datetime.datetime.now().strftime("%Y%m%d_%H%M%S")) install_path = os.path.join( bundle_cache_root, bootstrap_constants.BAKED_DESCRIPTOR_FOLDER_NAME, baked_name, baked_version) cfg_descriptor = create_descriptor(sg_connection, Descriptor.CONFIG, { "type": "path", "path": full_baked_path }) BakedConfiguration.bake_config_scaffold(install_path, sg_connection, manifest_data["plugin_id"], cfg_descriptor) # now lastly, base_config_uri_str = descriptor_dict_to_uri({ "type": bootstrap_constants.BAKED_DESCRIPTOR_TYPE, "name": baked_name, "version": baked_version }) if use_system_core: # If asked to use a globally installed tk-core instead of the one # specified by the config, we remove the local copy which was created # in the scaffold step. logger.info("Removing core reference in %s" % install_path) wipe_folder(os.path.join(install_path, "install")) # And make sure we don't have any reference to a tk-core in the config, # otherwise it would be picked up when bootstrapping. filesystem.safe_delete_file( os.path.join(install_path, "config", "core", "core_api.yml")) else: # Workaround for tk-core bootstrap needing a shotgun.yml file: when swapping # tk-core, this file is checked to see if a script user was specified and # should be used in place of the authenticated user. So we create a dummy # file with an "unspecified" host, as the key is required by the tk-core # code parsing the file. # It is not clear if this workaround is needed for non baked configs as # their workflow is different, so for now we just keep it for bake configs # only. shotgun_yaml_path = os.path.join(install_path, "config", "core", "shotgun.yml") if not os.path.exists(shotgun_yaml_path): logger.info("Patching %s" % shotgun_yaml_path) with open(shotgun_yaml_path, "w") as pf: pf.write( "# Workaround for tk-core bootstrap\nhost: unspecified" ) else: # if the descriptor in the config contains a version number # we will go into a fixed update mode. using_latest_config = is_descriptor_version_missing( base_config_uri_dict) if using_latest_config: logger.info( "Your configuration definition does not contain a version number. " "This means that the plugin will attempt to auto update at startup." ) else: logger.info( "Your configuration definition contains a version number. " "This means that the plugin will be frozen and no automatic updates " "will be performed at startup.") cfg_descriptor = create_descriptor(sg_connection, Descriptor.CONFIG, base_config_uri_dict, resolve_latest=using_latest_config) logger.info("Resolved config %r" % cfg_descriptor) logger.info("Runtime config descriptor uri will be %s" % base_config_uri_str) if install_path: logger.info("The config was baked in %s" % install_path) return cfg_descriptor, base_config_uri_str, install_path
def _process_configuration(sg_connection, source_path, target_path, bundle_cache_root, manifest_data, use_system_core): """ Given data in the plugin manifest, download resolve and cache the configuration. :param sg_connection: Shotgun connection :param source_path: Root path of plugin source. :param target_path: Build target path :param bundle_cache_root: Bundle cache root :param manifest_data: Manifest data as a dictionary :param bool use_system_core: If True, use a globally installed tk-core instead of the one specified in the configuration. :return: (Resolved config descriptor object, config descriptor uri to use at runtime, install path) """ logger.info("Analyzing configuration") install_path = None # get config def from info yml and generate both # dict and string uris. base_config_def = manifest_data["base_configuration"] if isinstance(base_config_def, str): # convert to dict so we can introspect base_config_uri_dict = descriptor_uri_to_dict(base_config_def) base_config_uri_str = base_config_def else: base_config_uri_dict = base_config_def base_config_uri_str = descriptor_dict_to_uri(base_config_def) # Special case - check for the 'baked' descriptor type # and process it. A baked descriptor is a special concept # that only exists in the build script. The baked descriptor # takes a single path parameter which can be a local or absolute # path. The path is copied across by the build script into a # manual descriptor, with a version number based on the current date. # This ensures that the manual descriptor will be correctly # re-cached at bootstrap time. if base_config_uri_dict["type"] == bootstrap_constants.BAKED_DESCRIPTOR_TYPE: logger.info("Baked descriptor detected.") baked_path = os.path.expanduser(os.path.expandvars(base_config_uri_dict["path"])) # if it's a relative path, expand it if not os.path.isabs(baked_path): full_baked_path = os.path.abspath(os.path.join(source_path, baked_path)) # if it's a relative path, we have already copied it to the build # target location. In this case, attempt to locate it and remove it. baked_target_path = os.path.abspath(os.path.join(target_path, baked_path)) if baked_target_path.startswith(baked_target_path): logger.debug("Removing '%s' from build" % baked_target_path) shutil.rmtree(baked_target_path) else: # path is absolute full_baked_path = os.path.abspath(baked_path) logger.info("Will bake an immutable config into the plugin from '%s'" % full_baked_path) # A baked config descriptor does not require a name nor a version, so # if these keys are not available, use the current date time for the # version and an arbitrary name for the config. Please note that this # only happens if the baked descriptor was set in the original config. # When baking a plugin with the --bake option, which is the recommended # workflow, these values are automatically set. baked_name = base_config_uri_dict.get("name") or "tk-config-plugin" baked_version = ( base_config_uri_dict.get("version") or datetime.datetime.now().strftime("%Y%m%d_%H%M%S") ) install_path = os.path.join( bundle_cache_root, bootstrap_constants.BAKED_DESCRIPTOR_FOLDER_NAME, baked_name, baked_version ) cfg_descriptor = create_descriptor( sg_connection, Descriptor.CONFIG, {"type": "path", "path": full_baked_path} ) BakedConfiguration.bake_config_scaffold( install_path, sg_connection, manifest_data["plugin_id"], cfg_descriptor ) # now lastly, base_config_uri_str = descriptor_dict_to_uri( { "type": bootstrap_constants.BAKED_DESCRIPTOR_TYPE, "name": baked_name, "version": baked_version } ) if use_system_core: # If asked to use a globally installed tk-core instead of the one # specified by the config, we remove the local copy which was created # in the scaffold step. logger.info("Removing core reference in %s" % install_path) wipe_folder(os.path.join(install_path, "install")) # And make sure we don't have any reference to a tk-core in the config, # otherwise it would be picked up when bootstrapping. filesystem.safe_delete_file(os.path.join(install_path, "config", "core", "core_api.yml")) else: # Workaround for tk-core bootstrap needing a shotgun.yml file: when swapping # tk-core, this file is checked to see if a script user was specified and # should be used in place of the authenticated user. So we create a dummy # file with an "unspecified" host, as the key is required by the tk-core # code parsing the file. # It is not clear if this workaround is needed for non baked configs as # their workflow is different, so for now we just keep it for bake configs # only. shotgun_yaml_path = os.path.join(install_path, "config", "core", "shotgun.yml") if not os.path.exists(shotgun_yaml_path): logger.info("Patching %s" % shotgun_yaml_path) with open(shotgun_yaml_path, "w") as pf: pf.write("# Workaround for tk-core bootstrap\nhost: unspecified") else: # if the descriptor in the config contains a version number # we will go into a fixed update mode. using_latest_config = is_descriptor_version_missing(base_config_uri_dict) if using_latest_config: logger.info( "Your configuration definition does not contain a version number. " "This means that the plugin will attempt to auto update at startup." ) else: logger.info( "Your configuration definition contains a version number. " "This means that the plugin will be frozen and no automatic updates " "will be performed at startup." ) cfg_descriptor = create_descriptor( sg_connection, Descriptor.CONFIG, base_config_uri_dict, resolve_latest=using_latest_config ) logger.info("Resolved config %r" % cfg_descriptor) logger.info("Runtime config descriptor uri will be %s" % base_config_uri_str) if install_path: logger.info("The config was baked in %s" % install_path) return cfg_descriptor, base_config_uri_str, install_path