1import os 2import platform 3from contextlib import contextmanager 4 5import six 6from six import string_types 7 8 9from conans.client import tools 10from conans.client.output import ScopedOutput 11from conans.client.tools.env import environment_append, no_op, pythonpath 12from conans.client.tools.oss import OSInfo 13from conans.errors import ConanException, ConanInvalidConfiguration 14from conans.model.build_info import DepsCppInfo 15from conans.model.conf import Conf 16from conans.model.dependencies import ConanFileDependencies 17from conans.model.env_info import DepsEnvInfo 18from conans.model.layout import Folders, Infos 19from conans.model.new_build_info import from_old_cppinfo 20from conans.model.options import Options, OptionsValues, PackageOptions 21from conans.model.requires import Requirements 22from conans.model.user_info import DepsUserInfo 23from conans.paths import RUN_LOG_NAME 24from conans.util.conan_v2_mode import conan_v2_error 25 26 27def create_options(conanfile): 28 try: 29 package_options = PackageOptions(getattr(conanfile, "options", None)) 30 options = Options(package_options) 31 32 default_options = getattr(conanfile, "default_options", None) 33 if default_options: 34 if isinstance(default_options, dict): 35 default_values = OptionsValues(default_options) 36 elif isinstance(default_options, (list, tuple)): 37 conan_v2_error("Declare 'default_options' as a dictionary") 38 default_values = OptionsValues(default_options) 39 elif isinstance(default_options, six.string_types): 40 conan_v2_error("Declare 'default_options' as a dictionary") 41 default_values = OptionsValues.loads(default_options) 42 else: 43 raise ConanException("Please define your default_options as list, " 44 "multiline string or dictionary") 45 options.values = default_values 46 return options 47 except Exception as e: 48 raise ConanException("Error while initializing options. %s" % str(e)) 49 50 51def create_requirements(conanfile): 52 try: 53 # Actual requirements of this package 54 if not hasattr(conanfile, "requires"): 55 return Requirements() 56 else: 57 if not conanfile.requires: 58 return Requirements() 59 if isinstance(conanfile.requires, (tuple, list)): 60 return Requirements(*conanfile.requires) 61 else: 62 return Requirements(conanfile.requires, ) 63 except Exception as e: 64 raise ConanException("Error while initializing requirements. %s" % str(e)) 65 66 67def create_settings(conanfile, settings): 68 try: 69 defined_settings = getattr(conanfile, "settings", None) 70 if isinstance(defined_settings, str): 71 defined_settings = [defined_settings] 72 current = defined_settings or {} 73 settings.constraint(current) 74 return settings 75 except Exception as e: 76 raise ConanInvalidConfiguration("The recipe %s is constraining settings. %s" % ( 77 conanfile.display_name, str(e))) 78 79 80@contextmanager 81def _env_and_python(conanfile): 82 with environment_append(conanfile.env): 83 # FIXME Conan 2.0, Remove old ways of reusing python code 84 with pythonpath(conanfile): 85 yield 86 87 88def get_env_context_manager(conanfile, without_python=False): 89 if not conanfile.apply_env: 90 return no_op() 91 if without_python: 92 return environment_append(conanfile.env) 93 return _env_and_python(conanfile) 94 95 96class ConanFile(object): 97 """ The base class for all package recipes 98 """ 99 100 name = None 101 version = None # Any str, can be "1.1" or whatever 102 url = None # The URL where this File is located, as github, to collaborate in package 103 # The license of the PACKAGE, just a shortcut, does not replace or 104 # change the actual license of the source code 105 license = None 106 author = None # Main maintainer/responsible for the package, any format 107 description = None 108 topics = None 109 homepage = None 110 build_policy = None 111 short_paths = False 112 apply_env = True # Apply environment variables from requires deps_env_info and profiles 113 exports = None 114 exports_sources = None 115 generators = ["txt"] 116 revision_mode = "hash" 117 118 # Vars to control the build steps (build(), package()) 119 should_configure = True 120 should_build = True 121 should_install = True 122 should_test = True 123 in_local_cache = True 124 develop = False 125 126 # Defaulting the reference fields 127 default_channel = None 128 default_user = None 129 130 # Settings and Options 131 settings = None 132 options = None 133 default_options = None 134 135 provides = None 136 deprecated = None 137 138 # Folders 139 folders = None 140 patterns = None 141 142 # Run in windows bash 143 win_bash = None 144 145 def __init__(self, output, runner, display_name="", user=None, channel=None): 146 # an output stream (writeln, info, warn error) 147 self.output = ScopedOutput(display_name, output) 148 self.display_name = display_name 149 # something that can run commands, as os.sytem 150 self._conan_runner = runner 151 self._conan_user = user 152 self._conan_channel = channel 153 154 self.compatible_packages = [] 155 self._conan_using_build_profile = False 156 self._conan_requester = None 157 from conan.tools.env import Environment 158 self.buildenv_info = Environment() 159 self.runenv_info = Environment() 160 # At the moment only for build_requires, others will be ignored 161 self.conf_info = Conf() 162 self._conan_buildenv = None # The profile buildenv, will be assigned initialize() 163 self._conan_node = None # access to container Node object, to access info, context, deps... 164 self._conan_new_cpp_info = None # Will be calculated lazy in the getter 165 self._conan_dependencies = None 166 167 self.env_scripts = {} # Accumulate the env scripts generated in order 168 169 # layout() method related variables: 170 self.folders = Folders() 171 self.cpp = Infos() 172 173 self.cpp.package.includedirs = ["include"] 174 self.cpp.package.libdirs = ["lib"] 175 self.cpp.package.bindirs = ["bin"] 176 self.cpp.package.resdirs = ["res"] 177 self.cpp.package.builddirs = [""] 178 self.cpp.package.frameworkdirs = ["Frameworks"] 179 180 @property 181 def context(self): 182 return self._conan_node.context 183 184 @property 185 def dependencies(self): 186 # Caching it, this object is requested many times 187 if self._conan_dependencies is None: 188 self._conan_dependencies = ConanFileDependencies.from_node(self._conan_node) 189 return self._conan_dependencies 190 191 @property 192 def ref(self): 193 return self._conan_node.ref 194 195 @property 196 def pref(self): 197 return self._conan_node.pref 198 199 @property 200 def buildenv(self): 201 # Lazy computation of the package buildenv based on the profileone 202 from conan.tools.env import Environment 203 if not isinstance(self._conan_buildenv, Environment): 204 # TODO: missing user/channel 205 ref_str = "{}/{}".format(self.name, self.version) 206 self._conan_buildenv = self._conan_buildenv.get_profile_env(ref_str) 207 return self._conan_buildenv 208 209 def initialize(self, settings, env, buildenv=None): 210 self._conan_buildenv = buildenv 211 if isinstance(self.generators, str): 212 self.generators = [self.generators] 213 # User defined options 214 self.options = create_options(self) 215 self.requires = create_requirements(self) 216 self.settings = create_settings(self, settings) 217 218 conan_v2_error("Setting 'cppstd' is deprecated in favor of 'compiler.cppstd'," 219 " please update your recipe.", 'cppstd' in self.settings.fields) 220 221 # needed variables to pack the project 222 self.cpp_info = None # Will be initialized at processing time 223 self._conan_dep_cpp_info = None # Will be initialized at processing time 224 self.deps_cpp_info = DepsCppInfo() 225 226 # environment variables declared in the package_info 227 self.env_info = None # Will be initialized at processing time 228 self.deps_env_info = DepsEnvInfo() 229 230 # user declared variables 231 self.user_info = None 232 # Keys are the package names (only 'host' if different contexts) 233 self.deps_user_info = DepsUserInfo() 234 235 # user specified env variables 236 self._conan_env_values = env.copy() # user specified -e 237 238 if self.description is not None and not isinstance(self.description, six.string_types): 239 raise ConanException("Recipe 'description' must be a string.") 240 241 if not hasattr(self, "virtualbuildenv"): # Allow the user to override it with True or False 242 self.virtualbuildenv = True 243 if not hasattr(self, "virtualrunenv"): # Allow the user to override it with True or False 244 self.virtualrunenv = True 245 246 @property 247 def new_cpp_info(self): 248 if not self._conan_new_cpp_info: 249 self._conan_new_cpp_info = from_old_cppinfo(self.cpp_info) 250 return self._conan_new_cpp_info 251 252 @property 253 def source_folder(self): 254 return self.folders.source_folder 255 256 @property 257 def build_folder(self): 258 return self.folders.build_folder 259 260 @property 261 def package_folder(self): 262 return self.folders.base_package 263 264 @property 265 def install_folder(self): 266 # FIXME: Remove in 2.0, no self.install_folder 267 return self.folders.base_install 268 269 @property 270 def generators_folder(self): 271 # FIXME: Remove in 2.0, no self.install_folder 272 return self.folders.generators_folder if self.folders.generators else self.install_folder 273 274 @property 275 def imports_folder(self): 276 return self.folders.imports_folder 277 278 @property 279 def env(self): 280 """Apply the self.deps_env_info into a copy of self._conan_env_values (will prioritize the 281 self._conan_env_values, user specified from profiles or -e first, then inherited)""" 282 # Cannot be lazy cached, because it's called in configure node, and we still don't have 283 # the deps_env_info objects available 284 tmp_env_values = self._conan_env_values.copy() 285 tmp_env_values.update(self.deps_env_info) 286 ret, multiple = tmp_env_values.env_dicts(self.name, self.version, self._conan_user, 287 self._conan_channel) 288 ret.update(multiple) 289 return ret 290 291 @property 292 def channel(self): 293 if not self._conan_channel: 294 _env_channel = os.getenv("CONAN_CHANNEL") 295 conan_v2_error("Environment variable 'CONAN_CHANNEL' is deprecated", _env_channel) 296 self._conan_channel = _env_channel or self.default_channel 297 if not self._conan_channel: 298 raise ConanException("channel not defined, but self.channel is used in conanfile") 299 return self._conan_channel 300 301 @property 302 def user(self): 303 if not self._conan_user: 304 _env_username = os.getenv("CONAN_USERNAME") 305 conan_v2_error("Environment variable 'CONAN_USERNAME' is deprecated", _env_username) 306 self._conan_user = _env_username or self.default_user 307 if not self._conan_user: 308 raise ConanException("user not defined, but self.user is used in conanfile") 309 return self._conan_user 310 311 def collect_libs(self, folder=None): 312 conan_v2_error("'self.collect_libs' is deprecated, use 'tools.collect_libs(self)' instead") 313 return tools.collect_libs(self, folder=folder) 314 315 @property 316 def build_policy_missing(self): 317 return self.build_policy == "missing" 318 319 @property 320 def build_policy_always(self): 321 return self.build_policy == "always" 322 323 def source(self): 324 pass 325 326 def system_requirements(self): 327 """ this method can be overwritten to implement logic for system package 328 managers, as apt-get 329 330 You can define self.global_system_requirements = True, if you want the installation 331 to be for all packages (not depending on settings/options/requirements) 332 """ 333 334 def config_options(self): 335 """ modify options, probably conditioned to some settings. This call is executed 336 before config_settings. E.g. 337 if self.settings.os == "Windows": 338 del self.options.shared # shared/static not supported in win 339 """ 340 341 def configure(self): 342 """ modify settings, probably conditioned to some options. This call is executed 343 after config_options. E.g. 344 if self.options.header_only: 345 self.settings.clear() 346 This is also the place for conditional requirements 347 """ 348 349 def build(self): 350 """ build your project calling the desired build tools as done in the command line. 351 E.g. self.run("cmake --build .") Or use the provided build helpers. E.g. cmake.build() 352 """ 353 self.output.warn("This conanfile has no build step") 354 355 def package(self): 356 """ package the needed files from source and build folders. 357 E.g. self.copy("*.h", src="src/includes", dst="includes") 358 """ 359 self.output.warn("This conanfile has no package step") 360 361 def package_info(self): 362 """ define cpp_build_info, flags, etc 363 """ 364 365 def run(self, command, output=True, cwd=None, win_bash=False, subsystem=None, msys_mingw=True, 366 ignore_errors=False, run_environment=False, with_login=True, env=None): 367 # NOTE: "self.win_bash" is the new parameter "win_bash" for Conan 2.0 368 369 def _run(cmd, _env): 370 # FIXME: run in windows bash is not using output 371 if platform.system() == "Windows": 372 if win_bash: 373 return tools.run_in_windows_bash(self, bashcmd=cmd, cwd=cwd, subsystem=subsystem, 374 msys_mingw=msys_mingw, with_login=with_login) 375 elif self.win_bash: # New, Conan 2.0 376 from conan.tools.microsoft.subsystems import run_in_windows_bash 377 return run_in_windows_bash(self, command=cmd, cwd=cwd, env=_env) 378 if _env is None: 379 _env = "conanbuild" 380 from conan.tools.env.environment import environment_wrap_command 381 wrapped_cmd = environment_wrap_command(_env, cmd, cwd=self.generators_folder) 382 return self._conan_runner(wrapped_cmd, output, os.path.abspath(RUN_LOG_NAME), cwd) 383 384 if run_environment: 385 # When using_build_profile the required environment is already applied through 386 # 'conanfile.env' in the contextmanager 'get_env_context_manager' 387 with tools.run_environment(self) if not self._conan_using_build_profile else no_op(): 388 if OSInfo().is_macos and isinstance(command, string_types): 389 # Security policy on macOS clears this variable when executing /bin/sh. To 390 # keep its value, set it again inside the shell when running the command. 391 command = 'DYLD_LIBRARY_PATH="%s" DYLD_FRAMEWORK_PATH="%s" %s' % \ 392 (os.environ.get('DYLD_LIBRARY_PATH', ''), 393 os.environ.get("DYLD_FRAMEWORK_PATH", ''), 394 command) 395 retcode = _run(command, env) 396 else: 397 retcode = _run(command, env) 398 399 if not ignore_errors and retcode != 0: 400 raise ConanException("Error %d while executing %s" % (retcode, command)) 401 402 return retcode 403 404 def package_id(self): 405 """ modify the binary info, typically to narrow values 406 e.g.: self.info.settings.compiler = "Any" => All compilers will generate same ID 407 """ 408 409 def test(self): 410 """ test the generated executable. 411 E.g. self.run("./example") 412 """ 413 raise ConanException("You need to create a method 'test' in your test/conanfile.py") 414 415 def __repr__(self): 416 return self.display_name 417