docs: def upload_resources(src_dir, project=None, folder='/', ensure_upload=False, force_symlinks=False):
      """
      :param ensure_upload: If True, will bypass checksum of resources directory
                            and upload resources bundle unconditionally;
                            will NOT be able to reuse this bundle in future builds.
                            Else if False, will compute checksum and upload bundle
                            if checksum is different from a previously uploaded
                            bundle's checksum.
      :type ensure_upload: boolean
      :param force_symlinks: If true, will bypass the attempt to dereference any
                             non-local symlinks and will unconditionally include
                             the link as-is.  Note that this will almost certainly
                             result in a broken link within the resource directory
                             unless you really know what you're doing.
      :type force_symlinks: boolean
      :returns: A list (possibly empty) of references to the generated archive(s)
      :rtype: list
  
      If it exists, archives and uploads the contents of the
      ``resources/`` subdirectory of *src_dir* to a new remote file
      object, and returns a list describing a single bundled dependency in
      the form expected by the ``bundledDepends`` field of a run
      specification. Returns an empty list, if no archive was created.
      """
      applet_spec = _get_applet_spec(src_dir)
  
      if project is None:
          dest_project = applet_spec['project']
      else:
          dest_project = project
          applet_spec['project'] = project
  
      resources_dir = os.path.join(src_dir, "resources")
      if os.path.exists(resources_dir) and len(os.listdir(resources_dir)) > 0:
          target_folder = applet_spec['folder'] if 'folder' in applet_spec else folder
  
          # While creating the resource bundle, optimistically look for a
          # resource bundle with the same contents, and reuse it if possible.
          # The resource bundle carries a property 'resource_bundle_checksum'
          # that indicates the checksum; the way in which the checksum is
          # computed is given below.   If the checksum matches  (and
          # ensure_upload is False), then we will use the existing file,
          # otherwise, we will compress and upload the tarball.
  
  
          # The input to the SHA1 contains entries of the form (whitespace
          # only included here for readability):
          #
          # / \0 MODE \0 MTIME \0
          # /foo \0 MODE \0 MTIME \0
          # ...
          #
          # where there is one entry for each directory or file (order is
          # specified below), followed by a numeric representation of the
          # mode, and the mtime in milliseconds since the epoch.
          #
          # Note when looking at a link, if the link is to be dereferenced,
          # the mtime and mode used are that of the target (using os.stat())
          # If the link is to be kept as a link, the mtime and mode are those
          # of the link itself (using os.lstat())
  
          with tempfile.NamedTemporaryFile(suffix=".tar") as tar_tmp_fh:
  
              output_sha1 = hashlib.sha1()
              tar_fh = tarfile.open(fileobj=tar_tmp_fh, mode='w')
  
              for dirname, subdirs, files in os.walk(resources_dir):
                  if not dirname.startswith(resources_dir):
                      raise AssertionError('Expected %r to start with root directory %r' % (dirname, resources_dir))
  
                  # Add an entry for the directory itself
                  relative_dirname = dirname[len(resources_dir):]
                  dir_stat = os.lstat(dirname)
                  if not relative_dirname.startswith('/'):
                      relative_dirname = '/' + relative_dirname
  
                  fields = [relative_dirname, str(_fix_perms(dir_stat.st_mode)), str(int(dir_stat.st_mtime * 1000))]
                  output_sha1.update(b''.join(s.encode('utf-8') + b'\0' for s in fields))
  
                  # add an entry in the tar file for the current directory, but
                  # do not recurse!
                  tar_fh.add(dirname, arcname='.' + relative_dirname, recursive=False, filter=_fix_perm_filter)
  
                  # Canonicalize the order of subdirectories; this is the order in
                  # which they will be visited by os.walk
                  subdirs.sort()
  
                  # check the subdirectories for symlinks.  We should throw an error
                  # if there are any links that point outside of the directory (unless
                  # --force-symlinks is given).  If a link is pointing internal to
                  # the directory (or --force-symlinks is given), we should add it
                  # as a file.
                  for subdir_name in subdirs:
                      dir_path = os.path.join(dirname, subdir_name)
  
                      # If we do have a symlink,
                      if os.path.islink(dir_path):
                          # Let's get the pointed-to path to ensure that it is
                          # still in the directory
                          link_target = os.readlink(dir_path)
  
                          # If this is a local link, add it to the list of files (case 1)
                          # else raise an error
                          if force_symlinks or is_link_local(link_target):
                              files.append(subdir_name)
                          else:
                              raise AppBuilderException("Cannot include symlinks to directories outside of the resource directory.  '%s' points to directory '%s'" % (dir_path, os.path.realpath(dir_path)))
  
  
                  # Canonicalize the order of files so that we compute the
                  # checksum in a consistent order
                  for filename in sorted(files):
                      deref_link = False
  
                      relative_filename = os.path.join(relative_dirname, filename)
                      true_filename = os.path.join(dirname, filename)
  
                      file_stat = os.lstat(true_filename)
                      # check for a link here, please!
                      if os.path.islink(true_filename):
  
                          # Get the pointed-to path
                          link_target = os.readlink(true_filename)
  
                          if not (force_symlinks or is_link_local(link_target)):
                              # if we are pointing outside of the directory, then:
                              # try to get the true stat of the file and make sure
                              # to dereference the link!
                              try:
                                  file_stat = os.stat(os.path.join(dirname, link_target))
                                  deref_link = True
                              except OSError:
                                  # uh-oh! looks like we have a broken link!
                                  # since this is guaranteed to cause problems (and
                                  # we know we're not forcing symlinks here), we
                                  # should throw an error
                                  raise AppBuilderException("Broken symlink: Link '%s' points to '%s', which does not exist" % (true_filename, os.path.realpath(true_filename)) )
  
  
                      fields = [relative_filename, str(_fix_perms(file_stat.st_mode)), str(int(file_stat.st_mtime * 1000))]
                      output_sha1.update(b''.join(s.encode('utf-8') + b'\0' for s in fields))
  
                      # If we are to dereference, use the target fn
                      if deref_link:
                          true_filename = os.path.realpath(true_filename)
  
                      tar_fh.add(true_filename, arcname='.' + relative_filename, filter=_fix_perm_filter)
  
                  # end for filename in sorted(files)
  
              # end for dirname, subdirs, files in os.walk(resources_dir):
  
              # at this point, the tar is complete, so close the tar_fh
              tar_fh.close()
  
              # Optimistically look for a resource bundle with the same
              # contents, and reuse it if possible. The resource bundle
              # carries a property 'resource_bundle_checksum' that indicates
              # the checksum; the way in which the checksum is computed is
              # given in the documentation of _directory_checksum.
  
              if ensure_upload:
                  properties_dict = {}
                  existing_resources = False
              else:
                  directory_checksum = output_sha1.hexdigest()
                  properties_dict = dict(resource_bundle_checksum=directory_checksum)
                  existing_resources = dxpy.find_one_data_object(
                      project=dest_project,
                      folder=target_folder,
                      properties=dict(resource_bundle_checksum=directory_checksum),
                      visibility='either',
                      zero_ok=True,
                      state='closed',
                      return_handler=True
                  )
  
              if existing_resources:
                  logger.info("Found existing resource bundle that matches local resources directory: " +
                              existing_resources.get_id())
  
                  dx_resource_archive = existing_resources
              else:
  
                  logger.debug("Uploading in " + src_dir)
                  # We need to compress the tar that we've created
  
  
                  targz_fh = tempfile.NamedTemporaryFile(suffix=".tar.gz", delete=False)
  
                  # compress the file by reading the tar file and passing
                  # it though a GzipFile object, writing the given
                  # block size (by default 8192 bytes) at a time
                  targz_gzf = gzip.GzipFile(fileobj=targz_fh, mode='wb')
                  tar_tmp_fh.seek(0)
                  dat = tar_tmp_fh.read(io.DEFAULT_BUFFER_SIZE)
                  while dat:
                      targz_gzf.write(dat)
                      dat = tar_tmp_fh.read(io.DEFAULT_BUFFER_SIZE)
  
                  targz_gzf.flush()
                  targz_gzf.close()
                  targz_fh.close()
  
                  if 'folder' in applet_spec:
                      try:
                          dxpy.get_handler(dest_project).new_folder(applet_spec['folder'], parents=True)
                      except dxpy.exceptions.DXAPIError:
                          pass # TODO: make this better
  
                  dx_resource_archive = dxpy.upload_local_file(
                      targz_fh.name,
                      wait_on_close=True,
                      project=dest_project,
                      folder=target_folder,
                      hidden=True,
                      properties=properties_dict
                  )
  
                  os.unlink(targz_fh.name)
  
                  # end compressed file creation and upload
  
              archive_link = dxpy.dxlink(dx_resource_archive.get_id())
  
          # end tempfile.NamedTemporaryFile(suffix=".tar") as tar_fh
  
          return [{'name': 'resources.tar.gz', 'id': archive_link}]
      else:
          return []