def run_in_windows_bash(conanfile, command, cwd=None, env=None): """ Will run a unix command inside a bash terminal It requires to have MSYS2, CYGWIN, or WSL""" if env: # Passing env invalidates the conanfile.environment_scripts env_win = [env] if not isinstance(env, list) else env env_shell = [] else: env_shell = ["conanbuild.sh"] env_win = ["conanbuild.bat"] subsystem = conanfile.conf["tools.microsoft.bash:subsystem"] shell_path = conanfile.conf["tools.microsoft.bash:path"] if not platform.system() == "Windows": raise ConanException("Command only for Windows operating system") if not subsystem or not shell_path: raise ConanException("The config 'tools.microsoft.bash:subsystem' and 'tools.microsoft.bash:path' are " "needed to run commands in a Windows subsystem") if subsystem == MSYS2: # Configure MSYS2 to inherith the PATH msys2_mode_env = Environment(conanfile) _msystem = {"x86": "MINGW32"}.get(conanfile.settings.get_safe("arch"), "MINGW64") msys2_mode_env.define("MSYSTEM", _msystem) msys2_mode_env.define("MSYS2_PATH_TYPE", "inherit") path = os.path.join(conanfile.generators_folder, "msys2_mode.bat") msys2_mode_env.save_bat(path) env_win.append(path) # Needed to change to that dir inside the bash shell wrapped_shell = '"%s"' % shell_path if " " in shell_path else shell_path if env_win: wrapped_shell = environment_wrap_command(conanfile, env_win, shell_path, cwd=conanfile.generators_folder) cwd = cwd or os.getcwd() if not os.path.isabs(cwd): cwd = os.path.join(os.getcwd(), cwd) cwd_inside = unix_path(conanfile, cwd) wrapped_user_cmd = command if env_shell: # Wrapping the inside_command enable to prioritize our environment, otherwise /usr/bin go # first and there could be commands that we want to skip wrapped_user_cmd = environment_wrap_command(conanfile, env_shell, command, cwd=conanfile.generators_folder) inside_command = 'cd "{cwd_inside}" && ' \ '{wrapped_user_cmd}'.format(cwd_inside=cwd_inside, wrapped_user_cmd=wrapped_user_cmd) inside_command = escape_windows_cmd(inside_command) final_command = 'cd "{cwd}" && {wrapped_shell} --login -c {inside_command}'.format( cwd=cwd, wrapped_shell=wrapped_shell, inside_command=inside_command) conanfile.output.info('Running in windows bash: %s' % final_command) return conanfile._conan_runner(final_command, output=conanfile.output, subprocess=True)
def run_pkg(msg): # FIXME: This only works with ``--install-folder``, layout() will break this cmd_release = environment_wrap_command("conanrunenv-release-x86_64", "dep_app", cwd=c.current_folder) c.run_command(cmd_release) assert "{}: Release!".format(msg) in c.out cmd_release = environment_wrap_command("conanrunenv-debug-x86_64", "dep_app", cwd=c.current_folder) c.run_command(cmd_release) assert "{}: Debug!".format(msg) in c.out
def test_shared_cmake_toolchain(): client = TestClient(default_server_user=True) client.save(pkg_cmake("hello", "0.1")) client.run("create . -o hello:shared=True") client.save(pkg_cmake("chat", "0.1", requires=["hello/0.1"]), clean_first=True) client.run("create . -o chat:shared=True -o hello:shared=True") client.save(pkg_cmake_app("app", "0.1", requires=["chat/0.1"]), clean_first=True) client.run("create . -o chat:shared=True -o hello:shared=True") client.run("upload * --all -c") client.run("remove * -f") client = TestClient(servers=client.servers, users=client.users) client.run( "install app/0.1@ -o chat:shared=True -o hello:shared=True -g VirtualRunEnv" ) conanfile = ConanFileMock() command = environment_wrap_command(conanfile, "conanrun", "app", cwd=client.current_folder) client.run_command(command) assert "main: Release!" in client.out assert "chat: Release!" in client.out assert "hello: Release!" in client.out
def run(self, command, output=True, cwd=None, win_bash=False, subsystem=None, msys_mingw=True, ignore_errors=False, run_environment=False, with_login=True, env="conanbuildenv"): command = environment_wrap_command(env, command, cwd=self.generators_folder) def _run(): if not win_bash: return self._conan_runner(command, output, os.path.abspath(RUN_LOG_NAME), cwd) # FIXME: run in windows bash is not using output return tools.run_in_windows_bash(self, bashcmd=command, cwd=cwd, subsystem=subsystem, msys_mingw=msys_mingw, with_login=with_login) if run_environment: # When using_build_profile the required environment is already applied through # 'conanfile.env' in the contextmanager 'get_env_context_manager' with tools.run_environment(self) if not self._conan_using_build_profile else no_op(): if OSInfo().is_macos and isinstance(command, string_types): # Security policy on macOS clears this variable when executing /bin/sh. To # keep its value, set it again inside the shell when running the command. command = 'DYLD_LIBRARY_PATH="%s" DYLD_FRAMEWORK_PATH="%s" %s' % \ (os.environ.get('DYLD_LIBRARY_PATH', ''), os.environ.get("DYLD_FRAMEWORK_PATH", ''), command) retcode = _run() else: retcode = _run() if not ignore_errors and retcode != 0: raise ConanException("Error %d while executing %s" % (retcode, command)) return retcode
def test_complete(client): conanfile = textwrap.dedent(""" import platform from conans import ConanFile class Pkg(ConanFile): requires = "openssl/1.0" build_requires = "mycmake/1.0" apply_env = False def build_requirements(self): self.build_requires("mygtest/1.0", force_host_context=True) def build(self): mybuild_cmd = "mycmake.bat" if platform.system() == "Windows" else "mycmake.sh" self.run(mybuild_cmd, env="conanbuildenv") mytest_cmd = "mygtest.bat" if platform.system() == "Windows" else "mygtest.sh" self.run(mytest_cmd, env="conanrunenv") """) client.save({"conanfile.py": conanfile}) client.run("install . -s:b os=Windows -s:h os=Linux --build=missing") # Run the BUILD environment ext = "bat" if platform.system( ) == "Windows" else "sh" # TODO: Decide on logic .bat vs .sh cmd = environment_wrap_command(ConanFileMock(), "conanbuildenv", "mycmake.{}".format(ext), cwd=client.current_folder) client.run_command(cmd) assert "MYCMAKE=Windows!!" in client.out assert "MYOPENSSL=Windows!!" in client.out # Run the RUN environment cmd = environment_wrap_command( ConanFileMock(), "conanrunenv", "mygtest.{ext} && .{sep}myrunner.{ext}".format(ext=ext, sep=os.sep), cwd=client.current_folder) client.run_command(cmd) assert "MYGTEST=Linux!!" in client.out assert "MYGTESTVAR=MyGTestValueLinux!!" in client.out client.run("build .") assert "MYCMAKE=Windows!!" in client.out assert "MYOPENSSL=Windows!!" in client.out assert "MYGTEST=Linux!!" in client.out
def test_profile_buildenv(): client = TestClient() save(client.cache.new_config_path, "tools.env.virtualenv:auto_use=True") conanfile = textwrap.dedent("""\ import os, platform from conans import ConanFile class Pkg(ConanFile): def generate(self): if platform.system() == "Windows": self.buildenv.save_bat("pkgenv.bat") else: self.buildenv.save_sh("pkgenv.sh") os.chmod("pkgenv.sh", 0o777) """) # Some scripts in a random system folders, path adding to the profile [env] compiler_bat = "@echo off\necho MYCOMPILER!!\necho MYPATH=%PATH%" compiler_sh = "echo MYCOMPILER!!\necho MYPATH=$PATH" compiler2_bat = "@echo off\necho MYCOMPILER2!!\necho MYPATH2=%PATH%" compiler2_sh = "echo MYCOMPILER2!!\necho MYPATH2=$PATH" myprofile = textwrap.dedent(""" [buildenv] PATH+=(path){} mypkg*:PATH=! mypkg*:PATH+=(path){} """.format(os.path.join(client.current_folder, "compiler"), os.path.join(client.current_folder, "compiler2"))) client.save({ "conanfile.py": conanfile, "myprofile": myprofile, "compiler/mycompiler.bat": compiler_bat, "compiler/mycompiler.sh": compiler_sh, "compiler2/mycompiler.bat": compiler2_bat, "compiler2/mycompiler.sh": compiler2_sh }) os.chmod(os.path.join(client.current_folder, "compiler", "mycompiler.sh"), 0o777) os.chmod(os.path.join(client.current_folder, "compiler2", "mycompiler.sh"), 0o777) client.run("install . -pr=myprofile") # Run the BUILD environment ext = "bat" if platform.system( ) == "Windows" else "sh" # TODO: Decide on logic .bat vs .sh cmd = environment_wrap_command("conanbuildenv", "mycompiler.{}".format(ext), cwd=client.current_folder) client.run_command(cmd) assert "MYCOMPILER!!" in client.out assert "MYPATH=" in client.out # Now with pkg-specific env-var client.run("install . mypkg/1.0@ -pr=myprofile") client.run_command(cmd) assert "MYCOMPILER2!!" in client.out assert "MYPATH2=" in client.out
def command_env_wrapper(conanfile, command, envfiles, envfiles_folder): from conan.tools.env.environment import environment_wrap_command if platform.system() == "Windows" and conanfile.win_bash: # New, Conan 2.0 wrapped_cmd = _windows_bash_wrapper(conanfile, command, envfiles, envfiles_folder) else: wrapped_cmd = environment_wrap_command(envfiles, envfiles_folder, command) return wrapped_cmd
def build_windows_subsystem(profile, make_program): """ The AutotoolsDeps can be used also in pure Makefiles, if the makefiles follow the Autotools conventions """ # FIXME: cygwin in CI (my local machine works) seems broken for path with spaces client = TestClient(path_with_spaces=False) client.run("new hello/0.1 --template=cmake_lib") # TODO: Test Windows subsystems in CMake, at least msys is broken os.rename(os.path.join(client.current_folder, "test_package"), os.path.join(client.current_folder, "test_package2")) client.save({"profile": profile}) client.run("create . --profile=profile") main = gen_function_cpp(name="main", includes=["hello"], calls=["hello"]) makefile = gen_makefile(apps=["app"]) conanfile = textwrap.dedent(""" from conans import ConanFile from conan.tools.gnu import AutotoolsToolchain, Autotools, AutotoolsDeps class TestConan(ConanFile): requires = "hello/0.1" settings = "os", "compiler", "arch", "build_type" exports_sources = "Makefile" generators = "AutotoolsDeps", "AutotoolsToolchain" def build(self): autotools = Autotools(self) autotools.make() """) client.save({"app.cpp": main, "Makefile": makefile, "conanfile.py": conanfile, "profile": profile}, clean_first=True) client.run("install . --profile=profile") cmd = environment_wrap_command(["conanbuildenv", "conanautotoolstoolchain", "conanautotoolsdeps"], client.current_folder, make_program) client.run_command(cmd) client.run_command("app") # TODO: fill compiler version when ready check_exe_run(client.out, "main", "gcc", None, "Release", "x86_64", None) assert "hello/0.1: Hello World Release!" in client.out client.save({"app.cpp": gen_function_cpp(name="main", msg="main2", includes=["hello"], calls=["hello"])}) # Make sure it is newer t = time.time() + 1 touch(os.path.join(client.current_folder, "app.cpp"), (t, t)) client.run("build .") client.run_command("app") # TODO: fill compiler version when ready check_exe_run(client.out, "main2", "gcc", None, "Release", "x86_64", None, cxx11_abi=0) assert "hello/0.1: Hello World Release!" in client.out return client.out
def test_complete(client): conanfile = textwrap.dedent(""" import os from conans import ConanFile class Pkg(ConanFile): requires = "openssl/1.0" build_requires = "mycmake/1.0" apply_env = False def build_requirements(self): self.build_requires("mygtest/1.0", force_host_context=True) def build(self): self.run("mycmake.bat", env="conanbuildenv") assert os.path.exists(os.path.join(self.generators_folder, "conanrunenv.sh")) """) client.save({"conanfile.py": conanfile}) client.run("install . -s:b os=Windows -s:h os=Linux --build=missing") # Run the BUILD environment if platform.system() == "Windows": cmd = environment_wrap_command("conanbuildenv", "mycmake.bat", cwd=client.current_folder) client.run_command(cmd) assert "MYCMAKE=Windows!!" in client.out assert "MYOPENSSL=Windows!!" in client.out # Run the RUN environment if platform.system() != "Windows": cmd = environment_wrap_command("conanrunenv", "mygtest.sh && .{}myrunner.sh".format( os.sep), cwd=client.current_folder) client.run_command(cmd) assert "MYGTEST=Linux!!" in client.out assert "MYGTESTVAR=MyGTestValueLinux!!" in client.out if platform.system() == "Windows": client.run("build .") assert "MYCMAKE=Windows!!" in client.out assert "MYOPENSSL=Windows!!" in client.out
def _windows_bash_wrapper(conanfile, command, env, envfiles_folder): from conan.tools.env import Environment from conan.tools.env.environment import environment_wrap_command """ Will wrap a unix command inside a bash terminal It requires to have MSYS2, CYGWIN, or WSL""" subsystem = conanfile.conf.get("tools.microsoft.bash:subsystem") shell_path = conanfile.conf.get("tools.microsoft.bash:path") if not subsystem or not shell_path: raise ConanException("The config 'tools.microsoft.bash:subsystem' and " "'tools.microsoft.bash:path' are " "needed to run commands in a Windows subsystem") env = env or [] if subsystem == MSYS2: # Configure MSYS2 to inherith the PATH msys2_mode_env = Environment() _msystem = { "x86": "MINGW32" }.get(conanfile.settings.get_safe("arch"), "MINGW64") msys2_mode_env.define("MSYSTEM", _msystem) msys2_mode_env.define("MSYS2_PATH_TYPE", "inherit") path = os.path.join(conanfile.generators_folder, "msys2_mode.bat") msys2_mode_env.vars(conanfile, "build").save_bat(path) env.append(path) wrapped_shell = '"%s"' % shell_path if " " in shell_path else shell_path wrapped_shell = environment_wrap_command(env, envfiles_folder, wrapped_shell, accepted_extensions=("bat", "ps1")) # Wrapping the inside_command enable to prioritize our environment, otherwise /usr/bin go # first and there could be commands that we want to skip wrapped_user_cmd = environment_wrap_command(env, envfiles_folder, command, accepted_extensions=("sh", )) wrapped_user_cmd = _escape_windows_cmd(wrapped_user_cmd) final_command = '{} -c {}'.format(wrapped_shell, wrapped_user_cmd) return final_command
def _run(cmd, _env): # FIXME: run in windows bash is not using output if platform.system() == "Windows": if win_bash: return tools.run_in_windows_bash(self, bashcmd=cmd, cwd=cwd, subsystem=subsystem, msys_mingw=msys_mingw, with_login=with_login) elif self.win_bash: # New, Conan 2.0 from conan.tools.microsoft.subsystems import run_in_windows_bash return run_in_windows_bash(self, command=cmd, cwd=cwd, env=_env) if _env is None: _env = "conanbuild" from conan.tools.env.environment import environment_wrap_command wrapped_cmd = environment_wrap_command(_env, cmd, cwd=self.generators_folder) return self._conan_runner(wrapped_cmd, output, os.path.abspath(RUN_LOG_NAME), cwd)
def test_massive_paths(num_deps): """ This test proves that having too many dependencies that will result in a very long PATH env-var in the consumer by one VirtualXXXEnv environment, will overflow. https://github.com/conan-io/conan/issues/9565 This seems an unsolvable limitation, the only alternatives are: - shorten the paths in general (shorter cache paths) - add exclusively the paths of needed things (better visibility) Seems that Conan 2.0 will improve over these things, allowing larger dependencies graphs without failing. Besides that, it might use the deployers to workaround shared-libs running scenarios. The test is parameterized for being fast an passing, but if we add a num_deps >= 80 approx, it will start to enter the failing scenarios. Not adding the >=80 scenario, because that tests takes 1 minute by itself, not worth the value. """ client = TestClient(path_with_spaces=False) compiler_bat = "@echo off\necho MYTOOL {}!!\n" conanfile = textwrap.dedent("""\ from conans import ConanFile class Pkg(ConanFile): exports = "*" def package(self): self.copy("*", dst="bin") """) for i in range(num_deps): client.save({ "conanfile.py": conanfile, "mycompiler{}.bat".format(i): compiler_bat.format(i) }) client.run("create . pkg{}/0.1@".format(i)) conanfile = textwrap.dedent("""\ from conans import ConanFile class Pkg(ConanFile): settings = "os" requires = {} generators = "VirtualRunEnv" """) requires = ", ".join('"pkg{}/0.1"'.format(i) for i in range(num_deps)) conanfile = conanfile.format(requires) client.save({"conanfile.py": conanfile}, clean_first=True) client.run("install . -c tools.env.virtualenv:powershell=True") assert os.path.isfile( os.path.join(client.current_folder, "conanrunenv.ps1")) assert not os.path.isfile( os.path.join(client.current_folder, "conanrunenv.bat")) for i in range(num_deps): cmd = environment_wrap_command("conanrunenv", client.current_folder, "mycompiler{}.bat".format(i)) if num_deps > 50: # to be safe if we change the "num_deps" number client.run_command(cmd, assert_error=True) assert "is not recognized as an internal" in client.out else: client.run_command(cmd) assert "MYTOOL {}!!".format(i) in client.out # Test .bats now client.save({"conanfile.py": conanfile}, clean_first=True) client.run("install .") assert not os.path.isfile( os.path.join(client.current_folder, "conanrunenv.ps1")) assert os.path.isfile( os.path.join(client.current_folder, "conanrunenv.bat")) for i in range(num_deps): cmd = environment_wrap_command("conanrunenv", client.current_folder, "mycompiler{}.bat".format(i)) if num_deps > 50: # to be safe if we change the "num_deps" number client.run_command(cmd, assert_error=True) # This also fails, but without an error message (in my terminal, it kills the terminal!) else: client.run_command(cmd) assert "MYTOOL {}!!".format(i) in client.out