From 7fe619e6ff938d2e8b896b4be29555e3bd3ab232 Mon Sep 17 00:00:00 2001 From: Hanchuan Wu <whc.shmily@gmail.com> Date: Wed, 28 Jul 2021 14:54:17 +0200 Subject: [PATCH] [bin] Rename, move and improve make_installscript, which is used to make an install script --- bin/make_installscript.py | 138 +++++++++++++++++++ bin/util/makeinstallscript.py | 250 ---------------------------------- 2 files changed, 138 insertions(+), 250 deletions(-) create mode 100755 bin/make_installscript.py delete mode 100755 bin/util/makeinstallscript.py diff --git a/bin/make_installscript.py b/bin/make_installscript.py new file mode 100755 index 0000000000..12103d3101 --- /dev/null +++ b/bin/make_installscript.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 + +"""" +Script to generate an install script for a dune-module, +accounting for non-published commits and local changes +""" + +import sys +import os +import argparse +import subprocess + +from util.moduleinfo import getDependencies, getModuleInfo +from util.installscript import ( + addDependencyPatches, + addDependencyVersions, + getDefaultScriptName, + filterDependencies, + makeInstallScript, + makeScriptWriter, + printProgressInfo, + printFoundDependencies, + printFoundVersionInfo, + printFinalMessage, + supportedLanguages, +) + + +if __name__ == "__main__": + + ################### + # parse arguments + parser = argparse.ArgumentParser( + description="This script generates an install script for your module, " + "taking into account non-published commits & changes.\n" + "This expects that all modules are git repositories and " + "have a remote origin URL defined." + ) + parser.add_argument( + "-p", "--path", required=True, help="The path to your dune module" + ) + parser.add_argument( + "-f", + "--filename", + required=False, + help="File in which to write the install script", + ) + parser.add_argument( + "-i", + "--ignoreuntracked", + required=False, + action="store_true", + help="Use this to ignore untracked files present", + ) + parser.add_argument( + "-t", + "--topfoldername", + required=False, + default="DUMUX", + help="Folder that the install script creates upon" + "execution to install the modules in. If you " + "pass an empty string, no folder will be created " + "and installation happens in place.", + ) + parser.add_argument( + "-o", + "--optsfile", + required=False, + help="Provide custom opts file to be used for project " + "configuration. Note that this file is required " + "to be contained and committed in the module or " + "one of its dependencies.", + ) + parser.add_argument( + "-s", + "--skipfolders", + required=False, + nargs="+", + help="a list of module folders to be skipped", + ) + parser.add_argument( + "-l", + "--language", + required=False, + default="python", + choices=supportedLanguages(), + help="Language in which to write the install script", + ) + + cmdArgs = vars(parser.parse_args()) + + modPath = cmdArgs["path"] + skipFolders = cmdArgs["skipfolders"] + if skipFolders: + skipFolders = list(set(skipFolders)) + + printProgressInfo(["Determining the module dependencies"]) + deps = getDependencies(modPath, verbose=True, includeSelf=True) + deps = filterDependencies(deps, skipFolders) + printFoundDependencies(deps) + if not deps: + sys.exit("No dependencies found. Exiting.") + + printProgressInfo(["Determining the module versions"]) + deps = addDependencyVersions(deps, cmdArgs.get("ignoreuntracked", False)) + printFoundVersionInfo(deps) + + printProgressInfo(["Making patches for unpublished & uncommited changes"]) + deps = addDependencyPatches(deps) + + # actual script generation + modPath = os.path.abspath(modPath) + modName = getModuleInfo(modPath, "Module") + printProgressInfo( + [ + "Creating install script for module '{}' in folder '{}'".format( + modName, modPath + ) + ] + ) + + language = cmdArgs["language"] + scriptName = cmdArgs.get("filename", None) + if not scriptName: + scriptName = getDefaultScriptName(modName, language) + + makeInstallScript( + modPath=modPath, + dependencies=deps, + scriptName=scriptName, + writer=makeScriptWriter(language), + topFolderName=cmdArgs.get("topfoldername", None), + optsFile=cmdArgs.get("optsFile", None), + ) + + subprocess.call(["chmod", "u+x", scriptName]) + printProgressInfo([f"Successfully created install script '{scriptName}'"]) + printFinalMessage(scriptName, cmdArgs.get("topfoldername", None)) diff --git a/bin/util/makeinstallscript.py b/bin/util/makeinstallscript.py deleted file mode 100755 index 8324859005..0000000000 --- a/bin/util/makeinstallscript.py +++ /dev/null @@ -1,250 +0,0 @@ -#!/usr/bin/env python3 - -# script to generate an install script for a dune-module, -# accounting for non-published commits and local changes - -import os -import sys -import argparse -import subprocess -from getmoduleinfo import * -from getusedversions import * - -if sys.version_info[0] < 3: - sys.exit("\nError': Python3 required") - - -################### -# parse arguments -parser = argparse.ArgumentParser( - description='This script generates an install script for your dune module,' \ - 'taking into account non-published commits and local changes.\n' \ - 'This expects that your module is a git repository and that a ' \ - 'remote origin exists and has been set already.' -) -parser.add_argument('-p', '--path', required=True, help='The path to your dune module') -parser.add_argument('-f', '--filename', required=False, help='Name of the file in which to write the install script') -parser.add_argument('-i', '--ignoreuntracked', required=False, action='store_true', help='Use this flag to ignore untracked files present in the modules') -parser.add_argument('-t', '--topfoldername', required=False, default='DUMUX', - help='Name of the folder that the install script creates upon execution to install the modules in. '\ - 'If you pass an empty string, no folder will be created and installation happens in place.') -parser.add_argument('-o', '--optsfile', required=False, - help='Provide custom opts file to be used for the call to dunecontrol. '\ - 'Note that this file is required to be contained and committed within the module or its dependencies.') -parser.add_argument('-s', '--skipfolders', required=False, nargs='*', help='a list of module folders to be skipped') -cmdArgs = vars(parser.parse_args()) - - -#################### -# Welcome message -modPath = os.path.abspath( os.path.join(os.getcwd(), cmdArgs['path']) ) -modParentPath = os.path.abspath( os.path.join(modPath, '../') ) -modFolder = os.path.relpath(modPath, modParentPath) - -try: modName = getModuleInfo(modPath, 'Module') -except: - sys.exit("\Error: Could not determine module name. Make sure the path to\n" \ - " your module is correct and that a dune.module file is present.") - -instFileName = 'install_' + modName + '.sh' if not cmdArgs['filename'] else cmdArgs['filename'] -print("\n-- Creating install script '{}' for the module '{}' in the folder '{}'".format(instFileName, modName, modPath)) - - -################### -# get dependencies -print("\n-- Determining the dependencies") -deps = getDependencies(modPath) -if len(deps) > 0: - print("-> Found the following dependencies") - print("\t| {:^50} | {:^50} |".format('module name', 'module folder')) - print("\t" + 107*'-') - for dep in deps: print('\t| {:^50} | {:^50} |'.format(dep['name'], dep['folder'])) -else: - sys.exit("Error: Could not determine dependencies. At least the module itself should appear.") - -depNames = [dep['name'] for dep in deps] -depFolders = [dep['folder'] for dep in deps] -depFolderPaths = [os.path.abspath(os.path.join(modParentPath, d)) for d in depFolders] -if cmdArgs['skipfolders']: - cmdArgs['skipfolders'] = [f.strip('/') for f in cmdArgs['skipfolders']] - depFolders = [d for d in depFolders if d not in cmdArgs['skipfolders']] - depNames = [getModuleInfo(d, 'Module') for d in depFolders] - depFolderPaths = [d for d in depFolderPaths if os.path.basename(d.strip('/')) not in cmdArgs['skipfolders']] - -################################# -# determine specific commits of all modules -print("\n-- Determining the module versions") -try: versions = getUsedVersions(depFolderPaths, cmdArgs['ignoreuntracked']) -except Exception as err: - print('\nCaught exception: ' + str(err)) - if 'untracked' in str(err): - print('If you are certain that the untracked files are not needed for the ' \ - 'installation of the modules, run this script with the -i flag.') - sys.exit(1) - -if len(versions) != len(depNames): - sys.exit("Error': Could not determine versions of all modules") - -print("-> Detected the following versions") -printVersionTable(versions) - -################################# -# create patches if necessary -print("\n-- Creating patches for unpublished commits and uncommitted changes") -patches = getPatches(depFolderPaths, cmdArgs['ignoreuntracked']) - -# create patch files -if len(patches) > 0: - patchesPath = os.path.join(modPath, 'patches') - if not os.path.exists(patchesPath): - os.mkdir(patchesPath) - print("-> Created a folder 'patches' in your module. You should commit this to your\n"\ - " repository in order for the installation script to work on other machines.") - else: - print("-> Adding patches to the 'patches' folder in your module. Make sure to commit\n"\ - " the newly added patches in order for the install script to work on other machines.") - - # get a non-used patch file name (makes sure not to overwrite anything) - def getPatchFileName(depModPath, targetName): - i = 1 - fileName = os.path.join(patchesPath, targetName + '.patch') - while os.path.exists(fileName): - fileName = os.path.join(patchesPath, targetName + '_' + str(i) + '.patch') - i += 1 - - return fileName, os.path.relpath(fileName, depModPath) - - # function to write a new patch file (returns the path to the new file) - def writeDepModPatchFile(depModPath, depModName, type): - patchPath, patchRelPath = getPatchFileName(depModPath, depModName + '_' + type) - patches[depModPath][type + '_relpath'] = patchRelPath - open(patchPath, 'w').write(patches[depModPath][type]) - return patchPath - - print("-> Created patch files:") - for depModPath in patches.keys(): - depModName = getModuleInfo(depModPath, "Module") - if 'unpublished' in patches[depModPath]: - print(' '*3 + writeDepModPatchFile(depModPath, depModName, 'unpublished')) - if 'uncommitted' in patches[depModPath]: - print(' '*3 + writeDepModPatchFile(depModPath, depModName, 'uncommitted')) - -else: - print("-> No Patches required") - - -################################## -# write installation shell script - -# in the install script, we work with relative paths from the parent to module folder -def getModRelPath(path): return os.path.relpath(path, modParentPath) -versions = {getModRelPath(modPath): val for modPath, val in versions.items()} -patches = {getModRelPath(modPath): val for modPath, val in patches.items()} - -topFolderName = cmdArgs['topfoldername'] -optsRelPath = 'dumux/cmake.opts' -if cmdArgs['optsfile']: - optsPath = os.path.abspath( os.path.join(os.getcwd(), cmdArgs['optsfile']) ) - optsRelPath = os.path.relpath(optsPath, modParentPath) - -# TODO: add support for different install script languages (e.g. Python). -with open(instFileName, 'w') as installFile: - installFile.write('#!/bin/bash\n\n') - installFile.write('#'*80 + '\n') - installFile.write('# This script installs the module "' + modName + '" together with all dependencies.\n') - installFile.write('\n') - - exitFunc = 'exitWith' - installFile.write('# defines a function to exit with error message\n') - installFile.write(exitFunc + ' ()\n') - installFile.write('{\n') - installFile.write(' echo ' + r'"\n$1"' +'\n') - installFile.write(' exit 1\n') - installFile.write('}\n\n') - - # function to write a command with error check into the script file - def writeCommand(command, errorMessage, indentationLevel = 0): - installFile.write(' '*indentationLevel + 'if ! ' + command + ';') - installFile.write(' then ' + exitFunc + ' "' + errorMessage + '"; fi\n') - - # write section on creating the top folder - if topFolderName: - installFile.write('# Everything will be installed into a newly created sub-folder named "' + topFolderName + '".\n') - installFile.write('echo "Creating the folder ' + topFolderName + ' to install the modules in"\n') - writeCommand('mkdir -p ' + topFolderName , '--Error: could not create top folder ' + topFolderName) - writeCommand('cd ' + topFolderName, '--Error: could not enter top folder ' + topFolderName) - installFile.write('\n') - else: - installFile.write('# Everything will be installed inside the folder from which the script is executed.\n\n') - - # function to write installation procedure for a module - def writeCloneModule(depModName, depModFolder): - installFile.write('# ' + depModName + '\n') - installFile.write('# ' + versions[depModFolder]['branch'] + ' # ' - + versions[depModFolder]['revision'] + ' # ' - + versions[depModFolder]['date'] + ' # ' - + versions[depModFolder]['author'] + '\n') - - writeCommand('git clone ' + versions[depModFolder]['remote'], - '-- Error: failed to clone ' + depModName + '.') - writeCommand('cd ' + depModFolder, - '-- Error: could not enter folder ' + depModFolder + '.') - writeCommand('git checkout ' + versions[depModFolder]['branch'], - '-- Error: failed to check out branch ' + versions[depModFolder]['branch'] + ' in module ' + depModName + '.') - writeCommand('git reset --hard ' + versions[depModFolder]['revision'], - '-- Error: failed to check out commit ' + versions[depModFolder]['revision'] + ' in module ' + depModName + '.') - - # write section on application of patches - def writeApplyPatch(patchRelPath): - installFile.write('if [ -f ' + patchRelPath + ' ]; then\n') - writeCommand('git apply ' + patchRelPath,'--Error: failed to apply patch ' + patchRelPath + ' in module ' + depModName + '.', 4) - installFile.write('else\n') - installFile.write(' '*4 + exitFunc + ' "--Error: patch ' + patchRelPath + ' was not found."\n') - installFile.write('fi\n') - - if depModFolder in patches and 'unpublished_relpath' in patches[depModFolder]: - writeApplyPatch(patches[depModFolder]['unpublished_relpath']) - if depModFolder in patches and 'uncommitted_relpath' in patches[depModFolder]: - writeApplyPatch(patches[depModFolder]['uncommitted_relpath']) - - installFile.write('echo "-- Successfully set up the module ' + depModName + r'\n"' + '\n') - installFile.write('cd ..\n\n') - - # write the module clone first in order for the patches to be present - if modName in depNames: - writeCloneModule(modName, modFolder) - for depModName, depModFolder in zip(depNames, depFolders): - if depModName != modName: - writeCloneModule(depModName, depModFolder) - - # write configure command - installFile.write('echo "-- All modules haven been cloned successfully. Configuring project..."\n') - writeCommand('./dune-common/bin/dunecontrol --opts=' + optsRelPath + ' all', '--Error: could not configure project') - - # build the tests of the module - installFile.write('\n') - installFile.write('echo "-- Configuring successful. Compiling applications..."\n') - writeCommand('cd ' + modFolder + "/build-cmake", '--Error: could not enter build directory at ' + modFolder + '/build-cmake') - writeCommand('make build_tests', '--Error: applications could not be compiled. Please try to compile them manually.') - -print("\n-- Successfully created install script file " + instFileName) - -if len(patches) > 0: - print("-> It is recommended that you now commit and publish the 'patches' folder and this install script in your module such that others can use it.") - print(" IMPORTANT: After you committed the patches, you have to adjust the line of the install script in which your module is checked out to a specific commit.") - print(" That is, in the line 'git reset --hard COMMIT_SHA' for your module, replace COMMIT_SHA by the commit in which you added the patches.") - print(" If patches had to be created for your own module, please think about comitting and pushing your local changes and rerunning this script again.") - -if modName in depNames: # print gudience to installation if the module is not skipped - print("\n-- You might want to put installation instructions into the README.md file of your module, for instance:\n") - print(" ## Installation\n") - print(" The easiest way of installation is to use the script `" + instFileName + "` provided in this repository.") - print(" Using `wget`, you can simply install all dependent modules by typing:\n") - print(" ```sh") - print(" wget " + versions[modFolder]['remote'] + "/" + instFileName) - print(" chmod u+x " + instFileName) - print(" ./" + instFileName) - print(" ```\n") - - if topFolderName: print(" This will create a sub-folder `" + topFolderName + "`, clone all modules into it, configure the entire project and build the applications contained in this module.") - else: print(" This will clone all modules into the folder from which the script is called, configure the entire project and build the applications contained in this module.") -- GitLab