From 10aedbc4beecb74c861ee3f2b25feb955c722b81 Mon Sep 17 00:00:00 2001 From: Uilian Ries Date: Fri, 27 Apr 2018 07:06:57 -0300 Subject: [PATCH] CircleCI support (#2359) (#2808) * #2359 Add Circle CI support - Create Conan + CircleCI project template using command new - Create Conan + CircleCI project template using API Signed-off-by: Uilian Ries * #2359 Validate CircleCI support - Test CircleCI template Signed-off-by: Uilian Ries --- conans/client/cmd/new.py | 7 +- conans/client/cmd/new_ci.py | 147 +++++++++++++++++++++++++++++++- conans/client/command.py | 14 ++- conans/client/conan_api.py | 8 +- conans/test/command/new_test.py | 43 +++++++++- 5 files changed, 210 insertions(+), 9 deletions(-) diff --git a/conans/client/cmd/new.py b/conans/client/cmd/new.py index 9e9b363ca5a..8dfeec6d5a1 100644 --- a/conans/client/cmd/new.py +++ b/conans/client/cmd/new.py @@ -227,7 +227,8 @@ def test(self): def cmd_new(ref, header=False, pure_c=False, test=False, exports_sources=False, bare=False, visual_versions=None, linux_gcc_versions=None, linux_clang_versions=None, osx_clang_versions=None, - shared=None, upload_url=None, gitignore=None, gitlab_gcc_versions=None, gitlab_clang_versions=None): + shared=None, upload_url=None, gitignore=None, gitlab_gcc_versions=None, gitlab_clang_versions=None, + circleci_gcc_versions=None, circleci_clang_versions=None, circleci_osx_versions=None): try: tokens = ref.split("@") name, version = tokens[0].split("/") @@ -284,5 +285,7 @@ def cmd_new(ref, header=False, pure_c=False, test=False, exports_sources=False, files.update(ci_get_files(name, version, user, channel, visual_versions, linux_gcc_versions, linux_clang_versions, osx_clang_versions, shared, upload_url, - gitlab_gcc_versions, gitlab_clang_versions)) + gitlab_gcc_versions, gitlab_clang_versions, + circleci_gcc_versions, circleci_clang_versions, + circleci_osx_versions)) return files diff --git a/conans/client/cmd/new_ci.py b/conans/client/cmd/new_ci.py index a1820864f3d..902f77ec46b 100644 --- a/conans/client/cmd/new_ci.py +++ b/conans/client/cmd/new_ci.py @@ -157,6 +157,96 @@ <<: *build-template """ +circleci = """ +version: 2 +.conan-steps: &conan-steps + steps: + - checkout + - run: + name: Update Conan package + command: | + chmod +x .circleci/install.sh + .circleci/install.sh + - run: + name: Build recipe + command: | + chmod +x .circleci/run.sh + .circleci/run.sh + environment: + CONAN_REFERENCE: "{name}/{version}" + CONAN_USERNAME: "{user}" + CONAN_CHANNEL: "{channel}" + {upload} +jobs: +{configs} +{workflow} +""" + +circleci_config_gcc = """ + gcc-{name}: + docker: + - image: lasote/conangcc{name} + environment: + - CONAN_GCC_VERSIONS: "{version}" + <<: *conan-steps +""" + +circleci_config_clang = """ + clang-{name}: + docker: + - image: lasote/conanclang{name} + environment: + - CONAN_CLANG_VERSIONS: "{version}" + <<: *conan-steps +""" + +circleci_config_osx = """ + xcode-{name}: + macos: + xcode: "{name}" + environment: + - CONAN_APPLE_CLANG_VERSIONS: "{version}" + <<: *conan-steps +""" + +circleci_install = """ +#!/bin/bash + +set -e +set -x + +SUDO=sudo + +if [[ "$(uname -s)" == 'Darwin' ]]; then + brew update || brew update + brew install cmake || true + SUDO= +fi + +$SUDO pip install conan --upgrade +$SUDO pip install conan_package_tools +conan user +""" + +circleci_run = """ +#!/bin/bash + +set -e +set -x + +python build.py +""" + +circleci_workflow = """ +workflows: + version: 2 + build_and_test: + jobs: +{jobs} +""" + +circleci_job = """ - {job} +""" def get_build_py(name, shared): shared = 'shared_option_name="{}:shared"'.format(name) if shared else "" @@ -224,15 +314,50 @@ def get_gitlab(name, version, user, channel, linux_gcc_versions, linux_clang_ver configs=configs, upload=upload)} return files +def get_circleci(name, version, user, channel, linux_gcc_versions, linux_clang_versions, + osx_clang_versions, upload_url): + config = [] + jobs = [] + + if linux_gcc_versions: + for gcc in linux_gcc_versions: + gcc_name = gcc.replace(".", "") + config.append(circleci_config_gcc.format(version=gcc, name=gcc_name)) + jobs.append(circleci_job.format(job='gcc-{}'.format(gcc_name))) + + if linux_clang_versions: + for clang in linux_clang_versions: + clang_name = clang.replace(".", "") + config.append(circleci_config_clang.format(version=clang, name=clang_name)) + jobs.append(circleci_job.format(job='clang-{}'.format(clang_name))) + + xcode_map = {"8.1": "8.3.3", + "9.0": "9.0"} + for apple_clang in osx_clang_versions: + osx_name = xcode_map[apple_clang] + config.append(circleci_config_osx.format(name=osx_name, version=apple_clang)) + jobs.append(circleci_job.format(job='xcode-{}'.format(osx_name))) + + configs = "".join(config) + workflow = circleci_workflow.format(jobs="".join(jobs)) + upload = ('CONAN_UPLOAD: "%s"\n' % upload_url) if upload_url else "" + files = {".circleci/config.yml": circleci.format(name=name, version=version, user=user, channel=channel, + configs=configs, workflow=workflow, upload=upload), + ".circleci/install.sh": circleci_install, + ".circleci/run.sh": circleci_run} + return files + def ci_get_files(name, version, user, channel, visual_versions, linux_gcc_versions, linux_clang_versions, osx_clang_versions, shared, upload_url, gitlab_gcc_versions, - gitlab_clang_versions): + gitlab_clang_versions, circleci_gcc_versions, circleci_clang_versions, circleci_osx_versions): if shared and not (visual_versions or linux_gcc_versions or linux_clang_versions or - osx_clang_versions or gitlab_gcc_versions or gitlab_clang_versions): + osx_clang_versions or gitlab_gcc_versions or gitlab_clang_versions or + circleci_gcc_versions or circleci_clang_versions or circleci_osx_versions): raise ConanException("Trying to specify 'shared' in CI, but no CI system specified") if not (visual_versions or linux_gcc_versions or linux_clang_versions or osx_clang_versions or - gitlab_gcc_versions or gitlab_clang_versions): + gitlab_gcc_versions or gitlab_clang_versions or circleci_gcc_versions or + circleci_clang_versions or circleci_osx_versions): return {} gcc_versions = ["4.9", "5", "6", "7"] clang_versions = ["3.9", "4.0"] @@ -242,12 +367,18 @@ def ci_get_files(name, version, user, channel, visual_versions, linux_gcc_versio linux_gcc_versions = gcc_versions if gitlab_gcc_versions is True: gitlab_gcc_versions = gcc_versions + if circleci_gcc_versions is True: + circleci_gcc_versions = gcc_versions if linux_clang_versions is True: linux_clang_versions = clang_versions if gitlab_clang_versions is True: gitlab_clang_versions = clang_versions + if circleci_clang_versions is True: + circleci_clang_versions = clang_versions if osx_clang_versions is True: osx_clang_versions = ["7.3", "8.1", "9.0"] + if circleci_osx_versions is True: + circleci_osx_versions = ["8.1", "9.0"] if not visual_versions: visual_versions = [] if not linux_gcc_versions: @@ -260,6 +391,12 @@ def ci_get_files(name, version, user, channel, visual_versions, linux_gcc_versio gitlab_gcc_versions = [] if not gitlab_clang_versions: gitlab_clang_versions = [] + if not circleci_gcc_versions: + circleci_gcc_versions = [] + if not circleci_clang_versions: + circleci_clang_versions = [] + if not circleci_osx_versions: + circleci_osx_versions = [] files = {"build.py": get_build_py(name, shared)} if linux_gcc_versions or osx_clang_versions or linux_clang_versions: files.update(get_travis(name, version, user, channel, linux_gcc_versions, @@ -269,6 +406,10 @@ def ci_get_files(name, version, user, channel, visual_versions, linux_gcc_versio files.update(get_gitlab(name, version, user, channel, gitlab_gcc_versions, gitlab_clang_versions, upload_url)) + if circleci_gcc_versions or circleci_clang_versions or circleci_osx_versions: + files.update(get_circleci(name, version, user, channel, circleci_gcc_versions, + circleci_clang_versions, circleci_osx_versions, upload_url)) + if visual_versions: files.update(get_appveyor(name, version, user, channel, visual_versions, upload_url)) diff --git a/conans/client/command.py b/conans/client/command.py index 01ae0b45800..01bf06ed71b 100644 --- a/conans/client/command.py +++ b/conans/client/command.py @@ -142,6 +142,15 @@ def new(self, *args): parser.add_argument("-ciglc", "--ci-gitlab-clang", action='store_true', default=False, help='Generate GitLab files for linux clang') + parser.add_argument("-ciccg", "--ci-circleci-gcc", action='store_true', + default=False, + help='Generate CicleCI files for linux gcc') + parser.add_argument("-ciccc", "--ci-circleci-clang", action='store_true', + default=False, + help='Generate CicleCI files for linux clang') + parser.add_argument("-cicco", "--ci-circleci-osx", action='store_true', + default=False, + help='Generate CicleCI files for OSX apple-clang') parser.add_argument("-gi", "--gitignore", action='store_true', default=False, help='Generate a .gitignore with the known patterns to excluded') parser.add_argument("-ciu", "--ci-upload-url", @@ -157,7 +166,10 @@ def new(self, *args): osx_clang_versions=args.ci_travis_osx, shared=args.ci_shared, upload_url=args.ci_upload_url, gitlab_gcc_versions=args.ci_gitlab_gcc, - gitlab_clang_versions=args.ci_gitlab_clang) + gitlab_clang_versions=args.ci_gitlab_clang, + circleci_gcc_versions=args.ci_circleci_gcc, + circleci_clang_versions=args.ci_circleci_clang, + circleci_osx_versions=args.ci_circleci_osx) def test(self, *args): """Test a package consuming it from a conanfile.py with a test() method. This command diff --git a/conans/client/conan_api.py b/conans/client/conan_api.py index 600d3398d0d..a1d8ba75b8e 100644 --- a/conans/client/conan_api.py +++ b/conans/client/conan_api.py @@ -234,7 +234,8 @@ def _init_manager(self): def new(self, name, header=False, pure_c=False, test=False, exports_sources=False, bare=False, cwd=None, visual_versions=None, linux_gcc_versions=None, linux_clang_versions=None, osx_clang_versions=None, shared=None, upload_url=None, gitignore=None, - gitlab_gcc_versions=None, gitlab_clang_versions=None): + gitlab_gcc_versions=None, gitlab_clang_versions=None, + circleci_gcc_versions=None, circleci_clang_versions=None, circleci_osx_versions=None): from conans.client.cmd.new import cmd_new cwd = os.path.abspath(cwd or os.getcwd()) files = cmd_new(name, header=header, pure_c=pure_c, test=test, @@ -245,7 +246,10 @@ def new(self, name, header=False, pure_c=False, test=False, exports_sources=Fals osx_clang_versions=osx_clang_versions, shared=shared, upload_url=upload_url, gitignore=gitignore, gitlab_gcc_versions=gitlab_gcc_versions, - gitlab_clang_versions=gitlab_clang_versions) + gitlab_clang_versions=gitlab_clang_versions, + circleci_gcc_versions=circleci_gcc_versions, + circleci_clang_versions=circleci_clang_versions, + circleci_osx_versions=circleci_osx_versions) save_files(cwd, files) for f in sorted(files): diff --git a/conans/test/command/new_test.py b/conans/test/command/new_test.py index 2233d9b8a80..5f9410bdec4 100644 --- a/conans/test/command/new_test.py +++ b/conans/test/command/new_test.py @@ -109,7 +109,7 @@ def new_without_test(self): def new_ci_test(self): client = TestClient() - client.run('new MyPackage/1.3@myuser/testing -cis -ciw -cilg -cilc -cio -ciglg -ciglc -ciu=myurl') + client.run('new MyPackage/1.3@myuser/testing -cis -ciw -cilg -cilc -cio -ciglg -ciglc -ciccg -ciccc -cicco -ciu=myurl') root = client.current_folder build_py = load(os.path.join(root, "build.py")) self.assertIn('builder.add_common_builds(shared_option_name="MyPackage:shared")', @@ -120,6 +120,9 @@ def new_ci_test(self): self.assertNotIn('apple_clang_versions=', build_py) self.assertNotIn('gitlab_gcc_versions=', build_py) self.assertNotIn('gitlab_clang_versions=', build_py) + self.assertNotIn('circleci_gcc_versions=', build_py) + self.assertNotIn('circleci_clang_versions=', build_py) + self.assertNotIn('circleci_osx_versions=', build_py) appveyor = load(os.path.join(root, "appveyor.yml")) self.assertIn("CONAN_UPLOAD: \"myurl\"", appveyor) @@ -145,6 +148,13 @@ def new_ci_test(self): self.assertIn('CONAN_CHANNEL: "testing"', gitlab) self.assertIn('CONAN_GCC_VERSIONS: "5"', gitlab) + circleci = load(os.path.join(root, ".circleci", "config.yml")) + self.assertIn("CONAN_UPLOAD: \"myurl\"", circleci) + self.assertIn('CONAN_REFERENCE: "MyPackage/1.3"', circleci) + self.assertIn('CONAN_USERNAME: "myuser"', circleci) + self.assertIn('CONAN_CHANNEL: "testing"', circleci) + self.assertIn('CONAN_GCC_VERSIONS: "5"', circleci) + def new_ci_test_partial(self): client = TestClient() root = client.current_folder @@ -158,6 +168,7 @@ def new_ci_test_partial(self): self.assertTrue(os.path.exists(os.path.join(root, ".travis/run.sh"))) self.assertFalse(os.path.exists(os.path.join(root, "appveyor.yml"))) self.assertFalse(os.path.exists(os.path.join(root, ".gitlab-ci.yml"))) + self.assertFalse(os.path.exists(os.path.join(root, ".circleci/config.yml"))) client = TestClient() root = client.current_folder @@ -168,6 +179,7 @@ def new_ci_test_partial(self): self.assertFalse(os.path.exists(os.path.join(root, ".travis/run.sh"))) self.assertTrue(os.path.exists(os.path.join(root, "appveyor.yml"))) self.assertFalse(os.path.exists(os.path.join(root, ".gitlab-ci.yml"))) + self.assertFalse(os.path.exists(os.path.join(root, ".circleci/config.yml"))) client = TestClient() root = client.current_folder @@ -178,6 +190,7 @@ def new_ci_test_partial(self): self.assertTrue(os.path.exists(os.path.join(root, ".travis/run.sh"))) self.assertFalse(os.path.exists(os.path.join(root, "appveyor.yml"))) self.assertFalse(os.path.exists(os.path.join(root, ".gitlab-ci.yml"))) + self.assertFalse(os.path.exists(os.path.join(root, ".circleci/config.yml"))) client = TestClient() root = client.current_folder @@ -193,6 +206,7 @@ def new_ci_test_partial(self): self.assertFalse(os.path.exists(os.path.join(root, ".travis/install.sh"))) self.assertFalse(os.path.exists(os.path.join(root, ".travis/run.sh"))) self.assertFalse(os.path.exists(os.path.join(root, "appveyor.yml"))) + self.assertFalse(os.path.exists(os.path.join(root, ".circleci/config.yml"))) client = TestClient() root = client.current_folder @@ -203,3 +217,30 @@ def new_ci_test_partial(self): self.assertFalse(os.path.exists(os.path.join(root, ".travis/install.sh"))) self.assertFalse(os.path.exists(os.path.join(root, ".travis/run.sh"))) self.assertFalse(os.path.exists(os.path.join(root, "appveyor.yml"))) + self.assertFalse(os.path.exists(os.path.join(root, ".circleci/config.yml"))) + + client = TestClient() + root = client.current_folder + client.run('new MyPackage/1.3@myuser/testing -ciccg') + self.assertTrue(os.path.exists(os.path.join(root, "build.py"))) + self.assertTrue(os.path.exists(os.path.join(root, ".circleci/config.yml"))) + self.assertTrue(os.path.exists(os.path.join(root, ".circleci/install.sh"))) + self.assertTrue(os.path.exists(os.path.join(root, ".circleci/run.sh"))) + self.assertFalse(os.path.exists(os.path.join(root, ".gitlab-ci.yml"))) + self.assertFalse(os.path.exists(os.path.join(root, ".travis.yml"))) + self.assertFalse(os.path.exists(os.path.join(root, ".travis/install.sh"))) + self.assertFalse(os.path.exists(os.path.join(root, ".travis/run.sh"))) + self.assertFalse(os.path.exists(os.path.join(root, "appveyor.yml"))) + + client = TestClient() + root = client.current_folder + client.run('new MyPackage/1.3@myuser/testing -ciccc') + self.assertTrue(os.path.exists(os.path.join(root, "build.py"))) + self.assertTrue(os.path.exists(os.path.join(root, ".circleci/config.yml"))) + self.assertTrue(os.path.exists(os.path.join(root, ".circleci/install.sh"))) + self.assertTrue(os.path.exists(os.path.join(root, ".circleci/run.sh"))) + self.assertFalse(os.path.exists(os.path.join(root, ".gitlab-ci.yml"))) + self.assertFalse(os.path.exists(os.path.join(root, ".travis.yml"))) + self.assertFalse(os.path.exists(os.path.join(root, ".travis/install.sh"))) + self.assertFalse(os.path.exists(os.path.join(root, ".travis/run.sh"))) + self.assertFalse(os.path.exists(os.path.join(root, "appveyor.yml")))