Skip to content
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

Profile include order #6398

Merged
merged 2 commits into from Jan 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
65 changes: 34 additions & 31 deletions conans/client/profile_loader.py
Expand Up @@ -14,6 +14,11 @@
class ProfileParser(object):

def __init__(self, text):
""" divides the text in 3 items:
- self.vars: Dictionary with variable=value declarations
- self.includes: List of other profiles to include
- self.profile_text: the remaining, containing settings, options, env, etc
"""
self.vars = OrderedDict() # Order matters, if user declares F=1 and then FOO=12,
# and in profile MYVAR=$FOO, it will
self.includes = []
Expand All @@ -39,33 +44,36 @@ def __init__(self, text):
value = unquote(value)
self.vars[name] = value

def apply_vars(self, repl_vars):
self.vars = self._apply_in_vars(repl_vars)
self.includes = self._apply_in_includes(repl_vars)
self.profile_text = self._apply_in_profile_text(repl_vars)
def apply_vars(self):
self._apply_in_vars()
self._apply_in_profile_text()

def get_includes(self):
# Replace over includes seems insane and it is not documented. I am leaving it now
# afraid of breaking, but should be removed Conan 2.0
for include in self.includes:
for repl_key, repl_value in self.vars.items():
include = include.replace("$%s" % repl_key, repl_value)
yield include

def update_vars(self, included_vars):
""" update the variables dict with new ones from included profiles,
but keeping (higher priority) existing values"""
included_vars.update(self.vars)
self.vars = included_vars

def _apply_in_vars(self, repl_vars):
def _apply_in_vars(self):
tmp_vars = OrderedDict()
for key, value in self.vars.items():
for repl_key, repl_value in repl_vars.items():
for repl_key, repl_value in self.vars.items():
key = key.replace("$%s" % repl_key, repl_value)
value = value.replace("$%s" % repl_key, repl_value)
tmp_vars[key] = value
return tmp_vars
self.vars = tmp_vars

def _apply_in_includes(self, repl_vars):
tmp_includes = []
for include in self.includes:
for repl_key, repl_value in repl_vars.items():
include = include.replace("$%s" % repl_key, repl_value)
tmp_includes.append(include)
return tmp_includes

def _apply_in_profile_text(self, repl_vars):
tmp_text = self.profile_text
for repl_key, repl_value in repl_vars.items():
tmp_text = tmp_text.replace("$%s" % repl_key, repl_value)
return tmp_text
def _apply_in_profile_text(self):
for k, v in self.vars.items():
self.profile_text = self.profile_text.replace("$%s" % k, v)


def get_profile_path(profile_name, default_folder, cwd, exists=True):
Expand Down Expand Up @@ -114,27 +122,25 @@ def _load_profile(text, profile_path, default_folder):
""" Parse and return a Profile object from a text config like representation.
cwd is needed to be able to load the includes
"""

try:
inherited_profile = Profile()
cwd = os.path.dirname(os.path.abspath(profile_path)) if profile_path else None
profile_parser = ProfileParser(text)
inherited_vars = profile_parser.vars
# Iterate the includes and call recursive to get the profile and variables
# from parent profiles
for include in profile_parser.includes:
for include in profile_parser.get_includes():
# Recursion !!
profile, declared_vars = read_profile(include, cwd, default_folder)
profile, included_vars = read_profile(include, cwd, default_folder)
inherited_profile.update(profile)
inherited_vars.update(declared_vars)
profile_parser.update_vars(included_vars)

# Apply the automatic PROFILE_DIR variable
if cwd:
inherited_vars["PROFILE_DIR"] = os.path.abspath(cwd).replace('\\', '/')
profile_parser.vars["PROFILE_DIR"] = os.path.abspath(cwd).replace('\\', '/')
# Allows PYTHONPATH=$PROFILE_DIR/pythontools

# Replace the variables from parents in the current profile
profile_parser.apply_vars(inherited_vars)
profile_parser.apply_vars()

# Current profile before update with parents (but parent variables already applied)
doc = ConfigParser(profile_parser.profile_text,
Expand All @@ -144,10 +150,7 @@ def _load_profile(text, profile_path, default_folder):
# Merge the inherited profile with the readed from current profile
_apply_inner_profile(doc, inherited_profile)

# Return the inherited vars to apply them in the parent profile if exists
inherited_vars.update(profile_parser.vars)
return inherited_profile, inherited_vars

return inherited_profile, profile_parser.vars
except ConanException:
raise
except Exception as exc:
Expand Down
46 changes: 25 additions & 21 deletions conans/test/unittests/client/profile_loader/profile_loader_test.py
@@ -1,4 +1,5 @@
import os
import textwrap
import unittest

import six
Expand Down Expand Up @@ -55,31 +56,16 @@ def test_parser(self):
os=$OTHERVAR
"""
a = ProfileParser(txt)
a.apply_vars({"REPLACE_VAR": "22", "FILE": "MyFile", "OTHERVAR": "thing"})
self.assertEqual(a.vars, {"VAR": "22", "OTHERVAR": "thing"})
self.assertEqual(a.includes, ["a/path/to\profile.txt", "other/path/to/MyFile"])
a.update_vars({"REPLACE_VAR": "22", "FILE": "MyFile", "OTHERVAR": "thing"})
a.apply_vars()
self.assertEqual(a.vars, {"VAR": "22", "OTHERVAR": "thing",
"REPLACE_VAR": "22", "FILE": "MyFile",})
self.assertEqual([i for i in a.get_includes()],
["a/path/to\profile.txt", "other/path/to/MyFile"])
self.assertEqual(a.profile_text, """[settings]
os=thing""")


conanfile_scope_env = """
import platform
from conans import ConanFile

class AConan(ConanFile):
name = "Hello0"
version = "0.1"
settings = "os", "compiler", "arch"

def build(self):
# Print environment vars
if self.settings.os == "Windows":
self.run("SET")
else:
self.run("env")
"""


class ProfileTest(unittest.TestCase):

def profile_loads_test(self):
Expand Down Expand Up @@ -340,3 +326,21 @@ def assert_path(profile):

profile, _ = read_profile("Myprofile.txt", None, tmp)
assert_path(profile)

def include_order_test(self):
tmp = temp_folder()

save(os.path.join(tmp, "profile1.txt"), "MYVAR=fromProfile1")

profile2 = textwrap.dedent("""
include(./profile1.txt)
MYVAR=fromProfile2
[settings]
os=$MYVAR
""")
save(os.path.join(tmp, "profile2.txt"), profile2)
profile, variables = read_profile("./profile2.txt", tmp, None)

self.assertEqual(variables, {"MYVAR": "fromProfile2",
"PROFILE_DIR": tmp.replace('\\', '/')})
self.assertEqual(profile.settings["os"], "fromProfile2")