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 |