diff --git a/conan/tools/premake/__init__.py b/conan/tools/premake/__init__.py index 339d38a6b18..c51356c5995 100644 --- a/conan/tools/premake/__init__.py +++ b/conan/tools/premake/__init__.py @@ -1,2 +1,3 @@ from conan.tools.premake.premake import Premake -from conan.tools.premake.premakedeps import PremakeDeps \ No newline at end of file +from conan.tools.premake.premakedeps import PremakeDeps +from conan.tools.premake.toolchain import PremakeToolchain diff --git a/conan/tools/premake/premake.py b/conan/tools/premake/premake.py index 69765dd3c56..ea63b25ebbe 100644 --- a/conan/tools/premake/premake.py +++ b/conan/tools/premake/premake.py @@ -1,10 +1,19 @@ +import textwrap +from pathlib import Path + +from jinja2 import Template + +from conan.tools.files import save +from conan.tools.microsoft.msbuild import MSBuild +from conan.tools.premake.toolchain import PremakeToolchain + # Source: https://learn.microsoft.com/en-us/cpp/overview/compiler-versions?view=msvc-170 PREMAKE_VS_VERSION = { - '190': '2015', - '191': '2017', - '192': '2019', - '193': '2022', - '194': '2022', # still 2022 + "190": "2015", + "191": "2017", + "192": "2019", + "193": "2022", + "194": "2022", # still 2022 } @@ -13,28 +22,67 @@ class Premake: Premake cli wrapper """ + filename = "conanfile.premake5.lua" + + # Conan premake file which will preconfigure toolchain and then will call the user's premake file + _premake_file_template = textwrap.dedent( + """\ + #!lua + include("{{luafile}}") + {% if has_conan_toolchain %} + include("conantoolchain.premake5.lua") + {% endif %} + """ + ) + def __init__(self, conanfile): self._conanfile = conanfile - - self.action = None # premake5 action to use (Autogenerated) - self.luafile = 'premake5.lua' # Path to the root (premake5) lua file + # Path to the root (premake5) lua file + self.luafile = Path(self._conanfile.source_folder) / "premake5.lua" # (key value pairs. Will translate to "--{key}={value}") - self.arguments = {} # https://premake.github.io/docs/Command-Line-Arguments/ + self.arguments = {} # https://premake.github.io/docs/Command-Line-Arguments/ + self.arguments["scripts"] = self._conanfile.generators_folder + if self._conanfile.settings.get_safe("arch"): + self.arguments["arch"] = self._conanfile.settings.arch if "msvc" in self._conanfile.settings.compiler: - msvc_version = PREMAKE_VS_VERSION.get(str(self._conanfile.settings.compiler.version)) - self.action = f'vs{msvc_version}' + msvc_version = PREMAKE_VS_VERSION.get( + str(self._conanfile.settings.compiler.version) + ) + self.action = f"vs{msvc_version}" else: - self.action = 'gmake2' + self.action = "gmake" @staticmethod def _expand_args(args): - return ' '.join([f'--{key}={value}' for key, value in args.items()]) + return " ".join([f"--{key}={value}" for key, value in args.items()]) def configure(self): + has_conan_toolchain = ( + Path(self._conanfile.generators_folder) / PremakeToolchain.filename + ).exists() + content = Template(self._premake_file_template).render( + has_conan_toolchain=has_conan_toolchain, luafile=self.luafile + ) + + conan_luafile = Path(self._conanfile.build_folder) / self.filename + save(self._conanfile, conan_luafile, content) + premake_options = dict() - premake_options["file"] = self.luafile + premake_options["file"] = conan_luafile - premake_command = f'premake5 {self._expand_args(premake_options)} {self.action} ' \ - f'{self._expand_args(self.arguments)}' + premake_command = ( + f"premake5 {self._expand_args(premake_options)} {self.action} " + f"{self._expand_args(self.arguments)}" + ) self._conanfile.run(premake_command) + + + def build(self): + if self.action.startswith("vs"): + msbuild = MSBuild(self) + # TODO determine the generated solution name from premake build script + # msbuild.build(sln=".sln") + else: + build_type = str(self._conanfile.settings.build_type) + self._conanfile.run(f"make config={build_type.lower()} -j") diff --git a/conan/tools/premake/toolchain.py b/conan/tools/premake/toolchain.py new file mode 100644 index 00000000000..04ea4e1bcbe --- /dev/null +++ b/conan/tools/premake/toolchain.py @@ -0,0 +1,75 @@ +import os +import textwrap + +from jinja2 import Template + +from conan.tools.files import save + + +class PremakeToolchain: + """ + PremakeToolchain generator + """ + + filename = "conantoolchain.premake5.lua" + + _premake_file_template = textwrap.dedent( + """\ + #!lua + include("conandeps.premake5.lua") + + + workspace "{{workspace}}" + premake.api.addAliases("architecture", { + ["armv8"] = "arm64" + }) + + {% if cppstd %} + cppdialect "{{cppstd}}" + {% endif %} + {% if cstd %} + cdialect "{{cstd}}" + {% endif %} + location "{{ build_folder }}" + targetdir "{{ build_folder }}" + conan_setup() + + {% if variables %} + defines { {{variables}} } + {% endif %} + """ + ) + + def __init__(self, conanfile, workspace="*"): + # '*' is the global workspace + self._conanfile = conanfile + self.workspace = workspace + # TODO: not possible to overwrite upstream defines yet + self.defines = {} + + def generate(self): + cppstd = self._conanfile.settings.get_safe("compiler.cppstd") + cstd = self._conanfile.settings.get_safe("compiler.cstd") + if cppstd.startswith("gnu"): + cppstd = f"gnu++{cppstd[3:]}" + + formated_variables = "" + for key, value in self.defines.items(): + if isinstance(value, bool): + value = 1 if value else 0 + formated_variables += f'"{key}={value}", ' + + content = Template( + self._premake_file_template, trim_blocks=True, lstrip_blocks=True + ).render( + workspace=self.workspace, + build_folder=self._conanfile.build_folder, + cppstd=cppstd, + cstd=cstd, + variables=formated_variables, + ) + save( + self, + os.path.join(self._conanfile.generators_folder, self.filename), + content, + ) diff --git a/test/integration/toolchains/premake/test_premake.py b/test/integration/toolchains/premake/test_premake.py index a7d8bce37ad..a991e47117c 100644 --- a/test/integration/toolchains/premake/test_premake.py +++ b/test/integration/toolchains/premake/test_premake.py @@ -22,3 +22,25 @@ def build(self): c.save({"conanfile.py": conanfile}) c.run("build . -s compiler=msvc -s compiler.version=193 -s compiler.runtime=dynamic") assert "conanfile.py: Running premake5 --file=myproject.lua vs2022 --myarg=myvalue!!" in c.out + + + +def test_premake_compile(): + c = TestClient() + conanfile = textwrap.dedent(""" + from conan import ConanFile + from conan.tools.premake import Premake + + class Pkg(ConanFile): + settings = "os", "compiler", "build_type", "arch" + def run(self, cmd, *args, **kwargs): + self.output.info(f"Running {cmd}!!") + def build(self): + premake = Premake(self) + premake.luafile = "myproject.lua" + premake.arguments = {"myarg": "myvalue"} + premake.configure() + """) + c.save({"conanfile.py": conanfile}) + c.run("build . -s compiler=msvc -s compiler.version=193 -s compiler.runtime=dynamic") + assert "conanfile.py: Running premake5 --file=myproject.lua vs2022 --myarg=myvalue!!" in c.out diff --git a/test/integration/toolchains/premake/test_premakedeps.py b/test/integration/toolchains/premake/test_premakedeps.py index 5beafc197dd..7c7bd60f3ca 100644 --- a/test/integration/toolchains/premake/test_premakedeps.py +++ b/test/integration/toolchains/premake/test_premakedeps.py @@ -1,6 +1,9 @@ import textwrap +from conan.test.utils.mocks import ConanFileMock from conan.test.utils.tools import TestClient +from conan.tools.env.environment import environment_wrap_command +from conan.test.assets.genconanfile import GenConanfile def assert_vars_file(client, configuration): @@ -62,3 +65,114 @@ def package_info(self): # Assert package per configuration files assert_vars_file(client, 'debug') assert_vars_file(client, 'release') + + +def test_todo(): + # Create package + client = TestClient() + # client.run("remote add conancenter https://center2.conan.io") + + def run_pkg(msg): + host_arch = client.get_default_host_profile().settings['arch'] + cmd_release = environment_wrap_command(ConanFileMock(), f"conanrunenv-release-{host_arch}", + client.current_folder, "dep") + client.run_command(cmd_release) + assert "{}: Hello World Release!".format(msg) in client.out + + client.run("new cmake_lib -d name=dep -d version=1.0 -o dep") + + consumer_source = textwrap.dedent(""" + #include + #include "dep.h" + + int main(void) { + dep(); + std::cout << "Hello World" << std::endl; + return 0; + } + """) + + premake5 = textwrap.dedent(""" + include("conandeps.premake5.lua") + + workspace "HelloWorld" + configurations { "Debug", "Release" } + + project "HelloWorld" + kind "ConsoleApp" + language "C++" + targetdir "bin/%{cfg.buildcfg}" + + files { "**.h", "**.cpp" } + conan_setup() + + filter "configurations:Debug" + defines { "DEBUG" } + symbols "On" + + filter "configurations:Release" + defines { "NDEBUG" } + optimize "On" + """) + + + conanfile = textwrap.dedent(""" + from conan import ConanFile + from conan import ConanFile + from conan.tools.files import copy, get, collect_libs, chdir, save, replace_in_file + from conan.tools.layout import basic_layout + from conan.tools.microsoft import MSBuild + from conan.tools.premake import Premake, PremakeDeps + import os + + class Pkg(ConanFile): + settings = "os", "compiler", "build_type", "arch" + name = "pkg" + version = "1.0" + exports_sources = '*' + + def layout(self): + basic_layout(self, src_folder="src") + + def requirements(self): + self.requires("dep/1.0") + + def generate(self): + deps = PremakeDeps(self) + deps.generate() + + def build(self): + with chdir(self, self.source_folder): + premake = Premake(self) + premake.arguments = {"scripts": "../build-release/conan"} + premake.configure() + if self.settings.os == "Windows": + pass + # msbuild = MSBuild(self) + # msbuild.build("Yojimbo.sln") + else: + build_type = str(self.settings.build_type) + self.run(f"make config={build_type.lower()} -j") + + def package(self): + copy(self, "*.h", os.path.join(self.source_folder, "include"), os.path.join(self.package_folder, "include", "pkg")) + for lib in ("*.lib", "*.a"): + copy(self, lib, self.source_folder, os.path.join(self.package_folder, "lib"), keep_path=False) + """) + + client.save({"consumer/conanfile.py": conanfile, + "consumer/src/hello.cpp": consumer_source, + "consumer/src/premake5.lua": premake5, + }) + + client.run("create dep") + client.run("create consumer --build=missing") + build_folder = client.created_layout().build() + print(build_folder) + + print(client.out) + client.run("install consumer") + run_pkg("Hello World") + + print(client.out) +