diff options
| author | Hisham Muhammad <hisham@gobolinux.org> | 2017-10-04 20:50:14 -0300 |
|---|---|---|
| committer | Hisham Muhammad <hisham@gobolinux.org> | 2017-10-04 20:53:33 -0300 |
| commit | ff73ca923833900ff7ebb3e98b3ab838214ca32c (patch) | |
| tree | 5b5eca98b876805fc9cbd56c7a96762b8b830e25 /src | |
| parent | c4b66299b1259a1e3d6bfef83d912332ccc58061 (diff) | |
| download | luarocks-ff73ca923833900ff7ebb3e98b3ab838214ca32c.tar.gz luarocks-ff73ca923833900ff7ebb3e98b3ab838214ca32c.tar.bz2 luarocks-ff73ca923833900ff7ebb3e98b3ab838214ca32c.zip | |
3.0: Patches can create and delete files.
Diffstat (limited to 'src')
| -rw-r--r-- | src/luarocks/build.lua | 3 | ||||
| -rw-r--r-- | src/luarocks/fs/lua.lua | 5 | ||||
| -rw-r--r-- | src/luarocks/tools/patch.lua | 306 |
3 files changed, 179 insertions, 135 deletions
diff --git a/src/luarocks/build.lua b/src/luarocks/build.lua index ceaa20dd..860cbdc1 100644 --- a/src/luarocks/build.lua +++ b/src/luarocks/build.lua | |||
| @@ -91,7 +91,8 @@ function build.apply_patches(rockspec) | |||
| 91 | extract_from_rockspec(build_spec.patches) | 91 | extract_from_rockspec(build_spec.patches) |
| 92 | for patch, patchdata in util.sortedpairs(build_spec.patches) do | 92 | for patch, patchdata in util.sortedpairs(build_spec.patches) do |
| 93 | util.printout("Applying patch "..patch.."...") | 93 | util.printout("Applying patch "..patch.."...") |
| 94 | local ok, err = fs.apply_patch(tostring(patch), patchdata) | 94 | local create_delete = rockspec:format_is_at_least("3.0") |
| 95 | local ok, err = fs.apply_patch(tostring(patch), patchdata, create_delete) | ||
| 95 | if not ok then | 96 | if not ok then |
| 96 | return nil, "Failed applying patch "..patch | 97 | return nil, "Failed applying patch "..patch |
| 97 | end | 98 | end |
diff --git a/src/luarocks/fs/lua.lua b/src/luarocks/fs/lua.lua index db5c675d..dbd4174d 100644 --- a/src/luarocks/fs/lua.lua +++ b/src/luarocks/fs/lua.lua | |||
| @@ -810,13 +810,14 @@ end | |||
| 810 | --- Apply a patch. | 810 | --- Apply a patch. |
| 811 | -- @param patchname string: The filename of the patch. | 811 | -- @param patchname string: The filename of the patch. |
| 812 | -- @param patchdata string or nil: The actual patch as a string. | 812 | -- @param patchdata string or nil: The actual patch as a string. |
| 813 | function fs_lua.apply_patch(patchname, patchdata) | 813 | -- @param create_delete boolean: Support creating and deleting files in a patch. |
| 814 | function fs_lua.apply_patch(patchname, patchdata, create_delete) | ||
| 814 | local p, all_ok = patch.read_patch(patchname, patchdata) | 815 | local p, all_ok = patch.read_patch(patchname, patchdata) |
| 815 | if not all_ok then | 816 | if not all_ok then |
| 816 | return nil, "Failed reading patch "..patchname | 817 | return nil, "Failed reading patch "..patchname |
| 817 | end | 818 | end |
| 818 | if p then | 819 | if p then |
| 819 | return patch.apply_patch(p, 1) | 820 | return patch.apply_patch(p, 1, create_delete) |
| 820 | end | 821 | end |
| 821 | end | 822 | end |
| 822 | 823 | ||
diff --git a/src/luarocks/tools/patch.lua b/src/luarocks/tools/patch.lua index f1a14578..30ed1419 100644 --- a/src/luarocks/tools/patch.lua +++ b/src/luarocks/tools/patch.lua | |||
| @@ -84,13 +84,6 @@ local function file_copy(src, dest) | |||
| 84 | return true | 84 | return true |
| 85 | end | 85 | end |
| 86 | 86 | ||
| 87 | local function overwrite_file(filename) | ||
| 88 | local fh = io.open(filename, "w+") | ||
| 89 | if not fh then return false end | ||
| 90 | fh:close() | ||
| 91 | return true | ||
| 92 | end | ||
| 93 | |||
| 94 | local function string_as_file(s) | 87 | local function string_as_file(s) |
| 95 | return { | 88 | return { |
| 96 | at = 0, | 89 | at = 0, |
| @@ -162,6 +155,10 @@ local function match_linerange(line) | |||
| 162 | return m1, m2, m3, m4 | 155 | return m1, m2, m3, m4 |
| 163 | end | 156 | end |
| 164 | 157 | ||
| 158 | local function match_epoch(str) | ||
| 159 | return str:match("[^0-9]1969[^0-9]") or str:match("[^0-9]1970[^0-9]") | ||
| 160 | end | ||
| 161 | |||
| 165 | function patch.read_patch(filename, data) | 162 | function patch.read_patch(filename, data) |
| 166 | -- define possible file regions that will direct the parser flow | 163 | -- define possible file regions that will direct the parser flow |
| 167 | local state = 'header' | 164 | local state = 'header' |
| @@ -173,7 +170,7 @@ function patch.read_patch(filename, data) | |||
| 173 | 170 | ||
| 174 | local all_ok = true | 171 | local all_ok = true |
| 175 | local lineends = {lf=0, crlf=0, cr=0} | 172 | local lineends = {lf=0, crlf=0, cr=0} |
| 176 | local files = {source={}, target={}, hunks={}, fileends={}, hunkends={}} | 173 | local files = {source={}, target={}, epoch={}, hunks={}, fileends={}, hunkends={}} |
| 177 | local nextfileno = 0 | 174 | local nextfileno = 0 |
| 178 | local nexthunkno = 0 --: even if index starts with 0 user messages | 175 | local nexthunkno = 0 --: even if index starts with 0 user messages |
| 179 | -- number hunks from 1 | 176 | -- number hunks from 1 |
| @@ -268,12 +265,6 @@ function patch.read_patch(filename, data) | |||
| 268 | warning(format("inconsistent line ends in patch hunks for %s", | 265 | warning(format("inconsistent line ends in patch hunks for %s", |
| 269 | files.source[nextfileno])) | 266 | files.source[nextfileno])) |
| 270 | end | 267 | end |
| 271 | if debugmode then | ||
| 272 | local debuglines = {crlf=ends.crlf, lf=ends.lf, cr=ends.cr, | ||
| 273 | file=files.target[nextfileno], hunk=nexthunkno} | ||
| 274 | debug(format("crlf: %(crlf)d lf: %(lf)d cr: %(cr)d\t " .. | ||
| 275 | "- file: %(file)s hunk: %(hunk)d", debuglines)) | ||
| 276 | end | ||
| 277 | end | 268 | end |
| 278 | -- state is 'hunkbody' or 'hunkskip' | 269 | -- state is 'hunkbody' or 'hunkskip' |
| 279 | end | 270 | end |
| @@ -304,12 +295,15 @@ function patch.read_patch(filename, data) | |||
| 304 | -- Accept a space as a terminator, like GNU patch does. | 295 | -- Accept a space as a terminator, like GNU patch does. |
| 305 | -- Breaks patches containing filenames with spaces... | 296 | -- Breaks patches containing filenames with spaces... |
| 306 | -- FIXME Figure out what does GNU patch do in those cases. | 297 | -- FIXME Figure out what does GNU patch do in those cases. |
| 307 | local match = line:match("^%-%-%- ([^ \t\r\n]+)") | 298 | local match, rest = line:match("^%-%-%- ([^ \t\r\n]+)(.*)") |
| 308 | if not match then | 299 | if not match then |
| 309 | all_ok = false | 300 | all_ok = false |
| 310 | warning(format("skipping invalid filename at line %d", lineno+1)) | 301 | warning(format("skipping invalid filename at line %d", lineno+1)) |
| 311 | state = 'header' | 302 | state = 'header' |
| 312 | else | 303 | else |
| 304 | if match_epoch(rest) then | ||
| 305 | files.epoch[nextfileno + 1] = true | ||
| 306 | end | ||
| 313 | table.insert(files.source, match) | 307 | table.insert(files.source, match) |
| 314 | end | 308 | end |
| 315 | elseif not startswith(line, "+++ ") then | 309 | elseif not startswith(line, "+++ ") then |
| @@ -338,8 +332,8 @@ function patch.read_patch(filename, data) | |||
| 338 | -- Accept a space as a terminator, like GNU patch does. | 332 | -- Accept a space as a terminator, like GNU patch does. |
| 339 | -- Breaks patches containing filenames with spaces... | 333 | -- Breaks patches containing filenames with spaces... |
| 340 | -- FIXME Figure out what does GNU patch do in those cases. | 334 | -- FIXME Figure out what does GNU patch do in those cases. |
| 341 | local re_filename = "^%+%+%+ ([^ \t\r\n]+)" | 335 | local re_filename = "^%+%+%+ ([^ \t\r\n]+)(.*)$" |
| 342 | local match = line:match(re_filename) | 336 | local match, rest = line:match(re_filename) |
| 343 | if not match then | 337 | if not match then |
| 344 | all_ok = false | 338 | all_ok = false |
| 345 | warning(format( | 339 | warning(format( |
| @@ -349,6 +343,9 @@ function patch.read_patch(filename, data) | |||
| 349 | else | 343 | else |
| 350 | table.insert(files.target, match) | 344 | table.insert(files.target, match) |
| 351 | nextfileno = nextfileno + 1 | 345 | nextfileno = nextfileno + 1 |
| 346 | if match_epoch(rest) then | ||
| 347 | files.epoch[nextfileno] = true | ||
| 348 | end | ||
| 352 | nexthunkno = 0 | 349 | nexthunkno = 0 |
| 353 | table.insert(files.hunks, {}) | 350 | table.insert(files.hunks, {}) |
| 354 | table.insert(files.hunkends, table_copy(lineends)) | 351 | table.insert(files.hunkends, table_copy(lineends)) |
| @@ -568,143 +565,188 @@ local function strip_dirs(filename, strip) | |||
| 568 | return filename | 565 | return filename |
| 569 | end | 566 | end |
| 570 | 567 | ||
| 571 | function patch.apply_patch(the_patch, strip) | 568 | local function write_new_file(filename, hunk) |
| 572 | local all_ok = true | 569 | local fh = io.open(filename, "wb") |
| 573 | local total = #the_patch.source | 570 | if not fh then return false end |
| 574 | for fileno, filename in ipairs(the_patch.source) do | 571 | for _, hline in ipairs(hunk.text) do |
| 575 | if filename == "/dev/null" then | 572 | if not hline:sub(1,1) == "+" then |
| 576 | filename = strip_dirs(the_patch.target[fileno], strip) | 573 | return false, "malformed patch" |
| 577 | assert(overwrite_file(filename)) | ||
| 578 | else | ||
| 579 | filename = strip_dirs(filename, strip) | ||
| 580 | end | 574 | end |
| 581 | local continue | 575 | fh:write(hline:sub(2)) |
| 582 | local f2patch = filename | 576 | end |
| 583 | if not exists(f2patch) then | 577 | fh:close() |
| 584 | f2patch = strip_dirs(the_patch.target[fileno], strip) | 578 | return true |
| 585 | f2patch = fs.absolute_name(f2patch) | 579 | end |
| 586 | if not exists(f2patch) then --FIX:if f2patch nil | 580 | |
| 587 | warning(format("source/target file does not exist\n--- %s\n+++ %s", | 581 | local function patch_file(source, target, epoch, hunks, strip, create_delete) |
| 588 | filename, f2patch)) | 582 | local create_file = false |
| 589 | all_ok = false | 583 | if create_delete then |
| 590 | continue = true | 584 | local is_src_epoch = epoch and #hunks == 1 and hunks[1].startsrc == 0 and hunks[1].linessrc == 0 |
| 591 | end | 585 | if is_src_epoch or source == "/dev/null" then |
| 586 | info(format("will create %s", target)) | ||
| 587 | create_file = true | ||
| 592 | end | 588 | end |
| 593 | if not continue and not isfile(f2patch) then | 589 | end |
| 594 | warning(format("not a file - %s", f2patch)) | 590 | if create_file then |
| 595 | all_ok = false | 591 | return write_new_file(fs.absolute_name(strip_dirs(target, strip)), hunks[1]) |
| 596 | continue = true | 592 | end |
| 593 | source = strip_dirs(source, strip) | ||
| 594 | local f2patch = source | ||
| 595 | if not exists(f2patch) then | ||
| 596 | f2patch = strip_dirs(target, strip) | ||
| 597 | f2patch = fs.absolute_name(f2patch) | ||
| 598 | if not exists(f2patch) then --FIX:if f2patch nil | ||
| 599 | warning(format("source/target file does not exist\n--- %s\n+++ %s", | ||
| 600 | source, f2patch)) | ||
| 601 | return false | ||
| 597 | end | 602 | end |
| 598 | if not continue then | 603 | end |
| 604 | if not isfile(f2patch) then | ||
| 605 | warning(format("not a file - %s", f2patch)) | ||
| 606 | return false | ||
| 607 | end | ||
| 599 | 608 | ||
| 600 | filename = f2patch | 609 | source = f2patch |
| 610 | |||
| 611 | -- validate before patching | ||
| 612 | local file = load_file(source) | ||
| 613 | local hunkno = 1 | ||
| 614 | local hunk = hunks[hunkno] | ||
| 615 | local hunkfind = {} | ||
| 616 | local validhunks = 0 | ||
| 617 | local canpatch = false | ||
| 618 | local hunklineno | ||
| 619 | if not file then | ||
| 620 | return nil, "failed reading file " .. source | ||
| 621 | end | ||
| 601 | 622 | ||
| 602 | info(format("processing %d/%d:\t %s", fileno, total, filename)) | 623 | if create_delete then |
| 624 | if epoch and #hunks == 1 and hunks[1].starttgt == 0 and hunks[1].linestgt == 0 then | ||
| 625 | local ok = os.remove(source) | ||
| 626 | if not ok then | ||
| 627 | return false | ||
| 628 | end | ||
| 629 | info(format("successfully removed %s", source)) | ||
| 630 | return true | ||
| 631 | end | ||
| 632 | end | ||
| 603 | 633 | ||
| 604 | -- validate before patching | 634 | find_hunks(file, hunks) |
| 605 | local hunks = the_patch.hunks[fileno] | ||
| 606 | local file = load_file(filename) | ||
| 607 | local hunkno = 1 | ||
| 608 | local hunk = hunks[hunkno] | ||
| 609 | local hunkfind = {} | ||
| 610 | local validhunks = 0 | ||
| 611 | local canpatch = false | ||
| 612 | local hunklineno | ||
| 613 | local isbreak | ||
| 614 | local lineno = 0 | ||
| 615 | |||
| 616 | find_hunks(file, hunks) | ||
| 617 | |||
| 618 | for _, line in ipairs(file) do | ||
| 619 | lineno = lineno + 1 | ||
| 620 | local continue | ||
| 621 | if not hunk or lineno < hunk.startsrc then | ||
| 622 | continue = true | ||
| 623 | elseif lineno == hunk.startsrc then | ||
| 624 | hunkfind = {} | ||
| 625 | for _,x in ipairs(hunk.text) do | ||
| 626 | if x:sub(1,1) == ' ' or x:sub(1,1) == '-' then | ||
| 627 | hunkfind[#hunkfind+1] = endlstrip(x:sub(2)) | ||
| 628 | end | ||
| 629 | end | ||
| 630 | hunklineno = 1 | ||
| 631 | 635 | ||
| 632 | -- todo \ No newline at end of file | 636 | local function process_line(line, lineno) |
| 633 | end | 637 | if not hunk or lineno < hunk.startsrc then |
| 634 | -- check hunks in source file | 638 | return false |
| 635 | if not continue and lineno < hunk.startsrc + #hunkfind - 1 then | 639 | end |
| 636 | if endlstrip(line) == hunkfind[hunklineno] then | 640 | if lineno == hunk.startsrc then |
| 637 | hunklineno = hunklineno + 1 | 641 | hunkfind = {} |
| 638 | else | 642 | for _,x in ipairs(hunk.text) do |
| 639 | debug(format("hunk no.%d doesn't match source file %s", | 643 | if x:sub(1,1) == ' ' or x:sub(1,1) == '-' then |
| 640 | hunkno, filename)) | 644 | hunkfind[#hunkfind+1] = endlstrip(x:sub(2)) |
| 641 | -- file may be already patched, but check other hunks anyway | ||
| 642 | hunkno = hunkno + 1 | ||
| 643 | if hunkno <= #hunks then | ||
| 644 | hunk = hunks[hunkno] | ||
| 645 | continue = true | ||
| 646 | else | ||
| 647 | isbreak = true; break | ||
| 648 | end | ||
| 649 | end | 645 | end |
| 650 | end | 646 | end |
| 651 | -- check if processed line is the last line | 647 | hunklineno = 1 |
| 652 | if not continue and lineno == hunk.startsrc + #hunkfind - 1 then | 648 | |
| 653 | debug(format("file %s hunk no.%d -- is ready to be patched", | 649 | -- todo \ No newline at end of file |
| 654 | filename, hunkno)) | 650 | end |
| 651 | -- check hunks in source file | ||
| 652 | if lineno < hunk.startsrc + #hunkfind - 1 then | ||
| 653 | if endlstrip(line) == hunkfind[hunklineno] then | ||
| 654 | hunklineno = hunklineno + 1 | ||
| 655 | else | ||
| 656 | debug(format("hunk no.%d doesn't match source file %s", | ||
| 657 | hunkno, source)) | ||
| 658 | -- file may be already patched, but check other hunks anyway | ||
| 655 | hunkno = hunkno + 1 | 659 | hunkno = hunkno + 1 |
| 656 | validhunks = validhunks + 1 | ||
| 657 | if hunkno <= #hunks then | 660 | if hunkno <= #hunks then |
| 658 | hunk = hunks[hunkno] | 661 | hunk = hunks[hunkno] |
| 662 | return false | ||
| 659 | else | 663 | else |
| 660 | if validhunks == #hunks then | 664 | return true |
| 661 | -- patch file | ||
| 662 | canpatch = true | ||
| 663 | isbreak = true; break | ||
| 664 | end | ||
| 665 | end | 665 | end |
| 666 | end | 666 | end |
| 667 | end | 667 | end |
| 668 | if not isbreak then | 668 | -- check if processed line is the last line |
| 669 | if lineno == hunk.startsrc + #hunkfind - 1 then | ||
| 670 | debug(format("file %s hunk no.%d -- is ready to be patched", | ||
| 671 | source, hunkno)) | ||
| 672 | hunkno = hunkno + 1 | ||
| 673 | validhunks = validhunks + 1 | ||
| 669 | if hunkno <= #hunks then | 674 | if hunkno <= #hunks then |
| 670 | warning(format("premature end of source file %s at hunk %d", | 675 | hunk = hunks[hunkno] |
| 671 | filename, hunkno)) | ||
| 672 | all_ok = false | ||
| 673 | end | ||
| 674 | end | ||
| 675 | if validhunks < #hunks then | ||
| 676 | if check_patched(file, hunks) then | ||
| 677 | warning(format("already patched %s", filename)) | ||
| 678 | else | 676 | else |
| 679 | warning(format("source file is different - %s", filename)) | 677 | if validhunks == #hunks then |
| 680 | all_ok = false | 678 | -- patch file |
| 681 | end | 679 | canpatch = true |
| 682 | end | 680 | return true |
| 683 | if canpatch then | ||
| 684 | local backupname = filename .. ".orig" | ||
| 685 | if exists(backupname) then | ||
| 686 | warning(format("can't backup original file to %s - aborting", | ||
| 687 | backupname)) | ||
| 688 | all_ok = false | ||
| 689 | else | ||
| 690 | assert(os.rename(filename, backupname)) | ||
| 691 | if patch_hunks(backupname, filename, hunks) then | ||
| 692 | warning(format("successfully patched %s", filename)) | ||
| 693 | assert(os.remove(backupname)) | ||
| 694 | else | ||
| 695 | warning(format("error patching file %s", filename)) | ||
| 696 | assert(file_copy(filename, filename .. ".invalid")) | ||
| 697 | warning(format("invalid version is saved to %s", | ||
| 698 | filename .. ".invalid")) | ||
| 699 | -- todo: proper rejects | ||
| 700 | assert(os.rename(backupname, filename)) | ||
| 701 | all_ok = false | ||
| 702 | end | 681 | end |
| 703 | end | 682 | end |
| 704 | end | 683 | end |
| 684 | return false | ||
| 685 | end | ||
| 686 | |||
| 687 | local done = false | ||
| 688 | for lineno, line in ipairs(file) do | ||
| 689 | done = process_line(line, lineno) | ||
| 690 | if done then | ||
| 691 | break | ||
| 692 | end | ||
| 693 | end | ||
| 694 | if not done then | ||
| 695 | if hunkno <= #hunks and not create_file then | ||
| 696 | warning(format("premature end of source file %s at hunk %d", | ||
| 697 | source, hunkno)) | ||
| 698 | return false | ||
| 699 | end | ||
| 700 | end | ||
| 701 | if validhunks < #hunks then | ||
| 702 | if check_patched(file, hunks) then | ||
| 703 | warning(format("already patched %s", source)) | ||
| 704 | elseif not create_file then | ||
| 705 | warning(format("source file is different - %s", source)) | ||
| 706 | return false | ||
| 707 | end | ||
| 708 | end | ||
| 709 | if not canpatch then | ||
| 710 | return true | ||
| 711 | end | ||
| 712 | local backupname = source .. ".orig" | ||
| 713 | if exists(backupname) then | ||
| 714 | warning(format("can't backup original file to %s - aborting", | ||
| 715 | backupname)) | ||
| 716 | return false | ||
| 717 | end | ||
| 718 | local ok = os.rename(source, backupname) | ||
| 719 | if not ok then | ||
| 720 | warning(format("failed backing up %s when patching", source)) | ||
| 721 | return false | ||
| 722 | end | ||
| 723 | ok = patch_hunks(backupname, source, hunks) | ||
| 724 | if not ok then | ||
| 725 | warning(format("error patching file %s", source)) | ||
| 726 | if file_copy(source, source .. ".invalid") then | ||
| 727 | warning(format("invalid version is saved to %s", | ||
| 728 | source .. ".invalid")) | ||
| 729 | -- todo: proper rejects | ||
| 730 | os.rename(backupname, source) | ||
| 731 | end | ||
| 732 | return false | ||
| 733 | end | ||
| 734 | info(format("successfully patched %s", source)) | ||
| 735 | os.remove(backupname) | ||
| 736 | return true | ||
| 737 | end | ||
| 705 | 738 | ||
| 706 | end -- if not continue | 739 | function patch.apply_patch(the_patch, strip, create_delete) |
| 707 | end -- for | 740 | local all_ok = true |
| 741 | local total = #the_patch.source | ||
| 742 | for fileno, source in ipairs(the_patch.source) do | ||
| 743 | local target = the_patch.target[fileno] | ||
| 744 | local hunks = the_patch.hunks[fileno] | ||
| 745 | local epoch = the_patch.epoch[fileno] | ||
| 746 | info(format("processing %d/%d:\t %s", fileno, total, source)) | ||
| 747 | local ok = patch_file(source, target, epoch, hunks, strip, create_delete) | ||
| 748 | all_ok = all_ok and ok | ||
| 749 | end | ||
| 708 | -- todo: check for premature eof | 750 | -- todo: check for premature eof |
| 709 | return all_ok | 751 | return all_ok |
| 710 | end | 752 | end |
