-
Notifications
You must be signed in to change notification settings - Fork 220
also include patch files in --new-pr and --update-pr #1852
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
33600a4
a28953b
107e2c8
4cc8288
2144903
cf96b73
a5636d2
4aa7776
d19b8e1
0d5319f
6500884
5713346
b5c9fec
cf07d04
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -593,6 +593,12 @@ def extract_cmd(filepath, overwrite=False): | |
| return cmd_tmpl % {'filepath': filepath, 'target': target} | ||
|
|
||
|
|
||
| def is_patch_file(path): | ||
| """Determine whether file at specified path is a patch file (based on +++ and --- lines being present).""" | ||
| txt = read_file(path) | ||
| return bool(re.search(r'^\+{3}\s', txt, re.M) and re.search(r'^-{3}\s', txt, re.M)) | ||
|
|
||
|
|
||
| def det_patched_files(path=None, txt=None, omit_ab_prefix=False, github=False, filter_deleted=False): | ||
| """ | ||
| Determine list of patched files from a patch. | ||
|
|
@@ -1324,3 +1330,18 @@ def find_flexlm_license(custom_env_vars=None, lic_specs=None): | |
| _log.info("Found valid license specs via %s: %s", via_msg, valid_lic_specs) | ||
|
|
||
| return (valid_lic_specs, lic_env_var) | ||
|
|
||
|
|
||
| def copy_file(path, target_path): | ||
| """ | ||
| Copy a file from path to target_path | ||
| :param path: the original filepath | ||
| :param target_path: path to copy the file to | ||
| """ | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. docstring please! |
||
| try: | ||
| mkdir(os.path.dirname(target_path), parents=True) | ||
| shutil.copy2(path, target_path) | ||
| _log.info("%s copied to %s", path, target_path) | ||
| except OSError as err: | ||
| raise EasyBuildError("Failed to copy %s to %s: %s", path, target_path, err) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -47,11 +47,13 @@ | |
| from vsc.utils import fancylogger | ||
| from vsc.utils.missing import nub | ||
|
|
||
| from easybuild.framework.easyconfig.easyconfig import copy_easyconfigs | ||
| from easybuild.framework.easyconfig.easyconfig import copy_easyconfigs, copy_patch_files, process_easyconfig | ||
| from easybuild.framework.easyconfig.format.one import EB_FORMAT_EXTENSION | ||
| from easybuild.framework.easyconfig.format.yeb import YEB_FORMAT_EXTENSION | ||
| from easybuild.tools.build_log import EasyBuildError, print_msg | ||
| from easybuild.tools.config import build_option | ||
| from easybuild.tools.filetools import apply_patch, det_patched_files, download_file, extract_file | ||
| from easybuild.tools.filetools import mkdir, read_file, which, write_file | ||
| from easybuild.tools.filetools import is_patch_file, mkdir, read_file, which, write_file | ||
| from easybuild.tools.systemtools import UNKNOWN, get_tool_version | ||
| from easybuild.tools.utilities import only_if_module_is_available | ||
|
|
||
|
|
@@ -627,6 +629,22 @@ def _easyconfigs_pr_common(paths, start_branch=None, pr_branch=None, target_acco | |
|
|
||
| _log.debug("git status: %s", git_repo.git.status()) | ||
|
|
||
| # seperate easyconfigs and patch files | ||
| ec_paths = [p for p in existing_paths if not is_patch_file(p)] | ||
| patch_paths = [p for p in existing_paths if p not in ec_paths] | ||
|
|
||
| # copy easyconfig files to right place | ||
| target_dir = os.path.join(git_working_dir, pr_target_repo) | ||
| print_msg("copying easyconfigs to %s..." % target_dir) | ||
| file_info = copy_easyconfigs(ec_paths, target_dir) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add guard
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nvm |
||
|
|
||
| # figure out to which software name patches relate, and copy them to the right place | ||
| if patch_paths: | ||
| patch_specs = det_patch_specs(patch_paths, file_info) | ||
|
|
||
| print_msg("copying patch files to %s..." % target_dir) | ||
| patch_info = copy_patch_files(patch_specs, target_dir) | ||
|
|
||
| # determine path to files to delete (if any) | ||
| deleted_paths = [] | ||
| for fn in delete_files: | ||
|
|
@@ -641,17 +659,14 @@ def _easyconfigs_pr_common(paths, start_branch=None, pr_branch=None, target_acco | |
| else: | ||
| raise EasyBuildError("Path doesn't exist or file to delete isn't found in target branch: %s", fn) | ||
|
|
||
| # copy edited/added files to right place | ||
| file_info = copy_easyconfigs(existing_paths, repo_path) | ||
|
|
||
| if existing_paths: | ||
| name_version = file_info['ecs'][0].name + string.translate(file_info['ecs'][0].version, None, '-.') | ||
| else: | ||
| name_version = os.path.basename(delete_files[0]) | ||
|
|
||
| # checkout target branch | ||
| if pr_branch is None: | ||
| pr_branch = '%s_new_pr_%s' % (time.strftime("%Y%m%d%H%M%S"), name_version) | ||
| if ec_paths: | ||
| label = file_info['ecs'][0].name + string.translate(file_info['ecs'][0].version, None, '-.') | ||
| else: | ||
| label = ''.join(random.choice(string.letters) for _ in range(10)) | ||
| pr_branch = '%s_new_pr_%s' % (time.strftime("%Y%m%d%H%M%S"), label) | ||
|
|
||
|
|
||
| # create branch to commit to and push; | ||
| # use force to avoid errors if branch already exists (OK since this is a local temporary copy of the repo) | ||
|
|
@@ -662,6 +677,10 @@ def _easyconfigs_pr_common(paths, start_branch=None, pr_branch=None, target_acco | |
| _log.debug("Staging all %d new/modified easyconfigs", len(file_info['paths_in_repo'])) | ||
| git_repo.index.add(file_info['paths_in_repo']) | ||
|
|
||
| if patch_paths: | ||
| _log.debug("Staging all %d new/modified patch files", len(patch_info['paths_in_repo'])) | ||
| git_repo.index.add(patch_info['paths_in_repo']) | ||
|
|
||
| # stage deleted files | ||
| if deleted_paths: | ||
| git_repo.index.remove(deleted_paths) | ||
|
|
@@ -716,6 +735,89 @@ def _easyconfigs_pr_common(paths, start_branch=None, pr_branch=None, target_acco | |
| return file_info, deleted_paths, git_repo, pr_branch, diff_stat | ||
|
|
||
|
|
||
| def det_patch_specs(patch_paths, file_info): | ||
| """ Determine software names for patch files """ | ||
| print_msg("determining software names for patch files...") | ||
| patch_specs = [] | ||
| for patch_path in patch_paths: | ||
| soft_name = None | ||
| patch_file = os.path.basename(patch_path) | ||
|
|
||
| # consider patch lists of easyconfigs being provided | ||
| for ec in file_info['ecs']: | ||
| if patch_file in ec['patches']: | ||
| soft_name = ec.name | ||
| break | ||
|
|
||
| if soft_name: | ||
| patch_specs.append((patch_path, soft_name)) | ||
| else: | ||
| # fall back on scanning all eb files for patches | ||
| print "Matching easyconfig for %s not found on the first try:" % patch_path, | ||
| print "scanning all easyconfigs to determine where patch file belongs (this may take a while)..." | ||
| soft_name = find_software_name_for_patch(patch_file) | ||
| if soft_name: | ||
| patch_specs.append((patch_path, soft_name)) | ||
| else: | ||
| # still nothing found | ||
| raise EasyBuildError("Failed to determine software name to which patch file %s relates", patch_path) | ||
|
|
||
| return patch_specs | ||
|
|
||
|
|
||
| def find_software_name_for_patch(patch_name): | ||
| """ | ||
| Scan all easyconfigs in the robot path(s) to determine which software a patch file belongs to | ||
|
|
||
| :param patch_name: name of the patch file | ||
| :return: name of the software that this patch file belongs to (if found) | ||
| """ | ||
|
|
||
| def is_patch_for(patch_name, ec): | ||
| """Check whether specified patch matches any patch in the provided EasyConfig instance.""" | ||
| res = False | ||
| for patch in ec['patches']: | ||
| if isinstance(patch, (tuple, list)): | ||
| patch = patch[0] | ||
| if patch == patch_name: | ||
| res = True | ||
| break | ||
|
|
||
| return res | ||
|
|
||
| robot_paths = build_option('robot_path') | ||
| soft_name = None | ||
|
|
||
| all_ecs = [] | ||
| for robot_path in robot_paths: | ||
| for (dirpath, _, filenames) in os.walk(robot_path): | ||
| for fn in filenames: | ||
| if fn != 'TEMPLATE.eb': | ||
| path = os.path.join(dirpath, fn) | ||
| rawtxt = read_file(path) | ||
| if 'patches' in rawtxt: | ||
| all_ecs.append(path) | ||
|
|
||
| nr_of_ecs = len(all_ecs) | ||
| for idx, path in enumerate(all_ecs): | ||
| if soft_name: | ||
| break | ||
| rawtxt = read_file(path) | ||
| try: | ||
| ecs = process_easyconfig(path, validate=False) | ||
| for ec in ecs: | ||
| if is_patch_for(patch_name, ec['ec']): | ||
| soft_name = ec['ec']['name'] | ||
| break | ||
| except EasyBuildError as err: | ||
| _log.debug("Ignoring easyconfig %s that fails to parse: %s", path, err) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see why you should use |
||
| sys.stdout.write('\r%s of %s easyconfigs checked' % (idx+1, nr_of_ecs)) | ||
| sys.stdout.flush() | ||
|
|
||
| sys.stdout.write('\n') | ||
| return soft_name | ||
|
|
||
|
|
||
| @only_if_module_is_available('git', pkgname='GitPython') | ||
| def new_pr(paths, title=None, descr=None, commit_msg=None): | ||
| """Open new pull request using specified files.""" | ||
|
|
@@ -752,6 +854,7 @@ def new_pr(paths, title=None, descr=None, commit_msg=None): | |
| classes_counted = sorted([(classes.count(c), c) for c in nub(classes)]) | ||
| class_label = ','.join([tc for (cnt, tc) in classes_counted if cnt == classes_counted[-1][0]]) | ||
|
|
||
|
|
||
| if title is None: | ||
| if file_info['ecs'] and all(file_info['new']) and not deleted_paths: | ||
| # mention software name/version in PR title (only first 3) | ||
|
|
@@ -762,7 +865,6 @@ def new_pr(paths, title=None, descr=None, commit_msg=None): | |
| main_title = ', '.join(names_and_versions[:3] + ['...']) | ||
|
|
||
| title = "{%s}[%s] %s" % (class_label, toolchain_label, main_title) | ||
|
|
||
| else: | ||
| raise EasyBuildError("Don't know how to make a PR title for this PR. " | ||
| "Please include a title (use --pr-title)") | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -672,6 +672,12 @@ def test_find_flexlm_license(self): | |
| del os.environ['LM_LICENSE_FILE'] | ||
| self.assertEqual(ft.find_flexlm_license(lic_specs=[None]), ([], None)) | ||
|
|
||
| def test_is_patch_file(self): | ||
| """Test for is_patch_file() function.""" | ||
| testdir = os.path.dirname(os.path.abspath(__file__)) | ||
| self.assertFalse(ft.is_patch_file(os.path.join(testdir, 'easyconfigs', 'toy-0.0.eb'))) | ||
| self.assertTrue(ft.is_patch_file(os.path.join(testdir, 'sandbox', 'sources', 'toy', 'toy-0.0_typo.patch'))) | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Caylo: please add a test for |
||
| def test_is_alt_pypi_url(self): | ||
| """Test is_alt_pypi_url() function.""" | ||
| url = 'https://pypi.python.org/packages/source/e/easybuild/easybuild-2.7.0.tar.gz' | ||
|
|
@@ -709,6 +715,16 @@ def test_apply_patch(self): | |
| # trying the patch again should fail | ||
| self.assertErrorRegex(EasyBuildError, "Couldn't apply patch file", ft.apply_patch, toy_patch, path) | ||
|
|
||
| def test_copy_file(self): | ||
| """ Test copy_file """ | ||
| testdir = os.path.dirname(os.path.abspath(__file__)) | ||
| tmpdir = self.test_prefix | ||
| to_copy = os.path.join(testdir, 'easyconfigs', 'toy-0.0.eb') | ||
| target_path = os.path.join(tmpdir, 'toy-0.0.eb') | ||
| ft.copy_file(to_copy, target_path) | ||
| self.assertTrue(os.path.exists(target_path)) | ||
| self.assertTrue(ft.read_file(to_copy) == ft.read_file(target_path)) | ||
|
|
||
|
|
||
| def suite(): | ||
| """ returns all the testcases in this module """ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be
return full_target_path?