diff --git a/make.py b/make.py index e461b49..72c1ded 100644 --- a/make.py +++ b/make.py @@ -1,7 +1,7 @@ #! python3 -import glob import os +from pathlib import Path from umake import make @@ -12,10 +12,10 @@ class Config: IGNORE_APPS = [] JOB_COUNT = int(os.cpu_count() * 0.8) # Concurent jobs (multi-processing) - BIN_DIR = 'bin' # Output directory (binaries) - INCLUDE_DIR = 'include' # Include directory (header files) - OBJECT_DIR = 'obj' # Temporary directory (object files) - SOURCE_DIR = 'src' # Source directories + BIN_DIR = Path('bin') # Output directory (binaries) + INCLUDE_DIR = Path('include') # Include directory (header files) + OBJECT_DIR = Path('obj') # Temporary directory (object files) + SOURCE_DIR = Path('src') # Source directories COMMON_FLAGS = '-std=c++17' # Flags used for comiling and linking COMMON_DEBUG_FLAGS = '-g' # Flags added in debug mode @@ -23,8 +23,8 @@ class Config: COMPILE_FLAGS = f'-Wall -I{INCLUDE_DIR}' # Flags added for compiling (recommandation : `pkg-config --cflags`) LINK_FLAGS = '' # Flags added for linking (recommandation : `pkg-config --libs`) - CPP_HEADERS = glob.glob(os.path.join(INCLUDE_DIR, '**', '*.hpp'), recursive=True) - CPP_SOURCES = glob.glob(os.path.join(SOURCE_DIR, '**', '*.cpp'), recursive=True) + CPP_HEADERS = INCLUDE_DIR.rglob('*.hpp') + CPP_SOURCES = SOURCE_DIR.rglob('*.cpp') def main(): diff --git a/umake.py b/umake.py index deada08..35392f2 100755 --- a/umake.py +++ b/umake.py @@ -12,27 +12,26 @@ from typing import List class Config: - CC = 'g++' - APPS = ['app_name'] - IGNORE_APPS = [] - JOB_COUNT = 1 + CC = 'g++' # Compiler to call + APPS = ['app_name'] # Output binaries (need to be found as .cpp directly in SOURCE_DIR) + JOB_COUNT = int(os.cpu_count() * 0.8) # Concurent jobs (multi-processing) - BIN_DIR = 'bin' - INCLUDE_DIR = 'include' - OBJECT_DIR = 'obj' - SOURCE_DIR = 'src' + BIN_DIR = Path('bin') # Output directory (binaries) + INCLUDE_DIR = Path('include') # Include directory (header files) + OBJECT_DIR = Path('obj') # Temporary directory (object files) + SOURCE_DIR = Path('src') # Source directories - COMMON_FLAGS = '-std=c++17' - COMMON_DEBUG_FLAGS = '-g' - COMMON_RELEASE_FLAGS = '-O2 -flto' - COMPILE_FLAGS = f'-Wall -I{INCLUDE_DIR}' - LINK_FLAGS = '' + COMMON_FLAGS = '-std=c++17' # Flags used for comiling and linking + COMMON_DEBUG_FLAGS = '-g' # Flags added in debug mode + COMMON_RELEASE_FLAGS = '-O2 -flto' # Flags added in release mode + COMPILE_FLAGS = f'-Wall -I{INCLUDE_DIR}' # Flags added for compiling (recommandation : `pkg-config --cflags`) + LINK_FLAGS = '' # Flags added for linking (recommandation : `pkg-config --libs`) - CPP_HEADERS = [] - CPP_SOURCES = [] + CPP_HEADERS = INCLUDE_DIR.rglob('*.hpp') + CPP_SOURCES = SOURCE_DIR.rglob('*.cpp') -def get_hash(path: str) -> str: +def get_hash(path: Path) -> str: with open(path, 'r') as hashing_file: hash_obj = hashlib.md5() hash_obj.update(hashing_file.read().encode()) @@ -55,19 +54,20 @@ def make(config: Config): # Update flags and directories for mode debug/release if arguments.type == 'debug': config.COMMON_FLAGS += ' ' + config.COMMON_DEBUG_FLAGS - config.OBJECT_DIR = os.path.join(config.OBJECT_DIR, 'debug') + config.OBJECT_DIR = config.OBJECT_DIR / 'debug' else: config.COMMON_FLAGS += ' ' + config.COMMON_RELEASE_FLAGS - config.OBJECT_DIR = os.path.join(config.OBJECT_DIR, 'release') + config.OBJECT_DIR = config.OBJECT_DIR / 'release' # Create list of source to process (tuple[source_path, object_path]) - compile_list = [(source_file, source_file[:-4].replace(config.SOURCE_DIR, config.OBJECT_DIR) + '.o') + compile_list = [(Path(source_file), + config.OBJECT_DIR / source_file.parent.relative_to(config.SOURCE_DIR) / (source_file.stem + '.o')) for source_file in config.CPP_SOURCES] todo_list = [] hash_dict = {} - if os.path.exists(os.path.join(config.OBJECT_DIR, 'hash.json')): - with open(os.path.join(config.OBJECT_DIR, 'hash.json'), 'r') as hash_file: + if (config.OBJECT_DIR / 'hash.json').exists(): + with open(config.OBJECT_DIR / 'hash.json', 'r') as hash_file: hash_dict = json.loads(hash_file.read()) # Check header files (if any modification is done all source file will be processed to avoid any problem) @@ -75,18 +75,19 @@ def make(config: Config): header_hash_dict = {} for header_path in config.CPP_HEADERS: header_hash = get_hash(header_path) - if header_path not in hash_dict or hash_dict[header_path] != header_hash: - header_hash_dict[header_path] = header_hash + if str(header_path) not in hash_dict or hash_dict[str(header_path)] != header_hash: + header_hash_dict[str(header_path)] = header_hash header_changed = True # Check source file and generate compilation commands to execute for source_path, object_path in compile_list: - cmd = ' '.join([config.CC, config.COMMON_FLAGS, config.COMPILE_FLAGS, source_path, '-c', '-o', object_path]) - if not os.path.exists(os.path.dirname(object_path)): - os.makedirs(os.path.dirname(object_path)) + cmd = ' '.join([config.CC, config.COMMON_FLAGS, config.COMPILE_FLAGS, + str(source_path), '-c', '-o', str(object_path)]) + if not object_path.parent.exists(): + object_path.parent.mkdir(parents=True) source_hash = get_hash(source_path) - if (header_changed or not os.path.exists(object_path) - or source_path not in hash_dict or hash_dict[source_path] != source_hash): + if (header_changed or not object_path.exists() + or str(source_path) not in hash_dict or hash_dict[str(source_path)] != source_hash): todo_list.append((source_path, source_hash, cmd)) continue @@ -105,14 +106,14 @@ def make(config: Config): if oldest_job.returncode != 0: error_path = job_path break - hash_dict[job_path] = job_hash # Update hash if no error + hash_dict[str(job_path)] = job_hash # Update hash if no error if error_path is None: for job_path, job_hash, job in jobs: # Wait the last jobs to finish job.wait() if job.returncode != 0: error_path = job_path break - hash_dict[job_path] = job_hash # Update hash if no error + hash_dict[str(job_path)] = job_hash # Update hash if no error else: # Single-process for source_path, source_hash, cmd in todo_list: print(cmd) @@ -120,29 +121,31 @@ def make(config: Config): if complete.returncode != 0: error_path = source_path break - hash_dict[source_path] = source_hash # Update hash if no error + hash_dict[str(source_path)] = source_hash # Update hash if no error if error_path: print(f'Error compiling {error_path}') - with open(os.path.join(config.OBJECT_DIR, 'hash.json'), 'w') as hash_file: + with open(config.OBJECT_DIR / 'hash.json', 'w') as hash_file: hash_file.write(json.dumps(hash_dict, indent=1)) sys.exit(1) # Running linking processes - if not os.path.exists(config.BIN_DIR): - os.makedirs(config.BIN_DIR) - for app_name in config.APPS: - if 'IGNORE_APPS' in config.__dict__ and app_name in config.IGNORE_APPS: + if not config.BIN_DIR.exists(): + config.BIN_DIR.mkdir(parents=True) + all_app_objects = [config.OBJECT_DIR / Path(app_path).parent / (Path(app_path).stem + '.o') + for app_path in config.APPS] + for app_path, app_object_path in zip(config.APPS, all_app_objects): + if 'IGNORE_APPS' in config.__dict__ and app_path in config.IGNORE_APPS: continue - app_path = Path(config.BIN_DIR, app_name) - if not app_path.parent.exists(): - app_path.parent.mkdir(parents=True) - app_objects = [file_name + '.o' for file_name in config.APPS if file_name != app_name] - object_files = [object_path for _, object_path in compile_list - if os.path.basename(object_path) not in app_objects] - cmd = ' '.join([config.CC, config.COMMON_FLAGS, *object_files, '-o', str(app_path), config.LINK_FLAGS]) + bin_path = config.BIN_DIR / app_path + if not bin_path.parent.exists(): + bin_path.parent.mkdir(parents=True) + object_files = [str(object_path) for _, object_path in compile_list + if object_path not in all_app_objects] + object_files.append(str(app_object_path)) + cmd = ' '.join([config.CC, config.COMMON_FLAGS, *object_files, '-o', str(bin_path), config.LINK_FLAGS]) complete = subprocess.run(cmd, check=False, shell=True) if complete.returncode != 0: - error_path = app_name + error_path = app_path if error_path: with open(os.path.join(config.OBJECT_DIR, 'hash.json'), 'w') as hash_file: hash_file.write(json.dumps(hash_dict, indent=1)) @@ -152,5 +155,5 @@ def make(config: Config): # Updating header hashes only if everything compiled and linked correctly for header_path in header_hash_dict: hash_dict[header_path] = header_hash_dict[header_path] - with open(os.path.join(config.OBJECT_DIR, 'hash.json'), 'w') as hash_file: + with open(config.OBJECT_DIR / 'hash.json', 'w') as hash_file: hash_file.write(json.dumps(hash_dict, indent=1))