Skip to content

Commit

Permalink
Integrate SPL project generator
Browse files Browse the repository at this point in the history
  • Loading branch information
cuinixam committed Sep 27, 2022
1 parent 57c8799 commit b9f6b3e
Show file tree
Hide file tree
Showing 52 changed files with 717 additions and 112 deletions.
3 changes: 0 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
{
"python.testing.pytestArgs": [
"./python"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"python.formatting.provider": "autopep8",
Expand Down
62 changes: 11 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,63 +4,23 @@ In this repository we construct the <SPL-core> functionality that can be i

## CI (Continuous Integration)

* [![selftests](https://github.com/avengineers/spl/actions/workflows/test.yml/badge.svg)](https://github.com/avengineers/spl/actions/workflows/test.yml)
* [![selftests](https://github.com/avengineers/spl/actions/workflows/gate.yml/badge.svg)](https://github.com/avengineers/spl/actions/workflows/gate.yml)

## Preparation
## Getting started

You can add SPL to your CMake project by running:
First thing to do is to install all SPL dependencies by opening a terminal and running:

```powershell
Invoke-Expression "& { $(Invoke-RestMethod https://raw.githubusercontent.com/avengineers/SPL/develop/install.ps1) } v1.0.9"
```

This call will download the installer script of a given version (for latest-greatest use "develop") and execute it. It will take care of downloading all mandatory dependencies. The same command can be used to up- or downgrade the version.

Our SPL works best with VSCode and some extensions. Make sure to install 'optional' tools as well by running a powershell command from your project root:

```powershell
.\build\spl-core\powershell\spl.ps1 -install -installOptional
powershell -File install.ps1 -useCurrent
```

In VS Code you need to install the following extensions. Hit `Ctrl+Shift+x` to search and install them.
* CMake Tools
* C/C++ Extension Pack

### Additional Files

There are some recommended files that should be placed in the root of your project to make it easier to use. See SPLDemo as reference:
* https://github.com/avengineers/SPLDemo/blob/develop/CMakeLists.txt (to include the `SPL` and see how the basic project structure looks like)
* https://github.com/avengineers/SPLDemo/blob/develop/build.bat (to make SPL calls easier, by simply passing all arguments; in case SPL is not downloaded it will do this for the user)
* https://github.com/avengineers/SPLDemo/blob/develop/install-spl.ps1 (activating auto download of SPL will require something similar to this)
* https://github.com/avengineers/SPLDemo/blob/develop/dependencies.json (define other dependencies to download)

## Configuration

There are multiple configurations available that you have to consider for your SPL usage.

### .vscode/cmake-variants.json

lorem ipsum
### .vscode/settings.json

lorem ipsum
Now you can just create a new SPL project. We recommend using the project creator wrapper to generate a tiny SPL project.

### dependencies.json

lorem ipsum

### tools/splExtensions/powershell/setup scripts

lorem ipsum

## TDD (Test Driven Development) and Unit Testing

In order to develop software using TDD, you need to [write and run unit tests](doc/unitTesting.md).

## Debugging

In case your unit tests are not sufficient enough and a bug was found that is not covered by an automated test, you can also debug your software. By stepping through your software units code line-by-line, you can see its behavior on your PC. [see details here](doc/debugging.md)
**Example:** create a project called `MyProject` with a flavor `FLV1/SYS1` under `C:\temp`
```
.\project_creator.bat --name MyProject --variant FLV1/SYS1 --out_dir C:\temp
```

## Build Binaries
Execute `.\project_creator.bat --help` to see all available options.

In order to build the project's binaries you have two options: Visual Studio Code or command line interface, preference: Visual Studio Code. [see details here](doc/build.md)
Note: one can use the `--variant` argument several times to create a project with multiple variants.
15 changes: 0 additions & 15 deletions cmake/common.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,6 @@ macro(_spl_get_absolute_path out in)
endif()
endmacro()

macro(spl_checkout_git_submodules)
# first time checkout submodules, ignore if existing
execute_process(
COMMAND git submodule update --init --recursive
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
ERROR_QUIET
)

# consecutive times just pull changes
execute_process(
COMMAND git submodule update --recursive
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
endmacro()

macro(spl_add_component component_path)
_spl_slash_to_underscore(component_name ${component_path})
add_subdirectory(${CMAKE_SOURCE_DIR}/${component_path})
Expand Down
2 changes: 1 addition & 1 deletion cmake/spl.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/common.cmake)
# Define the SPL Core root directory to be used to refer to files
# relative to the repository root.
set(SPL_CORE_ROOT_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/..)
set(SPL_CORE_PYTHON_MODULES_DIRECTORY ${SPL_CORE_ROOT_DIRECTORY}/python)
set(SPL_CORE_PYTHON_MODULES_DIRECTORY ${SPL_CORE_ROOT_DIRECTORY}/src)

if(PIP_INSTALL_REQUIREMENTS)
run_pip("${PIP_INSTALL_REQUIREMENTS}" $ENV{SPL_PIP_REPOSITORY} $ENV{SPL_PIP_TRUSTED_HOST})
Expand Down
46 changes: 24 additions & 22 deletions dependencies.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
{
"mandatory": {
"python": [
"gcovr==5.2",
"conan==1.50.0",
"kconfiglib==14.1.0",
"autopep8==1.6.0",
"pytest==7.1.2",
"hammocking==0.2.3"
],
"python_trusted_hosts": [
"pypi.org",
"files.pythonhosted.org"
],
"scoop": [
"cmake",
"mingw-winlibs-llvm-ucrt",
"python"
],
"scoop_repos": [
"versions@https://github.com/ScoopInstaller/Versions"
]
}
"mandatory": {
"python": [
"gcovr==5.2",
"conan==1.50.0",
"kconfiglib==14.1.0",
"autopep8==1.6.0",
"pytest==7.1.2",
"hammocking==0.2.3",
"textx==3.0.0",
"cookiecutter==2.1.1"
],
"python_trusted_hosts": [
"pypi.org",
"files.pythonhosted.org"
],
"scoop": [
"cmake",
"mingw-winlibs-llvm-ucrt",
"python"
],
"scoop_repos": [
"versions@https://github.com/ScoopInstaller/Versions"
]
}
}
10 changes: 10 additions & 0 deletions project_creator.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@echo off

set this_dir=%~dp0

pushd %this_dir%

set PYTHONPATH=%this_dir%;%PYTHONPATH%
python src/creator/creator.py %*

popd
3 changes: 3 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[pytest]
testpaths =
tests
File renamed without changes.
Empty file added src/common/__init__.py
Empty file.
17 changes: 17 additions & 0 deletions src/common/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from pathlib import Path


def to_path(input_path: str, check_if_exists: bool = True) -> Path:
return_path = Path(input_path)
if not check_if_exists or return_path.exists():
return return_path.absolute()
else:
raise FileNotFoundError(input_path)


def existing_path(input_path: str) -> Path:
return to_path(input_path, True)


def non_existing_path(input_path: str) -> Path:
return to_path(input_path, False)
Empty file added src/creator/__init__.py
Empty file.
84 changes: 84 additions & 0 deletions src/creator/creator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import argparse
import dataclasses
import logging
from pathlib import Path
from typing import Dict, List

from cookiecutter.main import cookiecutter

from src.common.common import existing_path
from src.creator.project_artifacts import ProjectArtifacts


@dataclasses.dataclass
class Variant:
flavor: str
subsystem: str

@classmethod
def from_string(cls, variant: str):
return cls(*variant.split('/'))


class ProjectGenerator:
def __init__(self, name: str, variants: List[Variant]):
self.logger = logging.getLogger(__name__)
self.project_description = self.create_project_description(name, variants)

@staticmethod
def create_project_description(name: str, variants: List[Variant]) -> Dict:
project_description = {'name': name, 'variants': {}}
for index, variant in enumerate(variants):
project_description['variants'][f"{index}"] = {
'flavor': variant.flavor,
'subsystem': variant.subsystem
}
return project_description

@property
def project_template_path(self) -> Path:
return Path(__file__).parent.joinpath('templates/project')

@property
def variant_template_path(self) -> Path:
return Path(__file__).parent.joinpath('templates/variant')

@property
def project_name(self) -> str:
return self.project_description['name']

def materialize(self, out_dir: Path) -> Path:
project_artifacts = ProjectArtifacts(out_dir.joinpath(self.project_name))
result_path = cookiecutter(str(self.project_template_path),
output_dir=f"{out_dir}",
no_input=True,
extra_context=self.project_description)
for variant in self.project_description['variants'].values():
self.add_variant(variant, project_artifacts.variants_dir)
self.logger.info(f"Project created under: {result_path}")
return Path(result_path)

def add_variant(self, variant_description: Dict, out_dir: Path):
result_path = cookiecutter(str(self.variant_template_path),
output_dir=f"{out_dir}",
no_input=True,
extra_context=variant_description,
overwrite_if_exists=True)
self.logger.info(f"Variant created under: {result_path}")
return Path(result_path)


def main():
parser = argparse.ArgumentParser(description='Project creator')
parser.add_argument('--name', required=True, type=str,
help="Project name. A directory with this name will be created in the <out_dir>.")
parser.add_argument('--variant', required=True, action='append', type=Variant.from_string,
help="Variant name as <flavor>/<subsystem>. E.g. FLV1/SYS1. This option can be used multiple times.")
parser.add_argument('--out_dir', required=True, type=existing_path,
help="Target directory where the project folder will be created.")
arguments = parser.parse_args()
ProjectGenerator(arguments.name, arguments.variant).materialize(arguments.out_dir)


if __name__ == '__main__':
main()
25 changes: 25 additions & 0 deletions src/creator/project_artifacts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from pathlib import Path

from src.creator.variant import Variant


class ProjectArtifacts:
def __init__(self, project_root_dir: Path):
self.project_root_dir = project_root_dir
self.variants_dir = self.root_dir.joinpath('variants')
self.src_dir = self.root_dir.joinpath('src')
self.test_dir = self.root_dir.joinpath('test')

@property
def root_dir(self) -> Path:
return self.project_root_dir

@property
def build_script(self) -> Path:
return self.root_dir.joinpath('build.bat')

def get_build_dir(self, variant: Variant, build_kit: str) -> Path:
return self.root_dir.joinpath(f"build/{variant}/{build_kit}")

def get_variant_dir(self, variant: Variant) -> Path:
return self.variants_dir.joinpath(f"{variant.flavor}/{variant.subsystem}")
10 changes: 10 additions & 0 deletions src/creator/spl_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from typing import List

from src.creator.variant import Variant


class SplProject:
def __init__(self, parent, name: str, variants: List[Variant]):
self.parent = parent
self.name = name
self.variants = variants
9 changes: 9 additions & 0 deletions src/creator/templates/project/cookiecutter.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "project",
"variants": {
"1": {
"flavor": "flavor",
"subsystem": "subsystem"
}
}
}
12 changes: 12 additions & 0 deletions src/creator/templates/project/{{cookiecutter.name}}/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Binary output dir, not recommended to push binary results to Git.
/build

# Output directory of test results
/test/output

# What: Python: byte-compiled / optimized / DLL files
# Why: automatically created by Python during script execution
__pycache__/
*.py[cod]
*$py.class
/.pytest_cache
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[
{
"name": "prod"
},
{
"name": "test",
"toolchainFile": "tools/toolchains/gcc/toolchain.cmake"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"variant": {
"choices": {
{%- for variant in cookiecutter.variants.values() -%}
"{{ variant["flavor"] }}/{{ variant["subsystem"] }}": {
"buildType": "{{ variant["flavor"] }}_{{ variant["subsystem"] }}",
"long": "select to build variant '{{ variant["flavor"] }}/{{ variant["subsystem"] }}'",
"settings": {
"FLAVOR": "{{ variant["flavor"] }}",
"SUBSYSTEM": "{{ variant["subsystem"] }}"
},
"short": "{{ variant["flavor"] }}/{{ variant["subsystem"] }}"
}{{ ", " if not loop.last else "" }}
{%- endfor -%}
},
"default": "{{ cookiecutter.variants["0"]["flavor"] }}/{{ cookiecutter.variants["0"]["subsystem"] }}"
}
}
Loading

0 comments on commit b9f6b3e

Please sign in to comment.