Skip to content

Commit

Permalink
new win_bash mechanism (#9194)
Browse files Browse the repository at this point in the history
* some changes

* Environment Automanage

* Fixing tests

* Divided into VirtualRunEnv and VirtualBuildEnv

* Register environment and VCVARS

* VirtualRunEnv is by default generated but not activated. Scripts are stored as full paths in the list

* Renamed var

* Fix v2 cmake template

* Very prelimilar stuff

* Fixed win test

* unused arg

* Added test

* WIP but working. Missing tests

* Broken, extract save scripts

* refactor

* bug

* Refactor in progress

* Refactor, not tested in windows

* Mac fixes, go to win again

* Working on windows

* Self win_bash

* win bash test in progress

* Added test, fixed stuff

* partial review

* default true for auto activate

* No conanfile in values

* Fix import mock

* Fixed replacements config

* Review

* assign conanfile
  • Loading branch information
lasote authored Jul 6, 2021
1 parent 07e59c4 commit 5012375
Show file tree
Hide file tree
Showing 24 changed files with 533 additions and 121 deletions.
3 changes: 2 additions & 1 deletion conan/tools/cmake/toolchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
from conan.tools import CONAN_TOOLCHAIN_ARGS_FILE
from conan.tools._compilers import architecture_flag, use_win_mingw
from conan.tools.cmake.utils import is_multi_configuration, get_file_name
from conan.tools.microsoft.toolchain import write_conanvcvars, vs_ide_version
from conan.tools.microsoft.toolchain import write_conanvcvars
from conan.tools.microsoft.visual import vs_ide_version
from conans.errors import ConanException
from conans.util.files import load, save

Expand Down
107 changes: 66 additions & 41 deletions conan/tools/env/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,48 @@ class _EnvVarPlaceHolder:
pass


def environment_wrap_command(filename, cmd, cwd=None):
assert filename
filenames = [filename] if not isinstance(filename, list) else filename
def environment_wrap_command(conanfile, env_filenames, cmd, cwd=None):
from conan.tools.microsoft.subsystems import unix_path
assert env_filenames
filenames = [env_filenames] if not isinstance(env_filenames, list) else env_filenames
bats, shs = [], []

cwd = cwd or os.getcwd()

for f in filenames:
full_path = os.path.join(cwd, f) if cwd else f
if os.path.isfile("{}.bat".format(full_path)):
bats.append("{}.bat".format(full_path))
elif os.path.isfile("{}.sh".format(full_path)):
shs.append("{}.sh".format(full_path))
f = f if os.path.isabs(f) else os.path.join(cwd, f)
if f.lower().endswith(".sh"):
if os.path.isfile(f):
f = unix_path(conanfile, f)
shs.append(f)
elif f.lower().endswith(".bat"):
if os.path.isfile(f):
bats.append(f)
else: # Simple name like "conanrunenv"
path_bat = "{}.bat".format(f)
path_sh = "{}.sh".format(f)
if os.path.isfile(path_bat):
bats.append(path_bat)
elif os.path.isfile(path_sh):
path_sh = unix_path(conanfile, path_sh)
shs.append(path_sh)

if bats and shs:
raise ConanException("Cannot wrap command with different envs, {} - {}".format(bats, shs))

if bats:
command = " && ".join('"{}"'.format(b) for b in bats)
return "{} && {}".format(command, cmd)
launchers = " && ".join('"{}"'.format(b) for b in bats)
return '{} && {}'.format(launchers, cmd)
elif shs:
curdir = "./" if cwd is None else ""
command = " && ".join('. "{}{}"'.format(curdir, f) for f in shs)
return "{} && {}".format(command, cmd)
launchers = " && ".join('. "{}"'.format(f) for f in shs)
return '{} && {}'.format(launchers, cmd)
else:
return cmd


class _EnvValue:
def __init__(self, name, value=_EnvVarPlaceHolder, separator=" ", path=False):
def __init__(self, name, value=_EnvVarPlaceHolder, separator=" ",
path=False):
self._name = name
self._values = [] if value is None else value if isinstance(value, list) else [value]
self._path = path
Expand Down Expand Up @@ -80,7 +96,7 @@ def compose(self, other):
new_value[index:index + 1] = other._values # replace the placeholder
self._values = new_value

def get_str(self, placeholder, pathsep=os.pathsep):
def get_str(self, conanfile, placeholder, pathsep=os.pathsep):
"""
:param placeholder: a OS dependant string pattern of the previous env-var value like
$PATH, %PATH%, et
Expand All @@ -93,21 +109,26 @@ def get_str(self, placeholder, pathsep=os.pathsep):
if placeholder:
values.append(placeholder.format(name=self._name))
else:
if self._path:
from conan.tools.microsoft.subsystems import unix_path
v = unix_path(conanfile, v)
values.append(v)
if self._path:
pathsep = ":" if conanfile.win_bash else pathsep
return pathsep.join(values)

return self._sep.join(values)

def get_value(self, pathsep=os.pathsep):
def get_value(self, conanfile, pathsep=os.pathsep):
previous_value = os.getenv(self._name)
return self.get_str(previous_value, pathsep)
return self.get_str(conanfile, previous_value, pathsep)


class Environment:
def __init__(self):
# TODO: Maybe we need to pass conanfile to get the [conf]
def __init__(self, conanfile):
# It being ordered allows for Windows case-insensitive composition
self._values = OrderedDict() # {var_name: [] of values, including separators}
self._conanfile = conanfile

def __bool__(self):
return bool(self._values)
Expand Down Expand Up @@ -169,7 +190,7 @@ def save_bat(self, filename, generate_deactivate=False, pathsep=os.pathsep):
""").format(deactivate=deactivate if generate_deactivate else "")
result = [capture]
for varname, varvalues in self._values.items():
value = varvalues.get_str("%{name}%", pathsep)
value = varvalues.get_str(self._conanfile, "%{name}%", pathsep)
result.append('set {}={}'.format(varname, value))

content = "\n".join(result)
Expand All @@ -183,7 +204,7 @@ def save_ps1(self, filename, generate_deactivate=False, pathsep=os.pathsep):
""").format(deactivate=deactivate if generate_deactivate else "")
result = [capture]
for varname, varvalues in self._values.items():
value = varvalues.get_str("$env:{name}", pathsep)
value = varvalues.get_str(self._conanfile, "$env:{name}", pathsep)
result.append('$env:{}={}'.format(varname, value))

content = "\n".join(result)
Expand Down Expand Up @@ -211,7 +232,7 @@ def save_sh(self, filename, generate_deactivate=False, pathsep=os.pathsep):
""").format(deactivate=deactivate if generate_deactivate else "")
result = [capture]
for varname, varvalues in self._values.items():
value = varvalues.get_str("${name}", pathsep)
value = varvalues.get_str(self._conanfile, "${name}", pathsep)
if value:
result.append('export {}="{}"'.format(varname, value))
else:
Expand All @@ -220,6 +241,18 @@ def save_sh(self, filename, generate_deactivate=False, pathsep=os.pathsep):
content = "\n".join(result)
save(filename, content)

def save_script(self, name, auto_activate=True):
# FIXME: using platform is not ideal but settings might be incomplete
if platform.system() == "Windows" and not self._conanfile.win_bash:
path = os.path.join(self._conanfile.generators_folder, "{}.bat".format(name))
self.save_bat(path)
else:
path = os.path.join(self._conanfile.generators_folder, "{}.sh".format(name))
self.save_sh(path)

if auto_activate:
register_environment_script(self._conanfile, path)

def compose(self, other):
"""
self has precedence, the "other" will add/append if possible and not conflicting, but
Expand All @@ -232,23 +265,24 @@ def compose(self, other):
self._values[k] = v.copy()
else:
existing.compose(v)

return self

# Methods to user access to the environment object as a dict
def keys(self):
return self._values.keys()

def __getitem__(self, name):
return self._values[name].get_value()
return self._values[name].get_value(self._conanfile)

def get(self, name, default=None):
v = self._values.get(name)
if v is None:
return default
return v.get_value()
return v.get_value(self._conanfile)

def items(self):
return {k: v.get_value() for k, v in self._values.items()}.items()
return {k: v.get_value(self._conanfile) for k, v in self._values.items()}.items()

def var(self, name):
return self._values[name]
Expand Down Expand Up @@ -285,15 +319,19 @@ def __init__(self):
def __repr__(self):
return repr(self._environments)

def get_env(self, ref):
def get_env(self, conanfile, ref):
""" computes package-specific Environment
it is only called when conanfile.buildenv is called
the last one found in the profile file has top priority
"""
result = Environment()
result = Environment(conanfile)
for pattern, env in self._environments.items():
if pattern is None or fnmatch.fnmatch(str(ref), pattern):
result = env.compose(result)

# FIXME: Needed to assign _conanfile here too because in the env.compose returns env and it
# hasn't conanfile
result._conanfile = conanfile
return result

def compose(self, other):
Expand Down Expand Up @@ -326,7 +364,7 @@ def loads(text):
else:
pattern, name = None, pattern_name[0]

env = Environment()
env = Environment(conanfile=None)
if method == "unset":
env.unset(name)
else:
Expand All @@ -346,19 +384,6 @@ def loads(text):
return result


def save_script(conanfile, env, name, auto_activate):
# FIXME: using platform is not ideal but settings might be incomplete
if platform.system() == "Windows":
path = os.path.join(conanfile.generators_folder, "{}.bat".format(name))
env.save_bat(path)
else:
path = os.path.join(conanfile.generators_folder, "{}.sh".format(name))
env.save_sh(path)

if auto_activate:
register_environment_script(conanfile, path)


def register_environment_script(conanfile, path):
if path not in conanfile.environment_scripts:
conanfile.environment_scripts.append(path)
8 changes: 4 additions & 4 deletions conan/tools/env/virtualbuildenv.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from conan.tools.env import Environment
from conan.tools.env.environment import save_script
from conan.tools.env.virtualrunenv import runenv_from_cpp_info


Expand All @@ -17,7 +16,8 @@ def environment(self):
of build_requires defining information for consumers
"""
# FIXME: Cache value?
build_env = Environment()
build_env = Environment(self._conanfile)

# Top priority: profile
profile_env = self._conanfile.buildenv
build_env.compose(profile_env)
Expand All @@ -31,7 +31,7 @@ def environment(self):
if build_require.runenv_info:
build_env.compose(build_require.runenv_info)
# Then the implicit
build_env.compose(runenv_from_cpp_info(build_require.cpp_info))
build_env.compose(runenv_from_cpp_info(self._conanfile, build_require.cpp_info))

# Requires in host context can also bring some direct buildenv_info
for require in self._conanfile.dependencies.host.values():
Expand All @@ -43,4 +43,4 @@ def environment(self):
def generate(self, auto_activate=True):
build_env = self.environment()
if build_env: # Only if there is something defined
save_script(self._conanfile, build_env, "conanbuildenv", auto_activate=auto_activate)
build_env.save_script("conanbuildenv", auto_activate=auto_activate)
13 changes: 6 additions & 7 deletions conan/tools/env/virtualrunenv.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
from conan.tools.env import Environment
from conan.tools.env.environment import save_script


def runenv_from_cpp_info(cpp_info):
def runenv_from_cpp_info(conanfile, cpp_info):
""" return an Environment deducing the runtime information from a cpp_info
"""
dyn_runenv = Environment()
dyn_runenv = Environment(conanfile)
if cpp_info is None: # This happens when the dependency is a private one = BINARY_SKIP
return dyn_runenv
if cpp_info.bin_paths: # cpp_info.exes is not defined yet
Expand All @@ -31,7 +30,7 @@ def environment(self):
""" collects the runtime information from dependencies. For normal libraries should be
very occasional
"""
runenv = Environment()
runenv = Environment(self._conanfile)
# FIXME: Missing profile info
# FIXME: Cache value?

Expand All @@ -40,12 +39,12 @@ def environment(self):
for _, dep in list(host_req.items()) + list(test_req.items()):
if dep.runenv_info:
runenv.compose(dep.runenv_info)
runenv.compose(runenv_from_cpp_info(dep.cpp_info))
runenv.compose(runenv_from_cpp_info(self._conanfile, dep.cpp_info))


return runenv

def generate(self, auto_activate=False):
run_env = self.environment()
if run_env:
save_script(self._conanfile, run_env, "conanrunenv", auto_activate=auto_activate)

run_env.save_script("conanrunenv", auto_activate=auto_activate)
25 changes: 23 additions & 2 deletions conan/tools/gnu/autotools.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import json
import os
import platform

from conan.tools import CONAN_TOOLCHAIN_ARGS_FILE
from conan.tools._compilers import use_win_mingw
from conan.tools.gnu.make import make_jobs_cmd_line_arg
from conan.tools.microsoft import unix_path
from conans.client.build import join_arguments
from conans.util.files import load

Expand All @@ -28,14 +30,16 @@ def configure(self):
if not self._conanfile.should_configure:
return

cmd = "{}/configure {}".format(self._conanfile.source_folder, self._configure_args)
configure_cmd = "{}/configure".format(self._conanfile.source_folder)
configure_cmd = unix_path(self._conanfile, configure_cmd)
cmd = "{} {}".format(configure_cmd, self._configure_args)
self._conanfile.output.info("Calling:\n > %s" % cmd)
self._conanfile.run(cmd)

def make(self, target=None):
make_program = self._conanfile.conf["tools.gnu:make_program"]
if make_program is None:
make_program = "mingw32-make" if use_win_mingw(self._conanfile) else "make"
make_program = "mingw32-make" if self._use_win_mingw() else "make"

str_args = self._make_args
jobs = ""
Expand All @@ -48,3 +52,20 @@ def install(self):
if not self._conanfile.should_install:
return
self.make(target="install")

def _use_win_mingw(self):
if hasattr(self._conanfile, 'settings_build'):
os_build = self._conanfile.settings_build.get_safe('os')
else:
os_build = self._conanfile.settings.get_safe("os")

if os_build == "Windows":
compiler = self._conanfile.settings.get_safe("compiler")
sub = self._conanfile.settings.get_safe("os.subsystem")
if sub in ("cygwin", "msys2", "msys") or compiler == "qcc":
return False
else:
if self._conanfile.win_bash:
return False
return True
return False
5 changes: 2 additions & 3 deletions conan/tools/gnu/autotoolsdeps.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from conan.tools.env import Environment
from conan.tools.env.environment import save_script
from conan.tools.gnu.gnudeps_flags import GnuDepsFlags
from conans.model.new_build_info import NewCppInfo

Expand Down Expand Up @@ -51,7 +50,7 @@ def environment(self):
cxxflags.append(srf)
ldflags.append(srf)

env = Environment()
env = Environment(self._conanfile)
env.append("CPPFLAGS", cpp_flags)
env.append("LIBS", flags.libs)
env.append("LDFLAGS", ldflags)
Expand All @@ -61,4 +60,4 @@ def environment(self):

def generate(self, env=None, auto_activate=True):
env = env or self.environment()
save_script(self._conanfile, env, "conanautotoolsdeps", auto_activate=auto_activate)
env.save_script("conanautotoolsdeps", auto_activate=auto_activate)
Loading

0 comments on commit 5012375

Please sign in to comment.