diff --git a/README.md b/README.md index 78bb6b2..10ed02b 100644 --- a/README.md +++ b/README.md @@ -163,10 +163,10 @@ Runs CMake's configure step. - options: - `src_dir`: path to the source directory - `build_dir`: path to the build directory - - `generator`: cmake project generator name (cmake option -G) (e.g. "Unix Makefiles") + - `generator` (optional): cmake project generator name (cmake option -G) (e.g. "Unix Makefiles") + - `toolset` (0.5.0+, optional): cmake toolset (cmake option -T) (e.g. v141 will use VS 2017 toolset, regardless of the generator) - `*`: all the other options will be passed to the `cmake` as `-{parameter}=value` - ###### Build Runs CMake's build step. diff --git a/pydevops/base.py b/pydevops/base.py index 70d2b73..b83586d 100644 --- a/pydevops/base.py +++ b/pydevops/base.py @@ -43,6 +43,17 @@ def apply_aliases(options: dict, aliases: dict): result[target] = alias_value return result +def apply_transforms(options: dict, transforms: set): + # A transform lambda takes in all options and returns a set of options to override. + # For example, take in build_type and output some_stage_build_type=STAGE_[lowercase(build_type)]: + # lambda options: {f"some_stage_build_type": f"STAGE_{options['build_type'].lower()}"} + result = options.copy() + for transform in transforms: + if callable(transform): + new_options = transform(options) + if new_options: + result.update(new_options) + return result @dataclass(frozen=True) class DevopsCfgContext: @@ -74,6 +85,7 @@ def create_context(env, args, options, cfg): defaults = expand_defaults(cfg.defaults, DevopsCfgContext(options)) options = {**defaults, **options} options = apply_aliases(options, cfg.aliases) + options = apply_transforms(options, cfg.transforms) return Context(env=env, args=args, options=options) diff --git a/pydevops/cmake.py b/pydevops/cmake.py index a5da85e..284559e 100644 --- a/pydevops/cmake.py +++ b/pydevops/cmake.py @@ -18,9 +18,21 @@ def execute(self, ctx: Context): src_dir = ctx.get_param("src_dir") build_dir = ctx.get_param("build_dir") options = ctx.get_options() - generator = options.pop("generator") + generator_options = "" + generator = options.pop("generator", None) + if generator: + generator_options = f"-G {generator}" + toolset = options.pop("toolset", None) + if toolset: + generator_options += f" -T {toolset}" + preset = options.pop("preset", None) + if preset: + generator_options += f" --preset {preset}" + else: + # This is a very simplified behavior, but it works for us + generator_options += f"-B {build_dir}" others = _convert_dict_to_kv_params(options) - ctx.sh(f"cmake -S {src_dir} -B {build_dir} -G {generator} {others}") + ctx.sh(f"cmake -S {src_dir} {generator_options} {others}") class Build(Step): @@ -30,7 +42,13 @@ def execute(self, ctx: Context): config = ctx.get_option("config") n_jobs = ctx.get_option_default("j", 1) verbose = ctx.get_option_default("verbose", False) - cmd = f"cmake --build {build_dir} --config {config} -j {n_jobs}" + preset_or_build_dir = "" + preset = ctx.get_option_default("preset", None) + if preset: + preset_or_build_dir = f" --preset {preset}" + else: + preset_or_build_dir = f" {build_dir}" + cmd = f"cmake --build {preset_or_build_dir} --config {config} -j {n_jobs}" if verbose: cmd += " --verbose" if ctx.has_option("target"): @@ -45,13 +63,17 @@ def execute(self, ctx: Context): build_dir = ctx.get_param("build_dir") config = ctx.get_option("C") verbose = ctx.get_option_default("verbose", False) + preset = ctx.get_option_default("preset", None) # Note: tests have to be run from the build dir cwd = os.getcwd() try: - os.chdir(build_dir) + if not preset: + os.chdir(build_dir) cmd = f"ctest -C {config}" if verbose: cmd += " --verbose" + if preset: + cmd += f" --preset {preset}" ctx.sh(cmd) finally: os.chdir(cwd) @@ -62,8 +84,9 @@ def execute(self, ctx: Context): build_dir = ctx.get_param("build_dir") config = ctx.get_option("config") prefix = ctx.get_option("prefix") + build_dir_suffix = ctx.get_option_default("build_dir_suffix", "") # Note: tests have to be run from the build dir - ctx.sh(f"cmake --install {build_dir} " + ctx.sh(f"cmake --install {build_dir}{build_dir_suffix} " f"--prefix {prefix} " f"--config {config}") diff --git a/pydevops/conan.py b/pydevops/conan.py index dd6e0ca..df9c512 100644 --- a/pydevops/conan.py +++ b/pydevops/conan.py @@ -1,5 +1,18 @@ from pydevops.base import Step, Context +import os +class AddLocalIndex(Step): + def execute(self, context: Context): + path_relative = context.get_option("path") + if path_relative is None: + raise ValueError("Option 'path' must be provided for conan.AddLocalIndex step.") + + build_dir = context.get_param("build_dir") + path = os.path.join(build_dir, path_relative) + + cmd = f"conan remote add local-{path_relative} {path} --force" + + context.sh(cmd) class Install(Step): @@ -10,10 +23,10 @@ def execute(self, context: Context): build = context.get_option_default("build", None) profile_file = context.get_option_default("profile", None) conan_home = context.get_option_default("conan_home", None) - cmd = f"conan install --build=missing {src_dir} -if {build_dir} " \ - f"-s build_type={build_type} " - if build: - cmd += f"--build={build} " + cmd = f"conan install --build=missing {src_dir} --update " \ + f"-s build_type={build_type}" + #if build: + # cmd += f"--build={build} " if profile_file: cmd += f"--profile={profile_file}" if conan_home: diff --git a/pydevops/gitdep.py b/pydevops/gitdep.py new file mode 100644 index 0000000..d5cdeca --- /dev/null +++ b/pydevops/gitdep.py @@ -0,0 +1,29 @@ +from pydevops.base import Step, Context +import os +import shutil + +class Fetch(Step): + + def execute(self, context: Context): + repo = context.get_option("repo") + if repo is None: + raise ValueError("Option 'repo' must be provided for gitdeps.Fetch step.") + + revision = context.get_option("revision") + if revision is None: + raise ValueError("Option 'revision' must be provided for gitdeps.Fetch step.") + path_relative = context.get_option("path") + if path_relative is None: + raise ValueError("Option 'path' must be provided for gitdeps.Fetch step.") + + remove_old = context.get_option_default("remove_old", True) + + build_dir = context.get_param("build_dir") + + path = os.path.join(build_dir, path_relative) + + if remove_old and os.path.exists(path): + shutil.rmtree(path) + + cmd = f"git clone --depth 1 --branch {revision} {repo} {path}" + context.sh(cmd) \ No newline at end of file diff --git a/pydevops/utils.py b/pydevops/utils.py index d17d996..553d8f9 100644 --- a/pydevops/utils.py +++ b/pydevops/utils.py @@ -1,11 +1,22 @@ import logging import inspect +import pydevops.version as version + LOGGING_FORMAT = "[%(asctime)s][%(name)s][%(levelname)s] %(message)s" ERROR_FORMAT = LOGGING_FORMAT + " (%(filename)s:%(lineno)d)" LOGGING_LEVEL=logging.DEBUG +def is_version_at_least(minimum_version): + version_parts = [int(part) for part in version.__version__.split('.')] + minimum_parts = [int(part) for part in minimum_version.split('.')] + for v, m in zip(version_parts, minimum_parts): + if v < m: + return False + elif v > m: + return True + return True # Credits: # https://stackoverflow.com/questions/384076/ diff --git a/pydevops/version.py b/pydevops/version.py index 6a9beea..3d18726 100644 --- a/pydevops/version.py +++ b/pydevops/version.py @@ -1 +1 @@ -__version__ = "0.4.0" +__version__ = "0.5.0"