text: import os
  import platform
  import shutil
  import time
  
  from conans.client import tools
  from conans.client.file_copier import report_copied_files
  from conans.client.generators import TXTGenerator, write_generators
  from conans.client.graph.graph import BINARY_BUILD, BINARY_CACHE, BINARY_DOWNLOAD, BINARY_MISSING, \
      BINARY_SKIP, BINARY_UPDATE
  from conans.client.importer import remove_imports
  from conans.client.output import ScopedOutput
  from conans.client.packager import create_package
  from conans.client.recorder.action_recorder import INSTALL_ERROR_BUILDING, INSTALL_ERROR_MISSING, \
      INSTALL_ERROR_MISSING_BUILD_FOLDER
  from conans.client.source import complete_recipe_sources, config_source
  from conans.client.tools.env import pythonpath
  from conans.errors import (ConanException, ConanExceptionInUserConanfileMethod,
                             conanfile_exception_formatter)
  from conans.model.build_info import CppInfo
  from conans.model.conan_file import get_env_context_manager
  from conans.model.env_info import EnvInfo
  from conans.model.manifest import FileTreeManifest
  from conans.model.ref import PackageReference
  from conans.model.user_info import UserInfo
  from conans.paths import BUILD_INFO, CONANINFO, RUN_LOG_NAME
  from conans.util.env_reader import get_env
  from conans.util.files import (clean_dirty, is_dirty, make_read_only, mkdir, rmdir, save, set_dirty)
  from conans.util.log import logger
  from conans.util.tracer import log_package_built, \
      log_package_got_from_local_cache
  
  
  def build_id(conan_file):
      if hasattr(conan_file, "build_id"):
          # construct new ConanInfo
          build_id_info = conan_file.info.copy()
          conan_file.info_build = build_id_info
          # effectively call the user function to change the package values
          with conanfile_exception_formatter(str(conan_file), "build_id"):
              conan_file.build_id()
          # compute modified ID
          return build_id_info.package_id()
      return None
  
  
  class _ConanPackageBuilder(object):
      """Builds and packages a single conan_file binary package"""
  
      def __init__(self, conan_file, package_reference, client_cache, output, hook_manager):
          self._client_cache = client_cache
          self._conan_file = conan_file
          self._out = output
          self._package_reference = package_reference
          self._conan_ref = self._package_reference.conan
          self._skip_build = False  # If build_id()
          self._hook_manager = hook_manager
  
          new_id = build_id(self._conan_file)
          self.build_reference = PackageReference(self._conan_ref,
                                                  new_id) if new_id else package_reference
          self.build_folder = self._client_cache.build(self.build_reference,
                                                       self._conan_file.short_paths)
          self.package_folder = self._client_cache.package(self._package_reference,
                                                           self._conan_file.short_paths)
          self.source_folder = self._client_cache.source(self._conan_ref, self._conan_file.short_paths)
  
      def prepare_build(self):
          if self.build_reference != self._package_reference and \
                os.path.exists(self.build_folder) and hasattr(self._conan_file, "build_id"):
              self._skip_build = True
              return
  
          # build_id is not caching the build folder, so actually rebuild the package
          export_folder = self._client_cache.export(self._conan_ref)
          export_source_folder = self._client_cache.export_sources(self._conan_ref,
                                                                   self._conan_file.short_paths)
          conanfile_path = self._client_cache.conanfile(self._conan_ref)
  
          try:
              rmdir(self.build_folder)
              rmdir(self.package_folder)
          except OSError as e:
              raise ConanException("%s\n\nCouldn't remove folder, might be busy or open\n"
                                   "Close any app using it, and retry" % str(e))
  
          self._out.info('Building your package in %s' % self.build_folder)
          config_source(export_folder, export_source_folder, self.source_folder,
                        self._conan_file, self._out, conanfile_path, self._conan_ref,
                        self._hook_manager, self._client_cache)
          self._out.info('Copying sources to build folder')
  
          if getattr(self._conan_file, 'no_copy_source', False):
              mkdir(self.build_folder)
              self._conan_file.source_folder = self.source_folder
          else:
              if platform.system() == "Windows" and os.getenv("CONAN_USER_HOME_SHORT") != "None":
                  from conans.util.windows import ignore_long_path_files
                  ignore = ignore_long_path_files(self.source_folder, self.build_folder, self._out)
              else:
                  ignore = None
  
              shutil.copytree(self.source_folder, self.build_folder, symlinks=True, ignore=ignore)
              logger.debug("BUILD: Copied to %s", self.build_folder)
              logger.debug("BUILD: Files copied %s", ",".join(os.listdir(self.build_folder)))
              self._conan_file.source_folder = self.build_folder
  
      def build(self):
          """Calls the conanfile's build method"""
          if self._skip_build:
              return
          with get_env_context_manager(self._conan_file):
              self._build_package()
  
      def package(self):
          """Generate the info txt files and calls the conanfile package method.
          """
  
          # FIXME: Is weak to assign here the recipe_hash
          manifest = self._client_cache.load_manifest(self._conan_ref)
          self._conan_file.info.recipe_hash = manifest.summary_hash
  
          # Creating ***info.txt files
          save(os.path.join(self.build_folder, CONANINFO), self._conan_file.info.dumps())
          self._out.info("Generated %s" % CONANINFO)
          save(os.path.join(self.build_folder, BUILD_INFO), TXTGenerator(self._conan_file).content)
          self._out.info("Generated %s" % BUILD_INFO)
  
          os.chdir(self.build_folder)
  
          if getattr(self._conan_file, 'no_copy_source', False):
              source_folder = self.source_folder
          else:
              source_folder = self.build_folder
          with get_env_context_manager(self._conan_file):
              install_folder = self.build_folder  # While installing, the infos goes to build folder
              pkg_id = self._conan_file.info.package_id()
              conanfile_path = self._client_cache.conanfile(self._conan_ref)
  
              create_package(self._conan_file, pkg_id, source_folder, self.build_folder,
                             self.package_folder, install_folder, self._hook_manager,
                             conanfile_path, self._conan_ref)
  
          p_hash = self._client_cache.package_summary_hash(self._package_reference)
          p_id = self._package_reference.package_id
  
          with self._client_cache.update_metadata(self._conan_ref) as metadata:
              metadata.packages[p_id].revision = p_hash
              metadata.packages[p_id].recipe_revision = self._conan_ref.revision
  
          if get_env("CONAN_READ_ONLY_CACHE", False):
              make_read_only(self.package_folder)
  
      def _build_package(self):
          """ calls the imports + conanfile.build() method
          """
          os.chdir(self.build_folder)
          self._conan_file.build_folder = self.build_folder
          self._conan_file.package_folder = self.package_folder
          # In local cache, install folder always is build_folder
          self._conan_file.install_folder = self.build_folder
  
          # Read generators from conanfile and generate the needed files
          logger.info("GENERATORS: Writing generators")
          write_generators(self._conan_file, self.build_folder, self._out)
  
          # Build step might need DLLs, binaries as protoc to generate source files
          # So execute imports() before build, storing the list of copied_files
          from conans.client.importer import run_imports
          copied_files = run_imports(self._conan_file, self.build_folder)
  
          try:
              # This is necessary because it is different for user projects
              # than for packages
              self._hook_manager.execute("pre_build", conanfile=self._conan_file,
                                         reference=self._conan_ref,
                                         package_id=self._package_reference.package_id)
              logger.debug("Call conanfile.build() with files in build folder: %s",
                           os.listdir(self.build_folder))
              self._out.highlight("Calling build()")
              with conanfile_exception_formatter(str(self._conan_file), "build"):
                  self._conan_file.build()
  
              self._out.success("Package '%s' built" % self._conan_file.info.package_id())
              self._out.info("Build folder %s" % self.build_folder)
              self._hook_manager.execute("post_build", conanfile=self._conan_file,
                                         reference=self._conan_ref,
                                         package_id=self._package_reference.package_id)
          except Exception as exc:
              self._out.writeln("")
              self._out.error("Package '%s' build failed" % self._conan_file.info.package_id())
              self._out.warn("Build folder %s" % self.build_folder)
              if isinstance(exc, ConanExceptionInUserConanfileMethod):
                  raise exc
              raise ConanException(exc)
          finally:
              # Now remove all files that were imported with imports()
              remove_imports(self._conan_file, copied_files, self._out)
  
  
  def _handle_system_requirements(conan_file, package_reference, client_cache, out):
      """ check first the system_reqs/system_requirements.txt existence, if not existing
      check package/sha1/
  
      Used after remote package retrieving and before package building
      """
      if "system_requirements" not in type(conan_file).__dict__:
          return
  
      system_reqs_path = client_cache.system_reqs(package_reference.conan)
      system_reqs_package_path = client_cache.system_reqs_package(package_reference)
      if os.path.exists(system_reqs_path) or os.path.exists(system_reqs_package_path):
          return
  
      ret = call_system_requirements(conan_file, out)
  
      try:
          ret = str(ret or "")
      except Exception:
          out.warn("System requirements didn't return a string")
          ret = ""
      if getattr(conan_file, "global_system_requirements", None):
          save(system_reqs_path, ret)
      else:
          save(system_reqs_package_path, ret)
  
  
  def call_system_requirements(conanfile, output):
      try:
          return conanfile.system_requirements()
      except Exception as e:
          output.error("while executing system_requirements(): %s" % str(e))
          raise ConanException("Error in system requirements")
  
  
  def raise_package_not_found_error(conan_file, conan_ref, package_id, dependencies, out, recorder):
      settings_text = ", ".join(conan_file.info.full_settings.dumps().splitlines())
      options_text = ", ".join(conan_file.info.full_options.dumps().splitlines())
      dependencies_text = ', '.join(dependencies)
  
      msg = '''Can't find a '%s' package for the specified settings, options and dependencies:
  - Settings: %s
  - Options: %s
  - Dependencies: %s
  - Package ID: %s
  ''' % (conan_ref, settings_text, options_text, dependencies_text, package_id)
      out.warn(msg)
      recorder.package_install_error(PackageReference(conan_ref, package_id),
                                     INSTALL_ERROR_MISSING, msg)
      raise ConanException('''Missing prebuilt package for '%s'
  Try to build it from sources with "--build %s"
  Or read "http://docs.conan.io/en/latest/faq/troubleshooting.html#error-missing-prebuilt-package"
  ''' % (conan_ref, conan_ref.name))
  
  
  class BinaryInstaller(object):
      """ main responsible of retrieving binary packages or building them from source
      locally in case they are not found in remotes
      """
      def __init__(self, client_cache, output, remote_manager, recorder, workspace, hook_manager):
          self._client_cache = client_cache
          self._out = output
          self._remote_manager = remote_manager
          self._registry = client_cache.registry
          self._recorder = recorder
          self._workspace = workspace
          self._hook_manager = hook_manager
  
      def install(self, deps_graph, keep_build=False, graph_info=None):
          # order by levels and separate the root node (conan_ref=None) from the rest
          nodes_by_level = deps_graph.by_levels()
          root_level = nodes_by_level.pop()
          root_node = root_level[0]
          # Get the nodes in order and if we have to build them
          self._build(nodes_by_level, deps_graph, keep_build, root_node, graph_info)
  
      def _build(self, nodes_by_level, deps_graph, keep_build, root_node, graph_info):
          inverse_levels = {n: i for i, level in enumerate(deps_graph.inverse_levels()) for n in level}
  
          processed_package_refs = set()
          for level in nodes_by_level:
              for node in level:
                  conan_ref, conan_file = node.conan_ref, node.conanfile
                  output = conan_file.output
                  package_id = conan_file.info.package_id()
                  if node.binary == BINARY_MISSING:
                      dependencies = [str(dep.dst) for dep in node.dependencies]
                      raise_package_not_found_error(conan_file, conan_ref, package_id, dependencies,
                                                    out=output, recorder=self._recorder)
  
                  workspace_package = self._workspace[node.conan_ref] if self._workspace else None
                  if workspace_package:
                      self._handle_node_workspace(node, workspace_package, inverse_levels, deps_graph,
                                                  graph_info)
                  else:
                      self._propagate_info(node, inverse_levels, deps_graph)
                      if node.binary == BINARY_SKIP:  # Privates not necessary
                          continue
                      package_ref = PackageReference(conan_ref, package_id)
                      _handle_system_requirements(conan_file, package_ref, self._client_cache, output)
                      self._handle_node_cache(node, package_ref, keep_build, processed_package_refs)
  
          # Finally, propagate information to root node (conan_ref=None)
          self._propagate_info(root_node, inverse_levels, deps_graph)
  
      def _node_concurrently_installed(self, node, package_folder):
          if node.binary == BINARY_DOWNLOAD and os.path.exists(package_folder):
              return True
          elif node.binary == BINARY_UPDATE:
              read_manifest = FileTreeManifest.load(package_folder)
              if node.update_manifest == read_manifest:
                  return True
  
      def _handle_node_cache(self, node, package_ref, keep_build, processed_package_references):
          conan_file = node.conanfile
          output = conan_file.output
          package_folder = self._client_cache.package(package_ref, conan_file.short_paths)
  
          with self._client_cache.package_lock(package_ref):
              if package_ref not in processed_package_references:
                  processed_package_references.add(package_ref)
                  set_dirty(package_folder)
                  if node.binary == BINARY_BUILD:
                      self._build_package(node, package_ref, output, keep_build)
                  elif node.binary in (BINARY_UPDATE, BINARY_DOWNLOAD):
                      if not self._node_concurrently_installed(node, package_folder):
                          new_ref = self._remote_manager.get_package(package_ref, package_folder,
                                                                     node.binary_remote, output,
                                                                     self._recorder)
                          self._registry.prefs.set(new_ref, node.binary_remote.name)
                      else:
                          output.success('Download skipped. Probable concurrent download')
                          log_package_got_from_local_cache(package_ref)
                          self._recorder.package_fetched_from_cache(package_ref)
                  elif node.binary == BINARY_CACHE:
                      output.success('Already installed!')
                      log_package_got_from_local_cache(package_ref)
                      self._recorder.package_fetched_from_cache(package_ref)
                  clean_dirty(package_folder)
              # Call the info method
              self._call_package_info(conan_file, package_folder)
              self._recorder.package_cpp_info(package_ref, conan_file.cpp_info)
  
      def _handle_node_workspace(self, node, workspace_package, inverse_levels, deps_graph,
                                 graph_info):
          conan_file = node.conanfile
          output = ScopedOutput("Workspace %s" % conan_file.display_name, self._out)
          include_dirs = workspace_package.includedirs
          lib_dirs = workspace_package.libdirs
          self._call_package_info(conan_file, workspace_package.package_folder)
          if include_dirs:
              conan_file.cpp_info.includedirs = include_dirs
          if lib_dirs:
              conan_file.cpp_info.libdirs = lib_dirs
              # Make sure the folders exists, otherwise they will be filtered out
              lib_paths = [os.path.join(conan_file.cpp_info.rootpath, p)
                           if not os.path.isabs(p) else p for p in lib_dirs]
              for p in lib_paths:
                  mkdir(p)
  
          self._propagate_info(node, inverse_levels, deps_graph)
  
          build_folder = workspace_package.build_folder
          write_generators(conan_file, build_folder, output)
          save(os.path.join(build_folder, CONANINFO), conan_file.info.dumps())
          output.info("Generated %s" % CONANINFO)
          graph_info.save(build_folder)
          output.info("Generated graphinfo")
          save(os.path.join(build_folder, BUILD_INFO), TXTGenerator(conan_file).content)
          output.info("Generated %s" % BUILD_INFO)
          # Build step might need DLLs, binaries as protoc to generate source files
          # So execute imports() before build, storing the list of copied_files
          from conans.client.importer import run_imports
          copied_files = run_imports(conan_file, build_folder)
          report_copied_files(copied_files, output)
  
      def _build_package(self, node, package_ref, output, keep_build):
          conan_ref, conan_file = node.conan_ref, node.conanfile
  
          t1 = time.time()
          # It is necessary to complete the sources of python requires, which might be used
          for python_require in conan_file.python_requires:
              complete_recipe_sources(self._remote_manager, self._client_cache,
                                      conan_file, python_require.conan_ref)
  
          builder = _ConanPackageBuilder(conan_file, package_ref, self._client_cache, output,
                                         self._hook_manager)
  
          if is_dirty(builder.build_folder):
              output.warn("Build folder is dirty, removing it: %s" % builder.build_folder)
              rmdir(builder.build_folder)
  
          skip_build = conan_file.develop and keep_build
          if skip_build:
              output.info("Won't be built as specified by --keep-build")
          if skip_build:
              if not os.path.exists(builder.build_folder):
                  msg = "--keep-build specified, but build folder not found"
                  self._recorder.package_install_error(package_ref,
                                                       INSTALL_ERROR_MISSING_BUILD_FOLDER,
                                                       msg, remote_name=None)
                  raise ConanException(msg)
          else:
              with self._client_cache.conanfile_write_lock(conan_ref):
                  set_dirty(builder.build_folder)
                  complete_recipe_sources(self._remote_manager, self._client_cache,
                                          conan_file, conan_ref)
                  builder.prepare_build()
  
          with self._client_cache.conanfile_read_lock(conan_ref):
              try:
                  if not skip_build:
                      builder.build()
                      clean_dirty(builder.build_folder)
                  builder.package()
              except ConanException as exc:
                  self._recorder.package_install_error(package_ref, INSTALL_ERROR_BUILDING,
                                                       str(exc), remote_name=None)
                  raise exc
              else:
                  # Log build
                  self._log_built_package(builder.build_folder, package_ref.copy_clear_rev(),
                                          time.time() - t1)
                  # FIXME: Conan 2.0 Clear the registry entry (package ref)
  
      def _log_built_package(self, build_folder, package_ref, duration):
          log_file = os.path.join(build_folder, RUN_LOG_NAME)
          log_file = log_file if os.path.exists(log_file) else None
          log_package_built(package_ref, duration, log_file)
          self._recorder.package_built(package_ref)
  
      @staticmethod
      def _propagate_info(node, inverse_levels, deps_graph):
          # Get deps_cpp_info from upstream nodes
          closure = deps_graph.full_closure(node)
          node_order = [n for n in closure.values() if n.binary != BINARY_SKIP]
          # List sort is stable, will keep the original order of the closure, but prioritize levels
          node_order.sort(key=lambda n: inverse_levels[n])
  
          conan_file = node.conanfile
          for n in node_order:
              if n.build_require:
                  conan_file.output.info("Applying build-requirement: %s" % str(n.conan_ref))
              conan_file.deps_cpp_info.update(n.conanfile.cpp_info, n.conan_ref.name)
              conan_file.deps_env_info.update(n.conanfile.env_info, n.conan_ref.name)
              conan_file.deps_user_info[n.conan_ref.name] = n.conanfile.user_info
  
          # Update the info but filtering the package values that not apply to the subtree
          # of this current node and its dependencies.
          subtree_libnames = [node.conan_ref.name for node in node_order]
          for package_name, env_vars in conan_file._conan_env_values.data.items():
              for name, value in env_vars.items():
                  if not package_name or package_name in subtree_libnames or \
                     package_name == conan_file.name:
                      conan_file.info.env_values.add(name, value, package_name)
  
      @staticmethod
      def _call_package_info(conanfile, package_folder):
          conanfile.cpp_info = CppInfo(package_folder)
          conanfile.cpp_info.version = conanfile.version
          conanfile.cpp_info.description = conanfile.description
          conanfile.env_info = EnvInfo()
          conanfile.user_info = UserInfo()
  
          # Get deps_cpp_info from upstream nodes
          public_deps = [name for name, req in conanfile.requires.items() if not req.private]
          conanfile.cpp_info.public_deps = public_deps
          # Once the node is build, execute package info, so it has access to the
          # package folder and artifacts
          with pythonpath(conanfile):  # Minimal pythonpath, not the whole context, make it 50% slower
              with tools.chdir(package_folder):
                  with conanfile_exception_formatter(str(conanfile), "package_info"):
                      conanfile.package_folder = package_folder
                      conanfile.source_folder = None
                      conanfile.build_folder = None
                      conanfile.install_folder = None
                      conanfile.package_info()
  