Skip to content

Commit

Permalink
Added configure.py and copy_solution.py and updated README
Browse files Browse the repository at this point in the history
  • Loading branch information
martinbond7 committed Apr 4, 2024
1 parent f04b120 commit 876f931
Show file tree
Hide file tree
Showing 3 changed files with 395 additions and 16 deletions.
66 changes: 50 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- [Debugging](#debugging)
- [VS Code debug](#vs-code-debug)
- [Building an exercise solution](#building-an-exercise-solution)
- [Docker Image](#docker-image)
- [Creating template starter projects](#creating-template-starter-projects)
- [Static analysis using clang-tidy](#static-analysis-using-clang-tidy)
- [Testing support](#testing-support)
Expand Down Expand Up @@ -176,33 +177,66 @@ Linux terminal to run Telnet and must have the graphic WMS window displayed.

# Building an exercise solution

To build any of the exercise solutions run the script:
To build a solution run the command:

```
$ python3 copy_solution.py
```

Select the required solution from the list you are shown.

You may supply the solution number (optionally omitting a leading zero)
on the command line to avoid the interactive prompt.

On loading a solution the script will:

* save and commit your current files using git
* replace all of your source files with those from the the solution
* rebuild the solution

**Note:** If the script cannot save your source files using git then they are
copied to a `src.bak` folder. Only that last set of source files are saved in
the backup folder.

Alternatively you can build any of the exercise solutions using the
`build-one.sh` bash script:

```
$ ./build-one.sh N
```
where `N` is the exercise number.

In the pre-built VM images the solutions are stored in the home folder but a
fodler called `solutions`.
Where *N* is the exercise number. The exercises must be stored in the
workspace folder in one of the following locations:
* A cloned github repo name ending `_exercises`
* An `exercises/solutions`sub-folder in the workspace
* A `solutions`sub-folder in the workspace

When working with a Docker image you will either be given an archive of the
solutions, an archive of the exercises, a web link to download the archive,
or a link to clone a GIT repo.
**NOTE:** this script will copy all files in the `src` and
`include` directories to a `src.bak` directory in the workspace;
any files already present in `src.bak` will be deleted.

Once you have identified your local copy of the solutions you should
copy this folder into the workspace and rename it to `solutions`.
## Docker Image

A workspace sub-folder called `solutions` is always used in preference to any other
location.
Inside your workspace subfolder called `scripts` there is
a `configure.py` script that can be used to copy the course exercises
into your workspace.

**NOTE:** building a solution will copy all files in the `src` directory to
the `src.bak` directory having removed any files already present in `src.bak`.
You can run this script at any time from your host environment
or, once you've opened the project workspace, from a terminal
window in VS Code using the command:

```
$ python3 configure.py
```

The `build-one.sh` script supports the `--help` option for more information.
The script will supply a list of suitable courses for you to choose from and
these exercises will be download from the appropriate Feabhas GitHub repo.

Do not use the `--all` option as this will build each solution in turn and is used
as part of our Continuous Integration testing.
You will now have a sub-folder with a name of the form `<COURSE>_exercises`.
where `<COURSE>` is the unique code for your course (such as cpp11-501).

If you know you course code you can supply this as a command line parameter
to the script.

# Creating template starter projects

Expand Down
167 changes: 167 additions & 0 deletions scripts/configure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
#!/usr/bin/python3
import os
import re
import shutil
import ssl
import sys
import urllib.request
import urllib.error
from zipfile import ZipFile
from io import BytesIO
from pathlib import Path


class ConfigureError(Exception):
pass


class Config:
repo_suffix = '_exercises'
url_code_base = 'http://codeload.github.com/feabhas/'
url_code_path = '/zip/refs/heads/main'
branch = 'main'
course_pattern = re.compile('^[a-z][-a-z+0-9]*-[0-9]{3}$', re.IGNORECASE)
exercise_pattern = re.compile(f'^(exercises|solutions|.*{repo_suffix})')


class Courses:
target = [
'C-501 "C for Real - Time Embedded Developers"',
'AC-401 "Advanced C Programming"',
'C++11-501 "Modern C++ for Embedded Systems (C++11/14/17)"',
'AC++11-501 "Advanced Modern C++ for Embedded Developers (C++11/14/17)"',
]
host = [
'C++11-502 "Real-Time Modern C++ (C++11/14/17")',
'AC++11-401 "Transitioning to Modern C++ (C++11/14/17)"',
'AC++11-502 "Advanced Real-Time Modern C++ (C++11/14/17)"',
'AC++11-CACHE-502 "Advanced Modern C++ Customised for Cache & Performance"',
'AC++20-302 "Migrating to C++20/23"',
'TDDC-301 "TDD for Embedded C"',
'TDDC++-301 "TDD for Embedded C++"',
'DP11-403 "Design Patterns in Modern C++"',
]


def read_course_code(selected: str = ''):
if selected:
print(f'Using command line argument "{selected}"')
course = selected
else:
print('If you know your course code type it now or just\n'
'press the <Enter> key to see a list of available courses.')
course = input('? ').strip().lower()
if not course or Config.course_pattern.search(course):
return course
print(f'"{course}" does not look like a valid course code.\n'
f'A typical course code looks like C-501, AC++11-502 or TDD++-301')


def choose_course(options: list):
print('Suggested courses:')
for n, option in enumerate(options, 1):
print(f'{n:2d}) {option}')
choice = input('Type course code, choice as a number, or q to quit? ').strip().lower()
if not choice or choice.startswith('q'):
return ''
if choice.isdigit():
n = int(choice) - 1
if 0 <= n < len(options):
course = options[n]
return course.split()[0]
return choice


def check_exercise_exists(path):
if path.exists():
print(f'Solutions folder "{path.name}" already exists')
choice = input('Do you want to replace this folder [y/N]? ').strip().lower()
if not choice.startswith('y'):
raise ConfigureError(f'{path.name} already present')
shutil.rmtree(path)
exercises = [p for p in sorted(path.parent.iterdir()) if Config.exercise_pattern.search(p.name)]
if exercises:
print('The following exercise folders are already present:')
print(' ', ', '.join(e.name for e in exercises))
choice = input('Do you want to delete these folders [y/N]? ').strip().lower()
if not choice.startswith('y'):
raise ConfigureError(f'You can only have one set of exercises in the workspace')
for ex in exercises:
shutil.rmtree(ex)


def save_archive(course: str, repo: Path, url: str):
try:
print(f'Downloading archive "{repo.name}.zip"\n {url}')
with urllib.request.urlopen(url) as fp:
zipfile = ZipFile(BytesIO(fp.read()))
for name in zipfile.namelist():
if name.startswith('.git'):
continue
zipfile.extract(name)
unzip = Path() / f'{repo}-{Config.branch}'
unzip.rename(repo)
except ssl.SSLError as ex:
raise ConfigureError(f'''{ex}
This is a known issue for macOS users which is documented in
/Applications/Python 3.6/ReadMe.rtf
Just browse to "Applications/Python 3.6" and double-click "Install Certificates.command"''')
except urllib.error.HTTPError:
raise ConfigureError(f'Cannot find GitHub repo for course "{course}"\n'
f'Please check your spelling or ask your instructor for help')


def download_course(course: str):
repo = course + Config.repo_suffix
url = Config.url_code_base + repo + Config.url_code_path
path = Path(repo)
check_exercise_exists(path)
save_archive(course, path, url)


def course_repo(code: str):
return code.replace('++', 'pp').lower()


def do_fetch_exercises(target: bool, selected: str):
course = read_course_code(selected)
if not course:
course = choose_course(Courses.target if target else Courses.host)
if not course:
raise ConfigureError('No course chosen')
repo = course_repo(course)
download_course(repo)


def cd_workspace():
cwd = Path().absolute()
for wd in cwd, cwd.parent:
if (wd / 'src').exists():
os.chdir(wd)
return (wd / 'system').is_dir()
raise ConfigureError('Please run this script from within the workspace root folder')


def main():
status = 1
try:
target = cd_workspace()
do_fetch_exercises(target, sys.argv[1] if len(sys.argv) > 1 else '')
print('\nCourse exercises configured OK')
status = 0
except ConfigureError as ex:
print('\n', ex, sep='', file=sys.stderr)
print('Course exercises have NOT been configured')
except (KeyboardInterrupt, EOFError):
pass
except Exception as ex:
print(ex, file=sys.stderr)
import traceback
print(traceback.format_exc(), file=sys.stderr)
if sys.platform == 'win32' and not os.getenv('PROMPT'):
input('Press <Enter> to close the window')
exit(status)


if __name__ == '__main__':
main()
Loading

0 comments on commit 876f931

Please sign in to comment.