diff --git a/conan/tools/__init__.py b/conan/tools/__init__.py index ec9305f7462..225863006b8 100644 --- a/conan/tools/__init__.py +++ b/conan/tools/__init__.py @@ -1 +1,2 @@ -CONAN_TOOLCHAIN_ARGS_FILE = "conanbuild.json" +CONAN_TOOLCHAIN_ARGS_FILE = "conanbuild.conf" +CONAN_TOOLCHAIN_ARGS_SECTION = "toolchain" diff --git a/conan/tools/cmake/cmake.py b/conan/tools/cmake/cmake.py index 6536b67eb67..e124984cf73 100644 --- a/conan/tools/cmake/cmake.py +++ b/conan/tools/cmake/cmake.py @@ -1,9 +1,9 @@ -import json +import os import os import platform -from conan.tools import CONAN_TOOLCHAIN_ARGS_FILE from conan.tools.cmake.utils import is_multi_configuration +from conan.tools.files import load_toolchain_args from conan.tools.gnu.make import make_jobs_cmd_line_arg from conan.tools.meson.meson import ninja_jobs_cmd_line_arg from conan.tools.microsoft.msbuild import msbuild_verbosity_cmd_line_arg, \ @@ -12,7 +12,7 @@ from conans.client.tools.files import chdir from conans.client.tools.oss import cpu_count, args_to_string from conans.errors import ConanException -from conans.util.files import mkdir, load +from conans.util.files import mkdir def _validate_recipe(conanfile): @@ -66,13 +66,10 @@ def __init__(self, conanfile, parallel=True): # Store a reference to useful data self._conanfile = conanfile self._parallel = parallel - self._generator = None - args_file = os.path.join(self._conanfile.generators_folder, CONAN_TOOLCHAIN_ARGS_FILE) - if os.path.exists(args_file): - json_args = json.loads(load(args_file)) - self._generator = json_args.get("cmake_generator") - self._toolchain_file = json_args.get("cmake_toolchain_file") + toolchain_file_content = load_toolchain_args(self._conanfile.generators_folder) + self._generator = toolchain_file_content.get("cmake_generator") + self._toolchain_file = toolchain_file_content.get("cmake_toolchain_file") self._cmake_program = "cmake" # Path to CMake should be handled by environment diff --git a/conan/tools/cmake/toolchain.py b/conan/tools/cmake/toolchain.py index 2ff9ba810b6..9fb1e87be05 100644 --- a/conan/tools/cmake/toolchain.py +++ b/conan/tools/cmake/toolchain.py @@ -1,4 +1,3 @@ -import json import os import re import textwrap @@ -7,10 +6,10 @@ import six from jinja2 import Template -from conan.tools import CONAN_TOOLCHAIN_ARGS_FILE from conan.tools._check_build_profile import check_using_build_profile from conan.tools._compilers import architecture_flag, use_win_mingw from conan.tools.cmake.utils import is_multi_configuration, get_file_name +from conan.tools.files import save_toolchain_args from conan.tools.microsoft import VCVars from conan.tools.microsoft.visual import vs_ide_version from conans.errors import ConanException @@ -741,7 +740,7 @@ def _writebuild(self, toolchain_file): result["cmake_toolchain_file"] = toolchain_file or self.filename if result: - save(CONAN_TOOLCHAIN_ARGS_FILE, json.dumps(result)) + save_toolchain_args(result) def _get_generator(self, recipe_generator): # Returns the name of the generator to be used by CMake diff --git a/conan/tools/files/__init__.py b/conan/tools/files/__init__.py index bb235068c7b..048030fa6c8 100644 --- a/conan/tools/files/__init__.py +++ b/conan/tools/files/__init__.py @@ -1,4 +1,4 @@ from conan.tools.files.files import load, save, mkdir, ftp_download, download, get, rename, \ - load_build_json, save_build_json + load_toolchain_args, save_toolchain_args from conan.tools.files.patches import patch, apply_conandata_patches from conan.tools.files.cpp_package import CppPackage diff --git a/conan/tools/files/files.py b/conan/tools/files/files.py index 30ea7565100..3be63b57bd8 100644 --- a/conan/tools/files/files.py +++ b/conan/tools/files/files.py @@ -1,10 +1,10 @@ +import configparser import errno -import json import os import platform import subprocess -from conan.tools import CONAN_TOOLCHAIN_ARGS_FILE +from conan.tools import CONAN_TOOLCHAIN_ARGS_FILE, CONAN_TOOLCHAIN_ARGS_SECTION from conans.client.downloaders.download import run_downloader from conans.client.tools.files import unzip, which from conans.errors import ConanException @@ -185,13 +185,40 @@ def rename(conanfile, src, dst): raise ConanException("rename {} to {} failed: {}".format(src, dst, err)) -def load_build_json(conanfile): - path = os.path.join(conanfile.generators_folder, CONAN_TOOLCHAIN_ARGS_FILE) - contents = load(conanfile, path) - data = json.loads(contents) - return data +def load_toolchain_args(generators_folder=None): + """ + Helper function to load the content of any CONAN_TOOLCHAIN_ARGS_FILE + + :param generators_folder: `str` folder where is located the CONAN_TOOLCHAIN_ARGS_FILE. + :return: + """ + args_file = os.path.join(generators_folder, CONAN_TOOLCHAIN_ARGS_FILE) if generators_folder \ + else CONAN_TOOLCHAIN_ARGS_FILE + toolchain_config = configparser.ConfigParser() + toolchain_file = toolchain_config.read(args_file) + if not toolchain_file: + raise ConanException("The file %s does not exist. Please, make sure that it was not" + " generated in another folder." % args_file) + try: + return toolchain_config[CONAN_TOOLCHAIN_ARGS_SECTION] + except KeyError: + raise ConanException("The primary section [%s] does not exist in the file %s. Please, add it" + " as the default one of all your configuration variables." % + (CONAN_TOOLCHAIN_ARGS_SECTION, args_file)) -def save_build_json(conanfile, contents): - path = os.path.join(conanfile.generators_folder, CONAN_TOOLCHAIN_ARGS_FILE) - save(conanfile, path, json.dumps(contents)) +def save_toolchain_args(content, generators_folder=None): + """ + Helper function to save the content into the CONAN_TOOLCHAIN_ARGS_FILE + + :param content: `dict` all the information to be saved into the toolchain file. + :param generators_folder: `str` folder where is located the CONAN_TOOLCHAIN_ARGS_FILE + """ + # Let's prune None values + content_ = {k: v for k, v in content.items() if v is not None} + args_file = os.path.join(generators_folder, CONAN_TOOLCHAIN_ARGS_FILE) if generators_folder \ + else CONAN_TOOLCHAIN_ARGS_FILE + toolchain_config = configparser.ConfigParser() + toolchain_config[CONAN_TOOLCHAIN_ARGS_SECTION] = content_ + with open(args_file, "w") as f: + toolchain_config.write(f) diff --git a/conan/tools/gnu/autotools.py b/conan/tools/gnu/autotools.py index 4780cb804f6..03c2f10eee6 100644 --- a/conan/tools/gnu/autotools.py +++ b/conan/tools/gnu/autotools.py @@ -1,13 +1,7 @@ -import json -import os -import platform - -from conan.tools import CONAN_TOOLCHAIN_ARGS_FILE -from conan.tools._compilers import use_win_mingw +from conan.tools.files import load_toolchain_args from conan.tools.gnu.make import make_jobs_cmd_line_arg from conan.tools.microsoft import unix_path from conans.client.build import join_arguments -from conans.util.files import load class Autotools(object): @@ -15,11 +9,9 @@ class Autotools(object): def __init__(self, conanfile): self._conanfile = conanfile - args_path = os.path.join(conanfile.generators_folder, CONAN_TOOLCHAIN_ARGS_FILE) - if os.path.isfile(args_path): - args = json.loads(load(args_path)) - self._configure_args = args.get("configure_args") - self._make_args = args.get("make_args") + toolchain_file_content = load_toolchain_args(self._conanfile.generators_folder) + self._configure_args = toolchain_file_content.get("configure_args") + self._make_args = toolchain_file_content.get("make_args") def configure(self): """ diff --git a/conan/tools/gnu/autotoolstoolchain.py b/conan/tools/gnu/autotoolstoolchain.py index ac3f7815804..fac0399c95a 100644 --- a/conan/tools/gnu/autotoolstoolchain.py +++ b/conan/tools/gnu/autotoolstoolchain.py @@ -1,13 +1,10 @@ -import json - -from conan.tools import CONAN_TOOLCHAIN_ARGS_FILE from conan.tools._check_build_profile import check_using_build_profile from conan.tools._compilers import architecture_flag, build_type_flags, cppstd_flag from conan.tools.apple.apple import apple_min_version_flag, to_apple_arch, \ apple_sdk_path from conan.tools.cross_building import cross_building, get_cross_building_settings from conan.tools.env import Environment -from conan.tools.files import save +from conan.tools.files import save_toolchain_args from conan.tools.gnu.get_gnu_triplet import _get_gnu_triplet from conans.tools import args_to_string @@ -168,4 +165,4 @@ def generate_args(self): args = {"configure_args": args_to_string(configure_args), "make_args": args_to_string(self.make_args)} - save(self._conanfile, CONAN_TOOLCHAIN_ARGS_FILE, json.dumps(args)) + save_toolchain_args(args) diff --git a/conan/tools/google/bazel.py b/conan/tools/google/bazel.py index a0282ca4bf3..804eec1b02b 100644 --- a/conan/tools/google/bazel.py +++ b/conan/tools/google/bazel.py @@ -1,8 +1,5 @@ -import os -import json +from conan.tools.files import load_toolchain_args -from conan.tools import CONAN_TOOLCHAIN_ARGS_FILE -from conans.util.files import load class Bazel(object): def __init__(self, conanfile): @@ -40,10 +37,6 @@ def build(self, args=None, label=None): self._conanfile.run(command) def _get_bazel_project_configuration(self): - self._bazel_config = None - self._bazelrc_path = None - - if os.path.exists(CONAN_TOOLCHAIN_ARGS_FILE): - conan_toolchain_args = json.loads(load(CONAN_TOOLCHAIN_ARGS_FILE)) - self._bazel_config = conan_toolchain_args.get("bazel_config", None) - self._bazelrc_path = conan_toolchain_args.get("bazelrc_path", None) + toolchain_file_content = load_toolchain_args(self._conanfile.generators_folder) + self._bazel_config = toolchain_file_content.get("bazel_config") + self._bazelrc_path = toolchain_file_content.get("bazelrc_path") diff --git a/conan/tools/google/toolchain.py b/conan/tools/google/toolchain.py index 5a88a2381be..2adc991883f 100644 --- a/conan/tools/google/toolchain.py +++ b/conan/tools/google/toolchain.py @@ -1,8 +1,5 @@ -import json - -from conan.tools import CONAN_TOOLCHAIN_ARGS_FILE from conan.tools._check_build_profile import check_using_build_profile -from conans.util.files import save +from conan.tools.files import save_toolchain_args class BazelToolchain(object): @@ -12,10 +9,7 @@ def __init__(self, conanfile): check_using_build_profile(self._conanfile) def generate(self): - bazel_config = self._conanfile.conf["tools.google.bazel:config"] - bazelrc_path = self._conanfile.conf["tools.google.bazel:bazelrc_path"] - - save(CONAN_TOOLCHAIN_ARGS_FILE, json.dumps({ - "bazel_config": bazel_config, - "bazelrc_path": bazelrc_path - })) + save_toolchain_args({ + "bazel_config": self._conanfile.conf["tools.google.bazel:config"], + "bazelrc_path": self._conanfile.conf["tools.google.bazel:bazelrc_path"] + }) diff --git a/conans/test/functional/toolchains/cmake/test_ninja.py b/conans/test/functional/toolchains/cmake/test_ninja.py index d757175830a..f983bffd0d5 100644 --- a/conans/test/functional/toolchains/cmake/test_ninja.py +++ b/conans/test/functional/toolchains/cmake/test_ninja.py @@ -4,6 +4,7 @@ import pytest from conan.tools.cmake import CMakeToolchain +from conan.tools.files import load_toolchain_args from conans.test.assets.cmake import gen_cmakelists from conans.test.assets.genconanfile import GenConanfile from conans.test.assets.sources import gen_function_h, gen_function_cpp @@ -219,8 +220,8 @@ def test_ninja_conf(): client.save({"conanfile.py": conanfile, "profile": profile}) client.run("install . -pr=profile") - conanbuild = client.load("conanbuild.json") - assert '"cmake_generator": "Ninja"' in conanbuild + conanbuild = load_toolchain_args(client.current_folder) + assert conanbuild["cmake_generator"] == "Ninja" vcvars = client.load("conanvcvars.bat") assert "2017" in vcvars diff --git a/conans/test/functional/toolchains/gnu/autotools/test_ios.py b/conans/test/functional/toolchains/gnu/autotools/test_ios.py index ff3e8685769..e6b04021339 100644 --- a/conans/test/functional/toolchains/gnu/autotools/test_ios.py +++ b/conans/test/functional/toolchains/gnu/autotools/test_ios.py @@ -3,6 +3,7 @@ import pytest +from conan.tools.files import load_toolchain_args from conans.client.tools.apple import XCRun, to_apple_arch from conans.test.assets.autotools import gen_makefile_am, gen_configure_ac from conans.test.assets.sources import gen_function_cpp @@ -72,6 +73,6 @@ def build(self): client.run_command("lipo -info main") assert "Non-fat file: main is architecture: arm64" in client.out - js = client.load("conanbuild.json") - assert '--host=aarch64-apple-ios' in js - assert '--build=x86_64-apple-darwin' in js + conanbuild = load_toolchain_args(client.current_folder) + configure_args = conanbuild["configure_args"] + assert configure_args == "'--host=aarch64-apple-ios' '--build=x86_64-apple-darwin'" diff --git a/conans/test/functional/toolchains/test_bazel_toolchain.py b/conans/test/functional/toolchains/test_bazel_toolchain.py index 39f9ad44a7e..7e56755bebc 100644 --- a/conans/test/functional/toolchains/test_bazel_toolchain.py +++ b/conans/test/functional/toolchains/test_bazel_toolchain.py @@ -1,6 +1,6 @@ -import json import textwrap +from conan.tools.files import load_toolchain_args from conans.test.assets.genconanfile import GenConanfile from conans.test.utils.tools import TestClient @@ -14,10 +14,8 @@ def test_toolchain_empty_config(): client.save({"conanfile.py": conanfile}) client.run("install .") - config = json.loads(client.load("conanbuild.json")) - - assert config['bazel_config'] is None - assert config['bazelrc_path'] is None + config = load_toolchain_args(client.current_folder) + assert not config def test_toolchain_loads_config_from_profile(): @@ -39,7 +37,6 @@ def test_toolchain_loads_config_from_profile(): }) client.run("install . -pr=test_profile") - config = json.loads(client.load("conanbuild.json")) - + config = load_toolchain_args(client.current_folder) assert config['bazel_config'] == "test_config" assert config['bazelrc_path'] == "/path/to/bazelrc" diff --git a/conans/test/functional/toolchains/test_cmake_toolchain.py b/conans/test/functional/toolchains/test_cmake_toolchain.py index 0635febeead..f5b29b16e21 100644 --- a/conans/test/functional/toolchains/test_cmake_toolchain.py +++ b/conans/test/functional/toolchains/test_cmake_toolchain.py @@ -1,9 +1,9 @@ -import json import os import platform import pytest +from conan.tools.files import load_toolchain_args from conans.test.assets.genconanfile import GenConanfile from conans.test.utils.tools import TestClient from conans.util.files import save @@ -61,5 +61,5 @@ def test_cmake_toolchain_custom_toolchain(): client.save({"conanfile.py": conanfile}) client.run("install .") assert not os.path.exists(os.path.join(client.current_folder, "conan_toolchain.cmake")) - buildjson = json.loads(client.load("conanbuild.json")) - assert "mytoolchain.cmake" == buildjson["cmake_toolchain_file"] + build_content = load_toolchain_args(client.current_folder) + assert "mytoolchain.cmake" == build_content["cmake_toolchain_file"] diff --git a/conans/test/unittests/client/toolchain/autotools/autotools_test.py b/conans/test/unittests/client/toolchain/autotools/autotools_test.py index f2959f49f1d..2e8dd0741b6 100644 --- a/conans/test/unittests/client/toolchain/autotools/autotools_test.py +++ b/conans/test/unittests/client/toolchain/autotools/autotools_test.py @@ -2,10 +2,9 @@ from mock import Mock -from conan.tools import CONAN_TOOLCHAIN_ARGS_FILE +from conan.tools.files import save_toolchain_args from conan.tools.gnu import Autotools from conans import ConanFile -from conans.client.tools.files import save from conans.model.conf import Conf from conans.test.unittests.util.tools_test import RunnerMock from conans.test.utils.mocks import MockSettings @@ -15,10 +14,10 @@ def test_configure_arguments(): tmp = temp_folder() os.chdir(tmp) - save(CONAN_TOOLCHAIN_ARGS_FILE, """ - {"configure_args": "my_configure_args", - "make_args": "my_make_args"} - """) + save_toolchain_args({ + "configure_args": "my_configure_args", + "make_args": "my_make_args"} + ) runner = RunnerMock() conanfile = ConanFile(Mock(), runner=runner) conanfile.settings = MockSettings({}) diff --git a/conans/test/unittests/client/toolchain/autotools/autotools_toolchain_test.py b/conans/test/unittests/client/toolchain/autotools/autotools_toolchain_test.py index c1697716ec2..871d57a0dbf 100644 --- a/conans/test/unittests/client/toolchain/autotools/autotools_toolchain_test.py +++ b/conans/test/unittests/client/toolchain/autotools/autotools_toolchain_test.py @@ -1,10 +1,9 @@ -import json import platform from os import chdir import pytest -from conan.tools import CONAN_TOOLCHAIN_ARGS_FILE +from conan.tools.files import load_toolchain_args from conan.tools.gnu import AutotoolsToolchain from conans.errors import ConanException from conans.model.conf import Conf @@ -45,9 +44,7 @@ def test_target_triple(): be = AutotoolsToolchain(conanfile) be.make_args = ["foo", "var"] be.generate_args() - with open(CONAN_TOOLCHAIN_ARGS_FILE) as f: - obj = json.load(f) - + obj = load_toolchain_args() assert "--host=x86_64-linux-gnu" in obj["configure_args"] assert "--build=i686-solaris" in obj["configure_args"] assert obj["make_args"].replace("'", "") == "foo var" @@ -75,7 +72,7 @@ def test_cppstd(): "cppstd": "17"}) be = AutotoolsToolchain(conanfile) env = be.environment() - assert not "-std=c++17" in env["CXXFLAGS"] + assert "-std=c++17" not in env["CXXFLAGS"] # Using "compiler.cppstd" works conanfile.settings = MockSettings( diff --git a/conans/test/unittests/tools/files/test_toolchain.py b/conans/test/unittests/tools/files/test_toolchain.py new file mode 100644 index 00000000000..0a4238e1d20 --- /dev/null +++ b/conans/test/unittests/tools/files/test_toolchain.py @@ -0,0 +1,61 @@ +import os +import tempfile +import textwrap + +import pytest +from conans import load + +from conan.tools import CONAN_TOOLCHAIN_ARGS_FILE, CONAN_TOOLCHAIN_ARGS_SECTION +from conan.tools.files import load_toolchain_args, save_toolchain_args +from conans.errors import ConanException +from conans.util.files import save, remove + + +def test_load_empty_toolchain_args_in_default_dir(): + save(CONAN_TOOLCHAIN_ARGS_FILE, "[%s]" % CONAN_TOOLCHAIN_ARGS_SECTION) + try: + config = load_toolchain_args() + assert not config + finally: + remove(CONAN_TOOLCHAIN_ARGS_FILE) + + +def test_load_toolchain_args_if_it_does_not_exist(): + with pytest.raises(ConanException): + load_toolchain_args() + + +def test_toolchain_args_with_content_full(): + temp_folder = tempfile.mkdtemp() + content = textwrap.dedent(r""" + [%s] + win_path=my\win\path + command=conan --option "My Option" + my_regex=([A-Z])\w+ + """ % CONAN_TOOLCHAIN_ARGS_SECTION) + save(os.path.join(temp_folder, CONAN_TOOLCHAIN_ARGS_FILE), content) + args = load_toolchain_args(generators_folder=temp_folder) + assert args["win_path"] == r'my\win\path' + assert args["command"] == r'conan --option "My Option"' + assert args["my_regex"] == r'([A-Z])\w+' + + +def test_save_toolchain_args_empty(): + temp_folder = tempfile.mkdtemp() + content = {} + save_toolchain_args(content, generators_folder=temp_folder) + args = load(os.path.join(temp_folder, CONAN_TOOLCHAIN_ARGS_FILE)) + assert "[%s]" % CONAN_TOOLCHAIN_ARGS_SECTION in args + + +def test_save_toolchain_args_full(): + temp_folder = tempfile.mkdtemp() + content = { + 'win_path': r'my\win\path', + 'command': r'conan --option "My Option"', + 'my_regex': r'([A-Z])\w+' + } + save_toolchain_args(content, generators_folder=temp_folder) + args = load(os.path.join(temp_folder, CONAN_TOOLCHAIN_ARGS_FILE)) + assert "[%s]" % CONAN_TOOLCHAIN_ARGS_SECTION in args + assert r'win_path = my\win\path' in args diff --git a/conans/test/unittests/tools/google/test_bazel.py b/conans/test/unittests/tools/google/test_bazel.py index d18e8a41b77..ff269504b83 100644 --- a/conans/test/unittests/tools/google/test_bazel.py +++ b/conans/test/unittests/tools/google/test_bazel.py @@ -1,4 +1,7 @@ import textwrap +import os +from conan.tools import CONAN_TOOLCHAIN_ARGS_SECTION, CONAN_TOOLCHAIN_ARGS_FILE +from conans.util.files import save, remove from conan.tools.google import Bazel from conans.model.conf import ConfDefinition @@ -6,32 +9,33 @@ def test_bazel_command_with_empty_config(): - c = ConfDefinition() - c.loads(textwrap.dedent("""\ - tools.google.bazel:config= - tools.google.bazel:bazelrc_path= - """)) - conanfile = ConanFileMock() - conanfile.conf = c.get_conanfile_conf(None) + args_file = os.path.join(conanfile.generators_folder, CONAN_TOOLCHAIN_ARGS_FILE) + save(args_file, + textwrap.dedent("""\ + [%s] + bazel_config= + bazelrc_path= + """ % CONAN_TOOLCHAIN_ARGS_SECTION)) bazel = Bazel(conanfile) bazel.build(label='//test:label') - + # TODO: Create a context manager to remove the file + remove(args_file) assert 'bazel build //test:label' == str(conanfile.command) def test_bazel_command_with_config_values(): - c = ConfDefinition() - c.loads(textwrap.dedent("""\ - tools.google.bazel:config=config - tools.google.bazel:bazelrc_path=/path/to/bazelrc - """)) - conanfile = ConanFileMock() - conanfile.conf = c.get_conanfile_conf(None) - + args_file = os.path.join(conanfile.generators_folder, CONAN_TOOLCHAIN_ARGS_FILE) + save(args_file, + textwrap.dedent("""\ + [%s] + bazel_config=config + bazelrc_path=/path/to/bazelrc + """ % CONAN_TOOLCHAIN_ARGS_SECTION)) bazel = Bazel(conanfile) bazel.build(label='//test:label') - - assert 'bazel build //test:label' == str(conanfile.command) + # TODO: Create a context manager to remove the file + remove(args_file) + assert 'bazel --bazelrc=/path/to/bazelrc build --config=config //test:label' == str(conanfile.command)