Skip to content

Commit

Permalink
Basic profiles (conan-io#554)
Browse files Browse the repository at this point in the history
* Basic profiles

* Almost done

* Tests passing

* Fixed test for windows

* Sorted env variables

* Macos env test

* Some fixes

* Added profile to build

* Remove nargs ?
  • Loading branch information
lasote authored and memsharded committed Oct 18, 2016
1 parent 63f7364 commit 4a8c458
Show file tree
Hide file tree
Showing 8 changed files with 366 additions and 7 deletions.
21 changes: 20 additions & 1 deletion conans/client/client_cache.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import os
from conans.util.files import save, load, relative_dirs, path_exists
from conans.util.files import save, load, relative_dirs, path_exists, mkdir
from conans.model.settings import Settings
from conans.client.conf import ConanClientConfigParser, default_client_conf, default_settings_yml
from conans.model.values import Values
Expand All @@ -8,11 +8,13 @@
from conans.model.manifest import FileTreeManifest
from conans.paths import SimplePaths
from genericpath import isdir
from conans.model.profile import Profile

CONAN_CONF = 'conan.conf'
CONAN_SETTINGS = "settings.yml"
LOCALDB = ".conan.db"
REGISTRY = "registry.txt"
PROFILES_FOLDER = "profiles"


class ClientCache(SimplePaths):
Expand Down Expand Up @@ -56,6 +58,10 @@ def localdb(self):
def conan_conf_path(self):
return os.path.join(self.conan_folder, CONAN_CONF)

@property
def profiles_path(self):
return os.path.join(self.conan_folder, PROFILES_FOLDER)

@property
def settings_path(self):
return os.path.join(self.conan_folder, CONAN_SETTINGS)
Expand All @@ -65,6 +71,7 @@ def settings(self):
"""Returns {setting: [value, ...]} defining all the possible
settings and their values"""
if not self._settings:
# TODO: Read default environment settings
if not os.path.exists(self.settings_path):
save(self.settings_path, default_settings_yml)
settings = Settings.loads(default_settings_yml)
Expand Down Expand Up @@ -140,3 +147,15 @@ def delete_empty_dirs(self, deleted_refs):
except OSError:
break # not empty
ref_path = os.path.dirname(ref_path)

def profile_path(self, name):
if not os.path.exists(self.profiles_path):
mkdir(self.profiles_path)
return os.path.join(self.profiles_path, name)

def load_profile(self, name):
text = load(self.profile_path(name))
return Profile.loads(text)

def current_profiles(self):
return [name for name in os.listdir(self.profiles_path) if not os.path.isdir(name)]
7 changes: 5 additions & 2 deletions conans/client/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ def install(self, *args):
help="update with new upstream packages")
parser.add_argument("--scope", "-sc", nargs=1, action=Extender,
help='Define scopes for packages')
parser.add_argument("--profile", "-pr", default=None, help='Define a profile')
parser.add_argument("--generator", "-g", nargs=1, action=Extender,
help='Generators to use')
parser.add_argument("--werror", action='store_true', default=False,
Expand Down Expand Up @@ -376,7 +377,8 @@ def install(self, *args):
manifest_verify=manifest_verify,
manifest_interactive=manifest_interactive,
scopes=scopes,
generators=args.generator)
generators=args.generator,
profile_name=args.profile)

def info(self, *args):
""" Prints information about the requirements.
Expand Down Expand Up @@ -436,13 +438,14 @@ def build(self, *args):
help='path to user conanfile.py, e.g., conan build .',
default="")
parser.add_argument("--file", "-f", help="specify conanfile filename")
parser.add_argument("--profile", "-pr", default=None, help='Define a profile')
args = parser.parse_args(*args)
current_path = os.getcwd()
if args.path:
root_path = os.path.abspath(args.path)
else:
root_path = current_path
self._manager.build(root_path, current_path, filename=args.file)
self._manager.build(root_path, current_path, filename=args.file, profile_name=args.profile)

def package(self, *args):
""" calls your conanfile.py "package" method for a specific package or
Expand Down
55 changes: 51 additions & 4 deletions conans/client/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from conans.client.source import config_source
from conans.client.manifest_manager import ManifestManager
from conans.model.env_info import EnvInfo, DepsEnvInfo
from conans.tools import environment_append


def get_user_channel(text):
Expand Down Expand Up @@ -61,8 +62,10 @@ def __init__(self, client_cache, user_io, runner, remote_manager, search_manager

def _loader(self, current_path=None, user_settings_values=None, user_options_values=None,
scopes=None):

# The disk settings definition, already including the default disk values
settings = self._client_cache.settings

options = OptionsValues()
conaninfo_scopes = Scopes()

Expand Down Expand Up @@ -210,16 +213,47 @@ def info(self, reference, current_path, remote=None, options=None, settings=None
info, registry, graph_updates_info,
remote)

def _read_profile(self, profile_name):
if profile_name:
try:
profile = self._client_cache.load_profile(profile_name)
return profile
except Exception:
current_profiles = ", ".join(self._client_cache.current_profiles()) or "[]"
raise ConanException("Specified profile '%s' doesn't exist.\nExisting profiles: "
"%s" % (profile_name, current_profiles))
return None

def _mix_settings_and_profile(self, settings, profile):
'''Mix the specified settings with the specified profile.
Specified settings are prioritized to profile'''
if profile:
profile.update_settings(dict(settings))
return profile.settings.items()
return settings

def _mix_scopes_and_profile(self, scopes, profile):
if profile:
profile.update_scopes(scopes)
return profile.scopes
return scopes

def _read_profile_env_vars(self, profile):
if profile:
return profile.env
return {}

def install(self, reference, current_path, remote=None, options=None, settings=None,
build_mode=False, filename=None, update=False, check_updates=False,
manifest_folder=None, manifest_verify=False, manifest_interactive=False,
scopes=None, generators=None):
scopes=None, generators=None, profile_name=None):
""" Fetch and build all dependencies for the given reference
@param reference: ConanFileReference or path to user space conanfile
@param current_path: where the output files will be saved
@param remote: install only from that remote
@param options: list of tuples: [(optionname, optionvalue), (optionname, optionvalue)...]
@param settings: list of tuples: [(settingname, settingvalue), (settingname, value)...]
@param profile: name of the profile to use
"""
generators = generators or []

Expand All @@ -231,6 +265,11 @@ def install(self, reference, current_path, remote=None, options=None, settings=N
else:
manifest_manager = None

profile = self._read_profile(profile_name)
settings = self._mix_settings_and_profile(settings, profile)
scopes = self._mix_scopes_and_profile(scopes, profile)
env_vars = self._read_profile_env_vars(profile)

objects = self._get_graph(reference, current_path, remote, options, settings, filename,
update, check_updates, manifest_manager, scopes)
(_, deps_graph, _, registry, conanfile, remote_proxy, loader) = objects
Expand All @@ -251,7 +290,10 @@ def install(self, reference, current_path, remote=None, options=None, settings=N
pass

installer = ConanInstaller(self._client_cache, self._user_io, remote_proxy)
installer.install(deps_graph, build_mode)

# Append env_vars to execution environment and clear when block code ends
with environment_append(env_vars):
installer.install(deps_graph, build_mode)

prefix = "PROJECT" if not isinstance(reference, ConanFileReference) else str(reference)
output = ScopedOutput(prefix, self._user_io.out)
Expand Down Expand Up @@ -323,7 +365,7 @@ def package(self, reference, package_id):
rmdir(package_folder)
packager.create_package(conanfile, build_folder, package_folder, output)

def build(self, conanfile_path, current_path, test=False, filename=None):
def build(self, conanfile_path, current_path, test=False, filename=None, profile_name=None):
""" Call to build() method saved on the conanfile.py
param conanfile_path: the original source directory of the user containing a
conanfile.py
Expand Down Expand Up @@ -365,7 +407,12 @@ def build(self, conanfile_path, current_path, test=False, filename=None):

os.chdir(current_path)
conan_file._conanfile_directory = conanfile_path
conan_file.build()
# Append env_vars to execution environment and clear when block code ends
profile = self._read_profile(profile_name)
env_vars = self._read_profile_env_vars(profile)
with environment_append(env_vars):
conan_file.build()

if test:
conan_file.test()
except ConanException:
Expand Down
88 changes: 88 additions & 0 deletions conans/model/profile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import copy
from collections import OrderedDict
from conans.util.config_parser import ConfigParser
from conans.model.scope import Scopes, _root


class Profile(object):
'''A profile contains a set of setting (with values), environment variables
and scopes'''

def __init__(self):
# Sections
self.settings = OrderedDict()
self.env = OrderedDict()
self.scopes = Scopes()

@staticmethod
def loads(text):
obj = Profile()
doc = ConfigParser(text, allowed_fields=["settings", "env", "scopes"])

for setting in doc.settings.split("\n"):
if setting:
name, value = setting.split("=")
obj.settings[name] = value

if doc.scopes:
obj.scopes = Scopes.from_list(doc.scopes.split("\n"))

for env in doc.env.split("\n"):
if env:
varname, value = env.split("=")
obj.env[varname] = value

obj._order()
return obj

def dumps(self):
self._order() # gets in order the settings

result = ["[settings]"]
for name, value in self.settings.items():
result.append("%s=%s" % (name, value))

result.append("[scopes]")
if self.scopes[_root].get("dev", None):
# FIXME: Ugly _root import
del self.scopes[_root]["dev"] # Do not include dev
scopes_txt = self.scopes.dumps()
result.append(scopes_txt)

result.append("[env]")
for name, value in self.env.items():
result.append("%s=%s" % (name, value))

return "\n".join(result).replace("\n\n", "\n")

def update_settings(self, new_settings):
'''Mix the specified settings with the current profile.
Specified settings are prioritized to profile'''
# apply the current profile
if new_settings:
self.settings.update(new_settings)
self._order()

def update_scopes(self, new_scopes):
'''Mix the specified settings with the current profile.
Specified settings are prioritized to profile'''
# apply the current profile
if new_scopes:
self.scopes.update(new_scopes)
self._order()

def _order(self):
tmp_settings = copy.copy(self.settings)
self.settings = OrderedDict()
# Insert in a good order
for func in [lambda x: "." not in x, # First the principal settings
lambda x: "." in x]:
for name, value in tmp_settings.items():
if func(name):
self.settings[name] = value

tmp_env = copy.copy(self.env)
self.env = OrderedDict()
for ordered_key in sorted(tmp_env):
self.env[ordered_key] = tmp_env[ordered_key]

Loading

0 comments on commit 4a8c458

Please sign in to comment.