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

proposal for source() buildenv discussion #15153

Merged
merged 15 commits into from
Dec 14, 2023
Merged
2 changes: 2 additions & 0 deletions conan/api/subapi/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ def source(self, path, name=None, version=None, user=None, channel=None, remotes
conanfile.folders.set_base_source(folder)
conanfile.folders.set_base_export_sources(folder)
conanfile.folders.set_base_recipe_metadata(os.path.join(folder, "metadata"))
# The generators are needed for the "conan source" local case with tool-requires
conanfile.folders.set_base_generators(folder)
conanfile.folders.set_base_build(None)
conanfile.folders.set_base_package(None)

Expand Down
28 changes: 17 additions & 11 deletions conan/tools/env/virtualbuildenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,27 @@ def __init__(self, conanfile):
self._conanfile = conanfile
self._conanfile.virtualbuildenv = False
self.basename = "conanbuildenv"
# TODO: Make this use the settings_build
self.configuration = conanfile.settings.get_safe("build_type")
if self.configuration:
self.configuration = self.configuration.lower()
self.arch = conanfile.settings.get_safe("arch")
if self.arch:
self.arch = self.arch.lower()
self.configuration = None
self.arch = None

@property
def _filename(self):
if not self.configuration:
AbrilRBS marked this conversation as resolved.
Show resolved Hide resolved
# TODO: Make this use the settings_build
configuration = self._conanfile.settings.get_safe("build_type")
configuration = configuration.lower() if configuration else None
else:
configuration = self.configuration
if not self.arch:
arch = self._conanfile.settings.get_safe("arch")
arch = arch.lower() if arch else None
else:
arch = self.arch
f = self.basename
if self.configuration:
f += "-" + self.configuration.replace(".", "_")
if self.arch:
f += "-" + self.arch.replace(".", "_")
if configuration:
f += "-" + configuration.replace(".", "_")
if arch:
f += "-" + arch.replace(".", "_")
return f

def environment(self):
Expand Down
10 changes: 8 additions & 2 deletions conans/client/source.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os

from conan.api.output import ConanOutput
from conan.tools.env import VirtualBuildEnv
from conans.errors import ConanException, conanfile_exception_formatter, NotFoundException, \
conanfile_remove_attr
from conans.util.files import (is_dirty, mkdir, rmdir, set_dirty_context_manager,
Expand Down Expand Up @@ -67,8 +68,13 @@ def config_source(export_source_folder, conanfile, hook_manager):
# First of all get the exported scm sources (if auto) or clone (if fixed)
# Now move the export-sources to the right location
merge_directories(export_source_folder, conanfile.folders.base_source)

run_source_method(conanfile, hook_manager)
if getattr(conanfile, "source_buildenv", True):
prev_status = conanfile.virtualbuildenv
with VirtualBuildEnv(conanfile).vars().apply():
run_source_method(conanfile, hook_manager)
conanfile.virtualbuildenv = prev_status
else:
run_source_method(conanfile, hook_manager)


def run_source_method(conanfile, hook_manager):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,8 @@ def test_tool_requires_revision_profile():
# We shoul be able to explicitly [tool_require] a recipe revision in the profile
c = TestClient()
build_profile = textwrap.dedent("""\
[settings]
os=Linux
[tool_requires]
*:tool/0.1#2d65f1b4af1ce59028f96adbfe7ed5a2
""")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import textwrap

import pytest

from conans.test.utils.tools import TestClient


class TestBuildEnvSource:
@pytest.fixture()
def client(self):
c = TestClient()
tool = textwrap.dedent(r"""
import os
from conan import ConanFile
from conan.tools.files import chdir, save

class Tool(ConanFile):
name = "tool"
version = "0.1"
def package(self):
with chdir(self, self.package_folder):
echo = f"@echo off\necho MY-TOOL! {self.name}/{self.version}!!"
save(self, "bin/mytool.bat", echo)
save(self, "bin/mytool.sh", echo)
os.chmod("bin/mytool.sh", 0o777)
""")
c.save({"conanfile.py": tool})
c.run("create .")
return c

def test_source_buildenv(self, client):
c = client
pkg = textwrap.dedent("""
from conan import ConanFile
import platform

class Pkg(ConanFile):
name = "pkg"
version = "0.1"
tool_requires = "tool/0.1"

def source(self):
cmd = "mytool.bat" if platform.system() == "Windows" else "mytool.sh"
self.run(cmd)
""")
c.save({"conanfile.py": pkg})
c.run("create .")
assert "MY-TOOL! tool/0.1" in c.out

c.run("install .") # to generate conanbuild script first, so it is available
c.run("source .")
assert "MY-TOOL! tool/0.1" in c.out

def test_source_buildenv_layout(self, client):
c = client
pkg = textwrap.dedent("""
from conan import ConanFile
import platform

class Pkg(ConanFile):
name = "pkg"
version = "0.1"
tool_requires = "tool/0.1"
settings = "build_type"

def layout(self):
self.folders.source = "mysrc"
bt = self.settings.get_safe("build_type") or "Release"
self.folders.generators = f"mybuild{bt}"

def source(self):
cmd = "mytool.bat" if platform.system() == "Windows" else "mytool.sh"
self.run(cmd)
""")
c.save({"conanfile.py": pkg})
c.run("create .")
assert "MY-TOOL! tool/0.1" in c.out

c.run("install .") # to generate conanbuild script first, so it is available
# But they are in a different folder, user can copy them to source folder to make
# them available to source() method. This works
# shutil.copytree(os.path.join(c.current_folder, "mybuild"),
# os.path.join(c.current_folder, "mysrc"))
# Another possibility is user directly calling the "conanbuild" script to activate
# The current solution defines "generators" to be robust for "conan source" command
# defaulting to "Release" config
c.run("source .")
assert "MY-TOOL! tool/0.1" in c.out

def test_source_buildenv_optout(self, client):
c = client

pkg = textwrap.dedent("""
from conan import ConanFile
import platform

class Pkg(ConanFile):
name = "pkg"
version = "0.1"
tool_requires = "tool/0.1"

source_buildenv = False

def source(self):
cmd = "mytool.bat" if platform.system() == "Windows" else "mytool.sh"
self.run(cmd)
""")
c.save({"conanfile.py": pkg})
c.run("create .", assert_error=True)
assert "ERROR: pkg/0.1: Error in source() method, line 14" in c.out

# Local will still work, because ``install`` generates env-scripts and no layout
c.run("install .")
c.run("source .")
assert "MY-TOOL! tool/0.1" in c.out


"""
ALTERNATIVE 1: in recipe, explicit
def source(self):
cmd = "mytool.bat" if platform.system() == "Windows" else "mytool.sh"
with VirtualBuildEnv(conanfile).vars().apply():
self.run(cmd)

- Need to bypass ``settings`` restriction in ``source()``
- we can make a nice helper ``with buildenv``
"""

"""
ALTERNATIVE 2: in recipe, attribute
inject_source_buildenv = True

def source(self):
cmd = "mytool.bat" if platform.system() == "Windows" else "mytool.sh"
self.run(cmd)

- No need to bypass ``settings`` restriction in ``source()``
"""

"""
ALTERNATIVE 3: always, this should be the expected default behavior

def source(self):
cmd = "mytool.bat" if platform.system() == "Windows" else "mytool.sh"
self.run(cmd)

- No need to bypass ``settings`` restriction in ``source()``
- We might want to do an opt-out, just in case?
"""

"""
ALTERNATIVE 4: global, controlled by conf

def source(self):
cmd = "mytool.bat" if platform.system() == "Windows" else "mytool.sh"
self.run(cmd)

- No need to bypass ``settings`` restriction in ``source()``
- [conf] core.source:inject_buildenv=True/False
"""