Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,26 @@ concore stop

For detailed CLI documentation, see [concore_cli/README.md](concore_cli/README.md).

## Configuration

_concore_ supports customization through configuration files in the `CONCOREPATH` directory (defaults to the _concore_ installation directory):

- **concore.tools** - Override tool paths (one per line, `KEY=value` format):
```
CPPEXE=/usr/local/bin/g++-12
PYTHONEXE=/usr/bin/python3.11
VEXE=/opt/iverilog/bin/iverilog
OCTAVEEXE=/snap/bin/octave
```
Supported keys: `CPPWIN`, `CPPEXE`, `VWIN`, `VEXE`, `PYTHONEXE`, `PYTHONWIN`, `MATLABEXE`, `MATLABWIN`, `OCTAVEEXE`, `OCTAVEWIN`

- **concore.octave** - Treat `.m` files as Octave instead of MATLAB (presence = enabled)
- **concore.mcr** - MATLAB Compiler Runtime path (single line)
- **concore.sudo** - Docker command override (e.g., `docker` instead of `sudo docker`)
- **concore.repo** - Docker repository override

Tool paths can also be set via environment variables (e.g., `CONCORE_CPPEXE=/usr/bin/g++`). Priority: config file > env var > defaults.

For a detailed and more scientific documentation, please read our extensive [open-access research paper on CONTROL-CORE](https://doi.org/10.1109/ACCESS.2022.3161471). This paper has a complete discussion on the CONTROL-CORE architecture and deployment, together with the commands to execute the studies in different programming languages and programming environments (Ubuntu, Windows, MacOS, Docker, and distributed execution).


Expand Down
120 changes: 73 additions & 47 deletions mkconcore.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,50 +75,63 @@
import shlex # Added for POSIX shell escaping

# input validation helper
def safe_name(value, context, allow_path=False):
"""
Validates that the input string does not contain characters dangerous
for filesystem paths or shell command injection.
"""
if not value:
raise ValueError(f"{context} cannot be empty")
# blocks control characters and shell metacharacters
# allow path separators and drive colons for full paths when needed
if allow_path:
pattern = r'[\x00-\x1F\x7F*?"<>|;&`$\'()]'
else:
# blocks path traversal (/, \, :) in addition to shell metacharacters
pattern = r'[\x00-\x1F\x7F\\/:*?"<>|;&`$\'()]'
if re.search(pattern, value):
raise ValueError(f"Unsafe {context}: '{value}' contains illegal characters.")
return value

MKCONCORE_VER = "22-09-18"

SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))

def _resolve_concore_path():
script_concore = os.path.join(SCRIPT_DIR, "concore.py")
if os.path.exists(script_concore):
return SCRIPT_DIR
cwd_concore = os.path.join(os.getcwd(), "concore.py")
if os.path.exists(cwd_concore):
return os.getcwd()
return SCRIPT_DIR

GRAPHML_FILE = sys.argv[1]
TRIMMED_LOGS = True
CONCOREPATH = _resolve_concore_path()
CPPWIN = "g++" #Windows C++ 6/22/21
CPPEXE = "g++" #Ubuntu/macOS C++ 6/22/21
VWIN = "iverilog" #Windows verilog 6/25/21
VEXE = "iverilog" #Ubuntu/macOS verilog 6/25/21
PYTHONEXE = "python3" #Ubuntu/macOS python3
PYTHONWIN = "python" #Windows python3
MATLABEXE = "matlab" #Ubuntu/macOS matlab
MATLABWIN = "matlab" #Windows matlab
OCTAVEEXE = "octave" #Ubuntu/macOS octave
OCTAVEWIN = "octave" #Windows octave
def safe_name(value, context, allow_path=False):
"""
Validates that the input string does not contain characters dangerous
for filesystem paths or shell command injection.
"""
if not value:
raise ValueError(f"{context} cannot be empty")
# blocks control characters and shell metacharacters
# allow path separators and drive colons for full paths when needed
if allow_path:
pattern = r'[\x00-\x1F\x7F*?"<>|;&`$\'()]'
else:
# blocks path traversal (/, \, :) in addition to shell metacharacters
pattern = r'[\x00-\x1F\x7F\\/:*?"<>|;&`$\'()]'
if re.search(pattern, value):
raise ValueError(f"Unsafe {context}: '{value}' contains illegal characters.")
return value

MKCONCORE_VER = "22-09-18"

SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))

def _load_tool_config(filepath):
tools = {}
with open(filepath, "r") as f:
for line in f:
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
k, v = line.split("=", 1)
k, v = k.strip(), v.strip()
if v:
tools[k] = v
return tools

def _resolve_concore_path():
script_concore = os.path.join(SCRIPT_DIR, "concore.py")
if os.path.exists(script_concore):
return SCRIPT_DIR
cwd_concore = os.path.join(os.getcwd(), "concore.py")
if os.path.exists(cwd_concore):
return os.getcwd()
return SCRIPT_DIR

GRAPHML_FILE = sys.argv[1]
TRIMMED_LOGS = True
CONCOREPATH = _resolve_concore_path()
CPPWIN = os.environ.get("CONCORE_CPPWIN", "g++") #Windows C++ 6/22/21
CPPEXE = os.environ.get("CONCORE_CPPEXE", "g++") #Ubuntu/macOS C++ 6/22/21
VWIN = os.environ.get("CONCORE_VWIN", "iverilog") #Windows verilog 6/25/21
VEXE = os.environ.get("CONCORE_VEXE", "iverilog") #Ubuntu/macOS verilog 6/25/21
PYTHONEXE = os.environ.get("CONCORE_PYTHONEXE", "python3") #Ubuntu/macOS python3
PYTHONWIN = os.environ.get("CONCORE_PYTHONWIN", "python") #Windows python3
MATLABEXE = os.environ.get("CONCORE_MATLABEXE", "matlab") #Ubuntu/macOS matlab
MATLABWIN = os.environ.get("CONCORE_MATLABWIN", "matlab") #Windows matlab
OCTAVEEXE = os.environ.get("CONCORE_OCTAVEEXE", "octave") #Ubuntu/macOS octave
OCTAVEWIN = os.environ.get("CONCORE_OCTAVEWIN", "octave") #Windows octave
M_IS_OCTAVE = False #treat .m as octave
MCRPATH = "~/MATLAB/R2021a" #path to local Ubunta Matlab Compiler Runtime
DOCKEREXE = "sudo docker"#assume simple docker install
Expand Down Expand Up @@ -147,13 +160,26 @@ def _resolve_concore_path():
with open(CONCOREPATH+"/concore.repo", "r") as f:
DOCKEREPO = f.readline().strip() #docker id for repo

if os.path.exists(CONCOREPATH+"/concore.tools"):
_tools = _load_tool_config(CONCOREPATH+"/concore.tools")
CPPWIN = _tools.get("CPPWIN", CPPWIN)
CPPEXE = _tools.get("CPPEXE", CPPEXE)
VWIN = _tools.get("VWIN", VWIN)
VEXE = _tools.get("VEXE", VEXE)
PYTHONEXE = _tools.get("PYTHONEXE", PYTHONEXE)
PYTHONWIN = _tools.get("PYTHONWIN", PYTHONWIN)
MATLABEXE = _tools.get("MATLABEXE", MATLABEXE)
MATLABWIN = _tools.get("MATLABWIN", MATLABWIN)
OCTAVEEXE = _tools.get("OCTAVEEXE", OCTAVEEXE)
OCTAVEWIN = _tools.get("OCTAVEWIN", OCTAVEWIN)


prefixedgenode = ""
sourcedir = sys.argv[2]
outdir = sys.argv[3]

# Validate outdir argument (allow full paths)
safe_name(outdir, "Output directory argument", allow_path=True)
# Validate outdir argument (allow full paths)
safe_name(outdir, "Output directory argument", allow_path=True)

if not os.path.isdir(sourcedir):
logging.error(f"{sourcedir} does not exist")
Expand Down Expand Up @@ -1227,4 +1253,4 @@ def cleanup_script_files():
os.chmod(outdir+"/clear",stat.S_IRWXU)
os.chmod(outdir+"/maxtime",stat.S_IRWXU)
os.chmod(outdir+"/params",stat.S_IRWXU)
os.chmod(outdir+"/unlock",stat.S_IRWXU)
os.chmod(outdir+"/unlock",stat.S_IRWXU)
76 changes: 76 additions & 0 deletions tests/test_tool_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import pytest
import os

# can't import mkconcore directly (sys.argv at module level), so we duplicate the parser
def _load_tool_config(filepath):
tools = {}
with open(filepath, "r") as f:
for line in f:
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
k, v = line.split("=", 1)
k, v = k.strip(), v.strip()
if v:
tools[k] = v
return tools


class TestLoadToolConfig:

def test_basic_overrides(self, temp_dir):
cfg = os.path.join(temp_dir, "concore.tools")
with open(cfg, "w") as f:
f.write("CPPEXE=/usr/local/bin/g++-12\n")
f.write("PYTHONEXE=/usr/bin/python3.11\n")

tools = _load_tool_config(cfg)
assert tools["CPPEXE"] == "/usr/local/bin/g++-12"
assert tools["PYTHONEXE"] == "/usr/bin/python3.11"
assert "VEXE" not in tools

def test_comments_and_blanks_ignored(self, temp_dir):
cfg = os.path.join(temp_dir, "concore.tools")
with open(cfg, "w") as f:
f.write("# custom tool paths\n")
f.write("\n")
f.write("OCTAVEEXE = /snap/bin/octave\n")
f.write("# MATLABEXE = /opt/matlab/bin/matlab\n")

tools = _load_tool_config(cfg)
assert tools["OCTAVEEXE"] == "/snap/bin/octave"
assert "MATLABEXE" not in tools

def test_empty_value_skipped(self, temp_dir):
cfg = os.path.join(temp_dir, "concore.tools")
with open(cfg, "w") as f:
f.write("CPPWIN=\n")
f.write("VEXE = \n")

tools = _load_tool_config(cfg)
assert "CPPWIN" not in tools
assert "VEXE" not in tools

def test_value_with_equals_sign(self, temp_dir):
cfg = os.path.join(temp_dir, "concore.tools")
with open(cfg, "w") as f:
f.write("CPPEXE=C:\\Program Files\\g++=fast\n")

tools = _load_tool_config(cfg)
assert tools["CPPEXE"] == "C:\\Program Files\\g++=fast"

def test_whitespace_around_key_value(self, temp_dir):
cfg = os.path.join(temp_dir, "concore.tools")
with open(cfg, "w") as f:
f.write(" VWIN = C:\\iverilog\\bin\\iverilog.exe \n")

tools = _load_tool_config(cfg)
assert tools["VWIN"] == "C:\\iverilog\\bin\\iverilog.exe"

def test_empty_file(self, temp_dir):
cfg = os.path.join(temp_dir, "concore.tools")
with open(cfg, "w") as f:
pass

tools = _load_tool_config(cfg)
assert tools == {}