def _get_key(self, item): for reference in self._data: if reference.ref.name == item: return reference raise ConanException("No requirement matching for %s" % (item))
def __init__(self, conanfile, generator=None, cmake_system_name=True, parallel=True, build_type=None, toolset=None, make_program=None, set_cmake_flags=False, msbuild_verbosity="minimal", cmake_program=None, generator_platform=None, append_vcvars=False): """ :param conanfile: Conanfile instance :param generator: Generator name to use or none to autodetect :param cmake_system_name: False to not use CMAKE_SYSTEM_NAME variable, True for auto-detect or directly a string with the system name :param parallel: Try to build with multiple cores if available :param build_type: Overrides default build type coming from settings :param toolset: Toolset name to use (such as llvm-vs2014) or none for default one, applies only to certain generators (e.g. Visual Studio) :param set_cmake_flags: whether or not to set CMake flags like CMAKE_CXX_FLAGS, CMAKE_C_FLAGS, etc. it's vital to set for certain projects (e.g. using CMAKE_SIZEOF_VOID_P or CMAKE_LIBRARY_ARCHITECTURE) :param msbuild_verbosity: verbosity level for MSBuild (in case of Visual Studio generator) :param cmake_program: Path to the custom cmake executable :param generator_platform: Generator platform name or none to autodetect (-A cmake option) """ if not isinstance(conanfile, ConanFile): raise ConanException( "First argument of CMake() has to be ConanFile. Use CMake(self)" ) self._append_vcvars = append_vcvars self._conanfile = conanfile self._settings = conanfile.settings self._build_type = build_type or conanfile.settings.get_safe( "build_type") self._cmake_program = os.getenv( "CONAN_CMAKE_PROGRAM") or cmake_program or "cmake" self.generator_platform = generator_platform self.generator = generator or get_generator(conanfile.settings) if not self.generator: self._conanfile.output.warn( "CMake generator could not be deduced from settings") self.parallel = parallel # Initialize definitions (won't be updated if conanfile or any of these variables change) builder = CMakeDefinitionsBuilder(self._conanfile, cmake_system_name=cmake_system_name, make_program=make_program, parallel=parallel, generator=self.generator, set_cmake_flags=set_cmake_flags, forced_build_type=build_type, output=self._conanfile.output) # FIXME CONAN 2.0: CMake() interface should be always the constructor and self.definitions. # FIXME CONAN 2.0: Avoid properties and attributes to make the user interface more clear self.definitions = builder.get_definitions() self.definitions["CONAN_EXPORTED"] = "1" self.toolset = toolset or get_toolset(self._settings) self.build_dir = None self.msbuild_verbosity = os.getenv( "CONAN_MSBUILD_VERBOSITY") or msbuild_verbosity
def add_repository(self, repository, repo_key=None): raise ConanException("PkgUtilTool::add_repository not implemented")
def install_packages(self, packages, update=True, force=False, arch_names=None): """ Get the system package tool install command and install all packages and/or variants. Inputs: "pkg-variant1" # (1) Install only one package ["pkg-variant1", "otherpkg", "thirdpkg"] # (2) All must be installed [("pkg-variant1", "pkg-variant2"), "otherpkg", "thirdpkg"] # (3) Install only one variant and all other packages "pkg1 pkg2", "pkg3 pkg4" # (4) Invalid ["pkg1 pkg2", "pkg3 pkg4"] # (5) Invalid :param packages: Supports multiple formats (string,list,tuple). Lists and tuples into a list are considered variants and is processed just like self.install(). A list of string is considered a list of packages to be installed (only not installed yet). :param update: Run update command before to install :param force: Force installing all packages, including all installed. :param arch_names: Package suffix/prefix name used by installer tool e.g. {"x86_64": "amd64"} :return: None """ packages = [packages] if isinstance( packages, six.string_types) else list(packages) # only one (first) variant will be installed list_variants = list( filter(lambda x: isinstance(x, (tuple, list)), packages)) # all packages will be installed packages = list( filter(lambda x: not isinstance(x, (tuple, list)), packages)) if [pkg for pkg in packages if " " in pkg]: raise ConanException( "Each string must contain only one package to be installed. " "Use a list instead e.g. ['foo', 'bar'].") for variant in list_variants: self.install(variant, update=update, force=force, arch_names=arch_names) packages = self._get_package_names(packages, arch_names) mode = self._get_sysrequire_mode() if mode in ("verify", "disabled"): # Report to output packages need to be installed if mode == "disabled": self._output.info( "The following packages need to be installed:\n %s" % "\n".join(packages)) return if mode == "verify" and not self._installed(packages): self._output.error( "The following packages need to be installed:\n %s" % "\n".join(packages)) raise ConanException( "Aborted due to CONAN_SYSREQUIRES_MODE=%s. " "Some system packages need to be installed" % mode) packages = packages if force else self._to_be_installed(packages) if not force and not packages: return # From here system packages can be updated/modified if update and not self._is_up_to_date: self.update() self._install_all(packages)
def unzip(filename, destination=".", keep_permissions=False, pattern=None, output=None): """ Unzip a zipped file :param filename: Path to the zip file :param destination: Destination folder (or file for .gz files) :param keep_permissions: Keep the zip permissions. WARNING: Can be dangerous if the zip was not created in a NIX system, the bits could produce undefined permission schema. Use this option only if you are sure that the zip was created correctly. :param pattern: Extract only paths matching the pattern. This should be a Unix shell-style wildcard, see fnmatch documentation for more details. :param output: output :return: """ output = default_output(output, 'conans.client.tools.files.unzip') if (filename.endswith(".tar.gz") or filename.endswith(".tgz") or filename.endswith(".tbz2") or filename.endswith(".tar.bz2") or filename.endswith(".tar")): return untargz(filename, destination, pattern) if filename.endswith(".gz"): import gzip with gzip.open(filename, 'rb') as f: file_content = f.read() target_name = filename[:-3] if destination == "." else destination save(target_name, file_content) return if filename.endswith(".tar.xz") or filename.endswith(".txz"): if six.PY2: raise ConanException("XZ format not supported in Python 2. Use Python 3 instead") return untargz(filename, destination, pattern) import zipfile full_path = os.path.normpath(os.path.join(get_cwd(), destination)) if hasattr(sys.stdout, "isatty") and sys.stdout.isatty(): def print_progress(the_size, uncomp_size): the_size = (the_size * 100.0 / uncomp_size) if uncomp_size != 0 else 0 txt_msg = "Unzipping %d %%" if the_size > print_progress.last_size + 1: output.rewrite_line(txt_msg % the_size) print_progress.last_size = the_size if int(the_size) == 99: output.rewrite_line(txt_msg % 100) else: def print_progress(_, __): pass with zipfile.ZipFile(filename, "r") as z: if not pattern: zip_info = z.infolist() else: zip_info = [zi for zi in z.infolist() if fnmatch(zi.filename, pattern)] uncompress_size = sum((file_.file_size for file_ in zip_info)) if uncompress_size > 100000: output.info("Unzipping %s, this can take a while" % human_size(uncompress_size)) else: output.info("Unzipping %s" % human_size(uncompress_size)) extracted_size = 0 print_progress.last_size = -1 if platform.system() == "Windows": for file_ in zip_info: extracted_size += file_.file_size print_progress(extracted_size, uncompress_size) try: z.extract(file_, full_path) except Exception as e: output.error("Error extract %s\n%s" % (file_.filename, str(e))) else: # duplicated for, to avoid a platform check for each zipped file for file_ in zip_info: extracted_size += file_.file_size print_progress(extracted_size, uncompress_size) try: z.extract(file_, full_path) if keep_permissions: # Could be dangerous if the ZIP has been created in a non nix system # https://bugs.python.org/issue15795 perm = file_.external_attr >> 16 & 0xFFF os.chmod(os.path.join(full_path, file_.filename), perm) except Exception as e: output.error("Error extract %s\n%s" % (file_.filename, str(e))) output.writeln("")
def __getitem__(self, value): value = str(value) try: return self._definition[value] except: raise ConanException(bad_value_msg(self._name, value, self.values_range))
def value(self, v): v = str(v) if self._definition != "ANY" and v not in self._definition: raise ConanException(bad_value_msg(self._name, v, self.values_range)) self._value = v
def configure(self): del self.settings.compiler.libcxx if self.settings.compiler == "Visual Studio" and int( str(self.settings.compiler.version)) < 14: raise ConanException("Visual Studio >= 14 (2015) is required")
def exists_function(remotes): if remote_name in remotes: raise ConanException( "Remote '%s' already exists in remotes (use update to modify)" % remote_name)
def loads(text): try: return Settings(yaml.safe_load(text) or {}) except (yaml.YAMLError, AttributeError) as ye: raise ConanException("Invalid settings.yml format: {}".format(ye))
def _replace_scm_data_in_conanfile(conanfile_path, scm_data): # Parsing and replacing the SCM field content = load(conanfile_path) headers = [] if six.PY2: # Workaround for https://bugs.python.org/issue22221 lines_without_headers = [] lines = content.splitlines(True) for line in lines: if not lines_without_headers and line.startswith("#"): headers.append(line) else: lines_without_headers.append(line) content = ''.join(lines_without_headers) lines = content.splitlines(True) tree = ast.parse(content) to_replace = [] comments = [] class_line = None tab_size = 4 for i_body, item in enumerate(tree.body): if isinstance(item, ast.ClassDef): statements = item.body class_line = item.lineno for i, stmt in enumerate(item.body): if isinstance(stmt, ast.Assign) and len(stmt.targets) == 1: line = lines[stmt.lineno - 1] tab_size = len(line) - len(line.lstrip()) if isinstance(stmt.targets[0], ast.Name) and stmt.targets[0].id == "scm": try: if i + 1 == len(statements ): # Last statement in my ClassDef if i_body + 1 == len( tree.body): # Last statement over all next_line = len(lines) else: next_line = tree.body[i_body + 1].lineno - 1 else: next_line = statements[i + 1].lineno - 1 next_line_content = lines[next_line].strip() if (next_line_content.endswith('"""') or next_line_content.endswith("'''")): next_line += 1 except IndexError: next_line = stmt.lineno replace = [ line for line in lines[(stmt.lineno - 1):next_line] ] to_replace.append("".join(replace).lstrip()) comments = [ line.strip('\n') for line in replace if line.strip().startswith("#") or not line.strip() ] break if len(to_replace) > 1: raise ConanException( "The conanfile.py defines more than one class level 'scm' attribute" ) new_text = "scm = " + ",\n ".join(str(scm_data).split(",")) + "\n" if len(to_replace) == 0: # SCM exists, but not found in the conanfile, probably inherited from superclass # FIXME: This will inject the lines only the latest class declared in the conanfile tmp = lines[0:class_line] tmp.append("{}{}".format(" " * tab_size, new_text)) tmp.extend(lines[class_line:]) content = ''.join(tmp) else: if comments: new_text += '\n'.join(comments) + "\n" content = content.replace(to_replace[0], new_text) content = content if not headers else ''.join(headers) + content remove(conanfile_path) save(conanfile_path, content)
def value(self, v): v = str(v) if self._not_any() and v not in self.values_range: raise ConanException( bad_value_msg(self._name, v, self.values_range)) self._value = v
def configure(self): v = Version(str(self.settings.compiler.version)) if self.settings.compiler == "gcc" and v < "7.0": raise ConanException("GCC >= 7.0 is required") if self.settings.compiler == "clang" and v < "5.0": raise ConanException("clang >= 5.0 is required")
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 __getitem__(self, item): raise ConanException( "self.%s not defined. If you need it for a " "local command run 'conan install'" % field_name)
def exists_function(remotes): if remote_name not in remotes: raise ConanException("Remote '%s' not found in remotes" % remote_name)
def patch(base_path=None, patch_file=None, patch_string=None, strip=0, output=None): """Applies a diff from file (patch_file) or string (patch_string) in base_path directory or current dir if None""" class PatchLogHandler(logging.Handler): def __init__(self): logging.Handler.__init__(self, logging.DEBUG) self.output = output or ConanOutput(sys.stdout, True) self.patchname = patch_file if patch_file else "patch" def emit(self, record): logstr = self.format(record) if record.levelno == logging.WARN: self.output.warn("%s: %s" % (self.patchname, logstr)) else: self.output.info("%s: %s" % (self.patchname, logstr)) patchlog = logging.getLogger("patch") if patchlog: patchlog.handlers = [] patchlog.addHandler(PatchLogHandler()) if not patch_file and not patch_string: return if patch_file: patchset = fromfile(patch_file) else: patchset = fromstring(patch_string.encode()) if not patchset: raise ConanException("Failed to parse patch: %s" % (patch_file if patch_file else "string")) def decode_clean(path, prefix): path = path.decode("utf-8").replace("\\", "/") if path.startswith(prefix): path = path[2:] return path def strip_path(path): tokens = path.split("/")[strip:] path = "/".join(tokens) if base_path: path = os.path.join(base_path, path) return path # account for new and deleted files, upstream dep won't fix them items = [] for p in patchset: source = decode_clean(p.source, "a/") target = decode_clean(p.target, "b/") if "dev/null" in source: target = strip_path(target) hunks = [s.decode("utf-8") for s in p.hunks[0].text] new_file = "".join(hunk[1:] for hunk in hunks) save(target, new_file) elif "dev/null" in target: source = strip_path(source) os.unlink(source) else: items.append(p) patchset.items = items if not patchset.apply(root=base_path, strip=strip): raise ConanException("Failed to apply patch: %s" % patch_file)
def raise_get_version(): raise ConanException('Error retrieving CMake version')
def undefined_field(name, field, fields=None, value=None): value_str = " for '%s'" % value if value else "" result = ["'%s.%s' doesn't exist%s" % (name, field, value_str), "'%s' possible configurations are %s" % (name, fields or "none")] return ConanException("\n".join(result))
def compiler(self): raise ConanException("mock: not available")
def undefined_value(name): return ConanException("'%s' value not defined" % name)
def msbuild_verbosity_cmd_line_arg(conanfile): verbosity = conanfile.conf["tools.microsoft.msbuild:verbosity"] if verbosity: if verbosity not in ("Quiet", "Minimal", "Normal", "Detailed", "Diagnostic"): raise ConanException("Unknown msbuild verbosity: {}".format(verbosity)) return '/verbosity:{}'.format(verbosity)
def get_conf(self, varname): """Gets the section from config file or raises an exception""" try: return self.items(varname) except NoSectionError: raise ConanException("Invalid configuration, missing %s" % varname)
def _config_node(self, conanfile, conanref, down_reqs, down_ref, down_options): """ update settings and option in the current ConanFile, computing actual requirement values, cause they can be overriden by downstream requires param settings: dict of settings values => {"os": "windows"} """ try: with environment_append(conanfile.env): if hasattr(conanfile, "config"): if not conanref: output = ScopedOutput(str("PROJECT"), self._output) output.warn("config() has been deprecated." " Use config_options and configure") with conanfile_exception_formatter(str(conanfile), "config"): conanfile.config() with conanfile_exception_formatter(str(conanfile), "config_options"): conanfile.config_options() conanfile.options.propagate_upstream(down_options, down_ref, conanref, self._output) if hasattr(conanfile, "config"): with conanfile_exception_formatter(str(conanfile), "config"): conanfile.config() with conanfile_exception_formatter(str(conanfile), "configure"): conanfile.configure() conanfile.settings.validate() # All has to be ok! conanfile.options.validate() # Update requirements (overwrites), computing new upstream if hasattr(conanfile, "requirements"): # If re-evaluating the recipe, in a diamond graph, with different options, # it could happen that one execution path of requirements() defines a package # and another one a different package raising Duplicate dependency error # Or the two consecutive calls, adding 2 different dependencies for the two paths # So it is necessary to save the "requires" state and restore it before a second # execution of requirements(). It is a shallow copy, if first iteration is # RequireResolve'd or overridden, the inner requirements are modified if not hasattr(conanfile, "_original_requires"): conanfile._original_requires = conanfile.requires.copy( ) else: conanfile.requires = conanfile._original_requires.copy( ) with conanfile_exception_formatter(str(conanfile), "requirements"): conanfile.requirements() new_options = conanfile.options.deps_package_values new_down_reqs = conanfile.requires.update( down_reqs, self._output, conanref, down_ref) except ConanExceptionInUserConanfileMethod: raise except ConanException as e: raise ConanException("%s: %s" % (conanref or "Conanfile", str(e))) except Exception as e: raise ConanException(e) return new_down_reqs, new_options
def create_package(conanfile, package_id, source_folder, build_folder, package_folder, install_folder, hook_manager, conanfile_path, ref, local=False, copy_info=False): """ copies built artifacts, libs, headers, data, etc. from build_folder to package folder """ mkdir(package_folder) output = conanfile.output # Make the copy of all the patterns output.info("Generating the package") output.info("Package folder %s" % package_folder) try: conanfile.package_folder = package_folder conanfile.source_folder = source_folder conanfile.install_folder = install_folder conanfile.build_folder = build_folder hook_manager.execute("pre_package", conanfile=conanfile, conanfile_path=conanfile_path, reference=ref, package_id=package_id) package_output = ScopedOutput("%s package()" % output.scope, output) output.highlight("Calling package()") def recipe_has(attribute): return attribute in conanfile.__class__.__dict__ if source_folder != build_folder: conanfile.copy = FileCopier(source_folder, package_folder, build_folder) with conanfile_exception_formatter(str(conanfile), "package"): with tools.chdir(source_folder): conanfile.package() copy_done = conanfile.copy.report(package_output) if not copy_done and recipe_has("package"): output.warn("No files copied from source folder!") conanfile.copy = FileCopier(build_folder, package_folder) with tools.chdir(build_folder): with conanfile_exception_formatter(str(conanfile), "package"): conanfile.package() copy_done = conanfile.copy.report(package_output) if not copy_done and recipe_has("build") and recipe_has("package"): output.warn("No files copied from build folder!") except Exception as e: if not local: os.chdir(build_folder) try: rmdir(package_folder) except Exception as e_rm: output.error("Unable to remove package folder %s\n%s" % (package_folder, str(e_rm))) output.warn("**** Please delete it manually ****") if isinstance(e, ConanExceptionInUserConanfileMethod): raise raise ConanException(e) _create_aux_files(install_folder, package_folder, conanfile, copy_info) package_id = package_id or os.path.basename(package_folder) output.success("Package '%s' created" % package_id) hook_manager.execute("post_package", conanfile=conanfile, conanfile_path=conanfile_path, reference=ref, package_id=package_id)
def write_generators(self, conanfile, old_gen_folder, new_gen_folder, output): """ produces auxiliary files, required to build a project or a package. """ _receive_conf(conanfile) for generator_name in set(conanfile.generators): generator_class = self._new_generator(generator_name, output) if generator_class: if generator_name == "msbuild": msg = ( "\n*****************************************************************\n" "******************************************************************\n" "'msbuild' has been deprecated and moved.\n" "It will be removed in next Conan release.\n" "Use 'MSBuildDeps' method instead.\n" "********************************************************************\n" "********************************************************************\n" ) from conans.client.output import Color output.writeln(msg, front=Color.BRIGHT_RED) try: generator = generator_class(conanfile) output.highlight( "Generator '{}' calling 'generate()'".format( generator_name)) mkdir(new_gen_folder) with chdir(new_gen_folder): generator.generate() continue except Exception as e: raise ConanException("Error in generator '{}': {}".format( generator_name, str(e))) try: generator_class = self._generators[generator_name] except KeyError: available = list( self._generators.keys()) + self._new_generators raise ConanException( "Invalid generator '%s'. Available types: %s" % (generator_name, ", ".join(available))) try: generator = generator_class(conanfile) except TypeError: # To allow old-style generator packages to work (e.g. premake) output.warn( "Generator %s failed with new __init__(), trying old one") generator = generator_class(conanfile.deps_cpp_info, conanfile.cpp_info) try: generator.output_path = old_gen_folder content = generator.content if isinstance(content, dict): if generator.filename: output.warn( "Generator %s is multifile. Property 'filename' not used" % (generator_name, )) for k, v in content.items(): if generator.normalize: # To not break existing behavior, to be removed 2.0 v = normalize(v) output.info("Generator %s created %s" % (generator_name, k)) save(join(old_gen_folder, k), v, only_if_modified=True) else: content = normalize(content) output.info("Generator %s created %s" % (generator_name, generator.filename)) save(join(old_gen_folder, generator.filename), content, only_if_modified=True) except Exception as e: if get_env("CONAN_VERBOSE_TRACEBACK", False): output.error(traceback.format_exc()) output.error("Generator %s(file:%s) failed\n%s" % (generator_name, generator.filename, str(e))) raise ConanException(e)
def patch_config_paths(self): """ changes references to the absolute path of the installed package and its dependencies in exported cmake config files to the appropriate conan variable. This makes most (sensible) cmake config files portable. For example, if a package foo installs a file called "fooConfig.cmake" to be used by cmake's find_package method, normally this file will contain absolute paths to the installed package folder, for example it will contain a line such as: SET(Foo_INSTALL_DIR /home/developer/.conan/data/Foo/1.0.0/...) This will cause cmake find_package() method to fail when someone else installs the package via conan. This function will replace such mentions to SET(Foo_INSTALL_DIR ${CONAN_FOO_ROOT}) which is a variable that is set by conanbuildinfo.cmake, so that find_package() now correctly works on this conan package. For dependent packages, if a package foo installs a file called "fooConfig.cmake" to be used by cmake's find_package method and if it depends to a package bar, normally this file will contain absolute paths to the bar package folder, for example it will contain a line such as: SET_TARGET_PROPERTIES(foo PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "/home/developer/.conan/data/Bar/1.0.0/user/channel/id/include") This function will replace such mentions to SET_TARGET_PROPERTIES(foo PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${CONAN_BAR_ROOT}/include") If the install() method of the CMake object in the conan file is used, this function should be called _after_ that invocation. For example: def build(self): cmake = CMake(self) cmake.configure() cmake.build() cmake.install() cmake.patch_config_paths() """ if not self._conanfile.should_install: return if not self._conanfile.name: raise ConanException( "cmake.patch_config_paths() can't work without package name. " "Define name in your recipe") pf = self.definitions.get(cmake_install_prefix_var_name) replstr = "${CONAN_%s_ROOT}" % self._conanfile.name.upper() allwalk = chain(walk(self._conanfile.build_folder), walk(self._conanfile.package_folder)) # We don't want warnings printed because there is no replacement of the abs path. # there could be MANY cmake files in the package and the normal thing is to not find # the abs paths _null_out = ConanOutput(StringIO()) for root, _, files in allwalk: for f in files: if f.endswith(".cmake") and not f.startswith("conan"): path = os.path.join(root, f) tools.replace_path_in_file(path, pf, replstr, strict=False, output=_null_out) # patch paths of dependent packages that are found in any cmake files of the # current package for dep in self._conanfile.deps_cpp_info.deps: from_str = self._conanfile.deps_cpp_info[dep].rootpath dep_str = "${CONAN_%s_ROOT}" % dep.upper() ret = tools.replace_path_in_file(path, from_str, dep_str, strict=False, output=_null_out) if ret: self._conanfile.output.info( "Patched paths for %s: %s to %s" % (dep, from_str, dep_str))
def _match_manifests(self, read_manifest, expected_manifest, reference): if read_manifest is None or read_manifest != expected_manifest: raise ConanException("%s local cache package is corrupted: " "some file hash doesn't match manifest" % (str(reference)))
def add_repository(self, repository, repo_key=None): raise ConanException("ChocolateyTool::add_repository not implemented")
def _invalid_python_requires(require): raise ConanException("Invalid use of python_requires(%s)" % require)