New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New AutotoolsDeps, AutotoolsToolchain helpers in conan.tools.gnu #8457
Changes from 23 commits
a841c9b
062c3f9
7330385
a92a2cb
08e7144
e41792c
03a4bbf
74e9198
3292aec
087f030
fd18ee9
b33cd92
cc5c8b3
9b997f8
cf97406
79675af
dfda73e
c6226b6
c2b7e63
36bf481
642b3fe
38b4dd3
96d0334
6fe2b2f
49cb4da
0b62989
d2de553
837ab8f
ccdd17c
4f3642c
14c308a
6d4ea51
ff0543e
bcd08d1
35ced48
c512f37
9ec6582
d5d238e
75ff512
99f3f91
3a585e5
5f323b1
16863a1
3fd4633
de6787e
7f86beb
49dfb47
7d98f08
7507507
f77726b
e4e280e
ab0375f
b4fbb64
99a3ae9
d3f065f
0fe4307
59800d8
b1de1f4
5aee77f
42c4dfc
fba1ede
e0e9c9c
58776e5
67d2275
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,6 @@ __pycache__/ | |
|
||
# Distribution / packaging | ||
.Python | ||
env/ | ||
build/ | ||
develop-eggs/ | ||
dist/ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from conan.tools.env.environment import Environment |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
import textwrap | ||
from collections import OrderedDict | ||
|
||
from conans.util.files import save | ||
|
||
PLACEHOLDER = "$CONANVARPLACEHOLDER%" | ||
|
||
|
||
class EnvironmentItem: | ||
|
||
def __init__(self, value=None, separator=None): | ||
self._value = value | ||
self._separator = separator | ||
|
||
def value(self, placeholder): | ||
value = [v if v != PLACEHOLDER else placeholder for v in self._value] | ||
value = self._separator.join(value) if value else "" | ||
return value | ||
|
||
def copy(self): | ||
return EnvironmentItem(self._value[:], self._separator) | ||
|
||
def define(self, value, separator=" "): | ||
self._value = value if isinstance(value, list) else [value] | ||
self._separator = separator | ||
|
||
def append(self, value, separator=" "): | ||
value = value if isinstance(value, list) else [value] | ||
self._value = [PLACEHOLDER] + value | ||
self._separator = separator | ||
|
||
def prepend(self, value, separator=" "): | ||
value = value if isinstance(value, list) else [value] | ||
self._value = value + [PLACEHOLDER] | ||
self._separator = separator | ||
|
||
def clean(self): | ||
self._value = [] | ||
self._separator = None | ||
|
||
def update(self, other): | ||
""" | ||
:type other: EnvironmentItem | ||
""" | ||
result = other._value | ||
try: | ||
index = result.index(PLACEHOLDER) | ||
result[index:index+1] = self._value | ||
assert self._separator == other._separator | ||
except ValueError: | ||
pass | ||
self._value = result | ||
self._separator = other._separator | ||
|
||
|
||
class Environment: | ||
def __init__(self): | ||
# It being ordered allows for Windows case-insensitive composition | ||
self._values = OrderedDict() | ||
|
||
def __getitem__(self, name): | ||
return self._values.setdefault(name, EnvironmentItem()) | ||
|
||
def save_bat(self, filename, generate_deactivate=True): | ||
deactivate = textwrap.dedent("""\ | ||
echo Capturing current environment in deactivate_{filename} | ||
setlocal | ||
echo @echo off > "deactivate_{filename}" | ||
echo echo Restoring environment >> "deactivate_{filename}" | ||
for %%v in ({vars}) do ( | ||
set foundenvvar= | ||
for /f "delims== tokens=1,2" %%a in ('set') do ( | ||
if "%%a" == "%%v" ( | ||
echo set %%a=%%b>> "deactivate_{filename}" | ||
set foundenvvar=1 | ||
) | ||
) | ||
if not defined foundenvvar ( | ||
echo set %%v=>> "deactivate_{filename}" | ||
) | ||
) | ||
endlocal | ||
|
||
""").format(filename=filename, vars=" ".join(self._values.keys())) | ||
capture = textwrap.dedent("""\ | ||
@echo off | ||
{deactivate} | ||
echo Configuring environment variables | ||
""").format(deactivate=deactivate if generate_deactivate else "") | ||
result = [capture] | ||
for k, v in self._values.items(): | ||
value = v.value("%{}%".format(k)) | ||
result.append('set {}={}'.format(k, value)) | ||
|
||
content = "\n".join(result) | ||
save(filename, content) | ||
|
||
def save_sh(self, filename): | ||
capture = textwrap.dedent("""\ | ||
echo Capturing current environment in deactivate_{filename} | ||
echo echo Restoring variables >> deactivate_{filename} | ||
for v in {vars} | ||
do | ||
value=${{!v}} | ||
if [ -n "$value" ] | ||
then | ||
echo export "$v=$value" >> deactivate_{filename} | ||
else | ||
echo unset $v >> deactivate_{filename} | ||
fi | ||
done | ||
echo Configuring environment variables | ||
""".format(filename=filename, vars=" ".join(self._values.keys()))) | ||
result = [capture] | ||
for k, v in self._values.items(): | ||
value = v.value("${}".format(k)) | ||
if value: | ||
result.append('export {}="{}"'.format(k, value)) | ||
else: | ||
result.append('unset {}'.format(k)) | ||
|
||
content = "\n".join(result) | ||
save(filename, content) | ||
|
||
def compose(self, other): | ||
""" | ||
:type other: Environment | ||
""" | ||
result = Environment() | ||
result._values = OrderedDict([(k, v.copy()) for k, v in self._values.items()]) | ||
for k, v in other._values.items(): | ||
v = v.copy() | ||
existing = result._values.get(k) | ||
if existing is None: | ||
result._values[k] = v | ||
else: | ||
existing.update(v) | ||
return result |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,4 @@ | ||
from .make import MakeToolchain | ||
from conan.tools.gnu.autotoolstoolchain import AutotoolsToolchain | ||
from conan.tools.gnu.autotoolsdeps import AutotoolsDeps | ||
from conan.tools.gnu.autotools import Autotools |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,239 @@ | ||||||
import copy | ||||||
import os | ||||||
import platform | ||||||
|
||||||
|
||||||
from conans.client import tools | ||||||
from conans.client.tools.oss import OSInfo, cross_building, \ | ||||||
detected_architecture, detected_os, get_gnu_triplet, get_target_os_arch, get_build_os_arch | ||||||
from conans.client.tools.win import unix_path | ||||||
from conans.errors import ConanException | ||||||
from conans.model.build_info import DEFAULT_BIN, DEFAULT_INCLUDE, DEFAULT_LIB, DEFAULT_SHARE | ||||||
from conans.util.files import get_abs_path | ||||||
|
||||||
|
||||||
class Autotools(object): | ||||||
|
||||||
def __init__(self, conanfile, win_bash=False, include_rpath_flags=False): | ||||||
""" | ||||||
FIXME: include_rpath_flags CONAN 2.0 to default True? Could break many packages in center | ||||||
""" | ||||||
self._conanfile = conanfile | ||||||
self._win_bash = win_bash | ||||||
self._include_rpath_flags = include_rpath_flags | ||||||
self.subsystem = OSInfo().detect_windows_subsystem() if self._win_bash else None | ||||||
self._os = conanfile.settings.get_safe("os") | ||||||
self._os_version = conanfile.settings.get_safe("os.version") | ||||||
self._os_sdk = conanfile.settings.get_safe("os.sdk") | ||||||
self._os_subsystem = conanfile.settings.get_safe("os.subsystem") | ||||||
self._arch = conanfile.settings.get_safe("arch") | ||||||
self._os_target, self._arch_target = get_target_os_arch(conanfile) | ||||||
self._build_type = conanfile.settings.get_safe("build_type") | ||||||
self._compiler = conanfile.settings.get_safe("compiler") | ||||||
self._compiler_version = conanfile.settings.get_safe("compiler.version") | ||||||
self._compiler_runtime = conanfile.settings.get_safe("compiler.runtime") | ||||||
|
||||||
# Precalculate build, host, target triplets | ||||||
self.build, self.host, self.target = self._get_host_build_target_flags() | ||||||
|
||||||
def _get_host_build_target_flags(self): | ||||||
"""Based on google search for build/host triplets, it could need a lot | ||||||
and complex verification""" | ||||||
|
||||||
if self._os_target and self._arch_target: | ||||||
try: | ||||||
target = get_gnu_triplet(self._os_target, self._arch_target, self._compiler) | ||||||
except ConanException as exc: | ||||||
self._conanfile.output.warn(str(exc)) | ||||||
target = None | ||||||
else: | ||||||
target = None | ||||||
|
||||||
if hasattr(self._conanfile, 'settings_build'): | ||||||
os_build, arch_build = get_build_os_arch(self._conanfile) | ||||||
else: | ||||||
# FIXME: Why not use 'os_build' and 'arch_build' from conanfile.settings? | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if it's new helper, can we avoid all these os_build/arch_build mess and just use modern two-profile approach? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, definitely, it is very ongoing work, I plan to refactor everything and use the 2 profiles. |
||||||
os_build = detected_os() or platform.system() | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd say, that may be going forward, avoid auto-detecting altogether and specify everything explicitly in profile/settings, to ensure it's reproducible? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 100% agree, there will be no auto-detection here, only deterministic from profiles. |
||||||
arch_build = detected_architecture() or platform.machine() | ||||||
|
||||||
if os_build is None or arch_build is None or self._arch is None or self._os is None: | ||||||
return False, False, target | ||||||
|
||||||
if not cross_building(self._conanfile, os_build, arch_build): | ||||||
return False, False, target | ||||||
|
||||||
try: | ||||||
build = get_gnu_triplet(os_build, arch_build, self._compiler) | ||||||
except ConanException as exc: | ||||||
self._conanfile.output.warn(str(exc)) | ||||||
build = None | ||||||
try: | ||||||
host = get_gnu_triplet(self._os, self._arch, self._compiler) | ||||||
except ConanException as exc: | ||||||
self._conanfile.output.warn(str(exc)) | ||||||
host = None | ||||||
return build, host, target | ||||||
|
||||||
def configure(self, configure_dir=None, args=None, build=None, host=None, target=None, | ||||||
pkg_config_paths=None, vars=None, use_default_install_dirs=True): | ||||||
""" | ||||||
http://jingfenghanmax.blogspot.com.es/2010/09/configure-with-host-target-and-build.html | ||||||
https://gcc.gnu.org/onlinedocs/gccint/Configure-Terms.html | ||||||
:param use_default_install_dirs: Use or not the defaulted installation dirs | ||||||
|
||||||
""" | ||||||
if not self._conanfile.should_configure: | ||||||
return | ||||||
if configure_dir: | ||||||
configure_dir = configure_dir.rstrip("/") | ||||||
else: | ||||||
configure_dir = "." | ||||||
""" | ||||||
triplet_args = [] | ||||||
|
||||||
if build is not False: # Skipped by user | ||||||
if build or self.build: # User specified value or automatic | ||||||
triplet_args.append("--build=%s" % (build or self.build)) | ||||||
|
||||||
if host is not False: # Skipped by user | ||||||
if host or self.host: # User specified value or automatic | ||||||
triplet_args.append("--host=%s" % (host or self.host)) | ||||||
|
||||||
if target is not False: # Skipped by user | ||||||
if target or self.target: # User specified value or automatic | ||||||
triplet_args.append("--target=%s" % (target or self.target)) | ||||||
|
||||||
if pkg_config_paths: | ||||||
pkg_env = {"PKG_CONFIG_PATH": | ||||||
[os.pathsep.join(get_abs_path(f, self._conanfile.install_folder) | ||||||
for f in pkg_config_paths)]} | ||||||
else: | ||||||
# If we are using pkg_config generator automate the pcs location, otherwise it could | ||||||
# read wrong files | ||||||
pkg_env = {"PKG_CONFIG_PATH": [self._conanfile.install_folder]} \ | ||||||
if "pkg_config" in self._conanfile.generators else None | ||||||
""" | ||||||
configure_dir = self._adjust_path(configure_dir) | ||||||
|
||||||
"""if self._conanfile.package_folder is not None: | ||||||
if not args: | ||||||
args = ["--prefix=%s" % self._conanfile.package_folder.replace("\\", "/")] | ||||||
elif not self._is_flag_in_args("prefix", args): | ||||||
args.append("--prefix=%s" % self._conanfile.package_folder.replace("\\", "/")) | ||||||
|
||||||
all_flags = ["bindir", "sbindir", "libexecdir", "libdir", "includedir", "oldincludedir", | ||||||
"datarootdir"] | ||||||
help_output = self._configure_help_output(configure_dir) | ||||||
available_flags = [flag for flag in all_flags if "--%s" % flag in help_output] | ||||||
|
||||||
if use_default_install_dirs: | ||||||
for varname in ["bindir", "sbindir", "libexecdir"]: | ||||||
if self._valid_configure_flag(varname, args, available_flags): | ||||||
args.append("--%s=${prefix}/%s" % (varname, DEFAULT_BIN)) | ||||||
if self._valid_configure_flag("libdir", args, available_flags): | ||||||
args.append("--libdir=${prefix}/%s" % DEFAULT_LIB) | ||||||
for varname in ["includedir", "oldincludedir"]: | ||||||
if self._valid_configure_flag(varname, args, available_flags): | ||||||
args.append("--%s=${prefix}/%s" % (varname, DEFAULT_INCLUDE)) | ||||||
if self._valid_configure_flag("datarootdir", args, available_flags): | ||||||
args.append("--datarootdir=${prefix}/%s" % DEFAULT_SHARE) | ||||||
|
||||||
with environment_append(pkg_env): | ||||||
with environment_append(vars or self.vars): | ||||||
command = '%s/configure %s %s' % (configure_dir, args_to_string(args), | ||||||
" ".join(triplet_args)) | ||||||
self._conanfile.output.info("Calling:\n > %s" % command) | ||||||
self._conanfile.run(command, win_bash=self._win_bash, subsystem=self.subsystem)""" | ||||||
|
||||||
cmd = "bash -c 'source autotoolsdeps.sh && source autotools.sh && %s/configure'" % configure_dir | ||||||
self._conanfile.output.info("Calling:\n > %s" % cmd) | ||||||
self._conanfile.run(cmd, win_bash=self._win_bash, subsystem=self.subsystem) | ||||||
|
||||||
def _configure_help_output(self, configure_path): | ||||||
from six import StringIO # Python 2 and 3 compatible | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for the new helper, I think we can avoid python 2 and six There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree, ongoing work. |
||||||
mybuf = StringIO() | ||||||
try: | ||||||
self._conanfile.run("%s/configure --help" % configure_path, win_bash=self._win_bash, | ||||||
output=mybuf) | ||||||
except ConanException as e: | ||||||
self._conanfile.output.warn("Error running `configure --help`: %s" % e) | ||||||
return "" | ||||||
return mybuf.getvalue() | ||||||
|
||||||
def _adjust_path(self, path): | ||||||
if self._win_bash: | ||||||
path = unix_path(path, path_flavor=self.subsystem) | ||||||
return '"%s"' % path if " " in path else path | ||||||
|
||||||
@staticmethod | ||||||
def _valid_configure_flag(varname, args, available_flags): | ||||||
return not AutoToolsBuildEnvironment._is_flag_in_args(varname, args) and \ | ||||||
varname in available_flags | ||||||
|
||||||
@staticmethod | ||||||
def _is_flag_in_args(varname, args): | ||||||
flag = "--%s=" % varname | ||||||
return any([flag in arg for arg in args]) | ||||||
|
||||||
def make(self, target=None): | ||||||
"""if not self._build_type: | ||||||
raise ConanException("build_type setting should be defined.") | ||||||
with environment_append(vars or self.vars): | ||||||
str_args = args_to_string(args) | ||||||
cpu_count_option = (("-j%s" % cpu_count(output=self._conanfile.output)) | ||||||
if ("-j" not in str_args and "nmake" not in make_program.lower()) | ||||||
else None) | ||||||
self._conanfile.run("%s" % join_arguments([make_program, target, str_args, | ||||||
cpu_count_option]), | ||||||
win_bash=self._win_bash, subsystem=self.subsystem)""" | ||||||
|
||||||
make_program = self._conanfile.conf["tools.gnu"].make_program | ||||||
if make_program is None: | ||||||
make_program = "mingw32-make" if platform.system() == "Windows" else "make" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also don't think it's always desired to enforce There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is probably a sane default in windows, the same as |
||||||
if platform.system() == "Windows": | ||||||
cmd = "autotoolsdeps.bat && autotools.bat && {}".format(make_program) | ||||||
else: | ||||||
cmd = "bash -c 'source autotoolsdeps.sh "\ | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we shouldn't hard-code bash here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unless it is the only way to make it work reproducibly. The autotoolsdeps.sh, etc are using bash. If we are supporting other shells, we need tests, I don't want to add things into the new codebase that cannot be tested. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the make command is not hard-coded and got from the config. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
I guess you are aware that many recipes in ConanCenter are doing Same as conan/conans/client/tools/oss.py Line 389 in 2ccc79c
The fact that they have configs, doesn't mean that they are not hardcoded in the codebase. Same I am doing here, we can discuss some defaults as we gain knowledge, but I am not introducing new hardcoded things. The new There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know there are recipes doing that, as I wrote some of them. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, agree, a helper would be the best. This helper can hardcode some defaults for some given configurations (settings, etc), and always have a But lets wait a bit and have a minimum set of build helpers, just the most common "AutotoolsXXX" ones, that work well and make recipes simpler for a majority of cases, before extracting new helpers like |
||||||
"&& source autotools.sh && {}'".format(make_program) | ||||||
self._conanfile.run(cmd, win_bash=self._win_bash, subsystem=self.subsystem) | ||||||
|
||||||
def install(self, args=""): | ||||||
if not self._conanfile.should_install: | ||||||
return | ||||||
self.make(target="install") | ||||||
|
||||||
def _get_vars(self): | ||||||
def append(*args): | ||||||
ret = [] | ||||||
for arg in args: | ||||||
if arg: | ||||||
if isinstance(arg, list): | ||||||
ret.extend(arg) | ||||||
else: | ||||||
ret.append(arg) | ||||||
return ret | ||||||
|
||||||
tmp_compilation_flags = copy.copy(self.flags) | ||||||
|
||||||
if tools.is_apple_os(self._os): | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it belongs here as well. let's keep all platform-specific code as minimal as possible, and isolated. the way we did it previously was proven to be fragile and doesn't scale well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree, this code is not used at the moment, needs to be worked on. |
||||||
concat = " ".join(tmp_compilation_flags) | ||||||
if os.environ.get("CFLAGS", None): | ||||||
concat += " " + os.environ.get("CFLAGS", None) | ||||||
if os.environ.get("CXXFLAGS", None): | ||||||
concat += " " + os.environ.get("CXXFLAGS", None) | ||||||
if self._os_version and "-version-min" not in concat and "-target" not in concat: | ||||||
tmp_compilation_flags.append(tools.apple_deployment_target_flag(self._os, | ||||||
self._os_version, | ||||||
self._os_sdk, | ||||||
self._os_subsystem, | ||||||
self._arch)) | ||||||
if "-isysroot" not in concat and platform.system() == "Darwin": | ||||||
tmp_compilation_flags.extend(["-isysroot", | ||||||
tools.XCRun(self._conanfile.settings).sdk_path]) | ||||||
if "-arch" not in concat and self._arch: | ||||||
tmp_compilation_flags.extend(["-arch", tools.to_apple_arch(self._arch)]) | ||||||
|
||||||
cxx_flags = append(tmp_compilation_flags, self.cxx_flags, self.cppstd_flag) | ||||||
c_flags = tmp_compilation_flags | ||||||
|
||||||
return ld_flags, cpp_flags, libs, cxx_flags, c_flags |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe this one deserves its own first class generator? and new Virtual*Env generators might be based on that one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not yet. This could be a first class model, like
self.env_info = Environment()
in the recipes. This is separated in another branch and will have a separate PR once it is stabilized.