def muck_patch(ctx, args): if not len(args) in (1, 2): failF('''\ muck patch error: patch command takes one or two arguments. usage: muck patch [original_target] [target] creates a new target by copying either the source or product of the original to _build/[target], and then creates an empty [target].pat. muck patch [target.pat] update the patch file with the diff of the previously specified original and target. ''') if len(args) == 2: # create new patch. orig_target_path, target_path = args if orig_target_path.endswith('.pat'): errFL('muck patch error: original should not be a patch file: {}', orig_target_path) if target_path.endswith('.pat'): errFL('muck patch error: {} {}: target should not be a patch file: {}', target_path) patch_path = target_path + '.pat' if path_exists(patch_path): failF('muck patch error: {}: patch already exists.', patch_path) update_dependency(ctx, orig_target_path, dependent=None) orig_path = actual_path_for_target(orig_target_path) prod_path = product_path_for_target(target_path) if path_exists(prod_path): errFL('muck patch note: product already exists: {}', prod_path) else: errFL('muck patch note: copying original to product: {} -> {}', orig_path, prod_path) copy_file(orig_path, prod_path) else: # update existing patch. patch_path = args[0] if path_ext(patch_path) != '.pat': failF('muck patch error: argument does not specify a .pat file: {!r}', patch_path) deps = pat_dependencies(patch_path, open(patch_path), {}) orig_target_path = deps[0] update_dependency(ctx, orig_target_path, dependent=None) orig_path = actual_path_for_target(orig_target_path) target_path = path_stem(patch_path) prod_path = product_path_for_target(target_path) # update patch (both cases). patch_path_tmp = patch_path + tmp_ext cmd = ['pat', 'diff', orig_path, prod_path] errFL('muck patch note: diffing: `{}`', ' '.join(shlex.quote(w) for w in cmd)) with open(patch_path_tmp, 'wb') as f: code = runC(cmd, out=f) move_file(patch_path_tmp, patch_path, overwrite=True) if len(args) == 1: # updated existing patch. # need to remove or update the target record to avoid the 'did you mean to patch?' safeguard. # for now, just delete it to be safe; this makes the target look stale. try: ctx.db.delete_record(target_path=target_path) except KeyError: pass
def update_product_with_tmp(ctx: Ctx, src: str, tmp_path: str): product_path, ext = split_stem_ext(tmp_path) if ext not in (out_ext, tmp_ext): failF(tmp_path, 'product output path has unexpected extension: {!r}', ext) if not is_product_path(product_path): failF(product_path, 'product path is not in build dir.') target_path = product_path[len(build_dir_slash):] size, mtime, old = calc_size_mtime_old(ctx, target_path, tmp_path) file_hash = hash_for_path(tmp_path) is_changed = (size != old.size or file_hash != old.hash) if is_changed: ctx.db.delete_record(target_path=target_path) # delete metadata if it exists, just before overwrite, in case muck fails before update. move_file(tmp_path, product_path, overwrite=True) # move regardless; if not changed, just cleans up the identical tmp file. noteF(target_path, 'product {}; {}.', 'changed' if is_changed else 'did not change', format_byte_count(size)) return update_deps_and_record(ctx, target_path, product_path, is_changed=is_changed, size=size, mtime=mtime, file_hash=file_hash, src=src, old=old)