--!A cross-platform build utility based on Lua -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. -- You may obtain a copy of the License at -- -- http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, software -- distributed under the License is distributed on an "AS IS" BASIS, -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- See the License for the specific language governing permissions and -- limitations under the License. -- -- Copyright (C) 2015-present, Xmake Open Source Community. -- -- @author ruki -- @file extract.lua -- -- imports import("core.base.option") import("lib.detect.find_file") import("lib.detect.find_tool") import("extract_xmz") import("extension", {alias = "get_archive_extension"}) -- extract archivefile using xmake decompress module function _extract_using_xmz(archivefile, outputdir, extension, opt) extract_xmz(archivefile, outputdir, opt) return true end -- extract archivefile using tar function _extract_using_tar(archivefile, outputdir, extension, opt) -- the tar on windows can only extract "*.tar", "*.tar.gz" -- the tar on msys2 can extract more, like "*.tar.bz2", .. if os.host() == "windows" and (extension ~= ".tar" and extension ~= ".tar.gz") then return false end -- find tar local tar = find_tool("tar") if not tar then return false end local program = tar.program -- init argv local argv = {} if is_host("windows") then -- force "x:\\xx" as local file local force_local = _g.force_local if force_local == nil then force_local = try {function () local result = os.iorunv(program, {"--help"}) if result and result:find("--force-local", 1, true) then return true end end} _g.force_local = force_local or false end if force_local then table.insert(argv, "--force-local") end end table.insert(argv, "-xf") table.insert(argv, path.absolute(archivefile)) -- ensure output directory if not os.isdir(outputdir) then os.mkdir(outputdir) end -- set outputdir if not is_host("windows") then table.insert(argv, "-C") table.insert(argv, outputdir) end -- excludes files if opt.excludes then table.insert(argv, "--exclude") for _, exclude in ipairs(opt.excludes) do table.insert(argv, exclude) end end -- extract it if is_host("windows") then os.vrunv(program, argv, {curdir = outputdir}) else os.vrunv(program, argv) end return true end -- extract archivefile using 7z function _extract_using_7z(archivefile, outputdir, extension, opt) -- find 7z local z7 = find_tool("7z") if not z7 then return false end local program = z7.program -- p7zip cannot extract other archive format on msys/cygwin, but it can extract .tgz -- https://github.com/xmake-io/xmake/issues/1575#issuecomment-898205462 if is_subhost("msys", "cygwin") and program:startswith("sh ") and extension ~= ".7z" and extension ~= ".tgz" then return false end -- extract to *.tar file first local outputdir_old = nil if extension:startswith(".tar.") or extension == ".tgz" then outputdir_old = outputdir outputdir = os.tmpfile({ramdisk = false}) .. ".tar" end -- on msys2/cygwin? we need to translate input path to cygwin-like path if is_subhost("msys", "cygwin") and program:gsub("\\", "/"):find("/usr/bin") then archivefile = path.cygwin_path(archivefile) end -- init argv local argv = {"x", "-y", archivefile} -- disable to store symlinks on windows if is_host("windows") then table.insert(argv, "-snl-") end -- ensure output directory if not os.isdir(outputdir) then os.mkdir(outputdir) end -- set outputdir table.insert(argv, "-o" .. outputdir) -- excludes files local excludesfile = nil if opt.excludes and not outputdir_old then excludesfile = os.tmpfile() io.writefile(excludesfile, table.concat(table.wrap(opt.excludes), '\n')) table.insert(argv, "-xr@" .. excludesfile) end -- extract it os.vrunv(program, argv) -- remove the excludes file if excludesfile then os.tryrm(excludesfile) end -- remove unused pax_global_header file after extracting .tar file if extension == ".tar" then os.tryrm(path.join(outputdir, "pax_global_header")) os.tryrm(path.join(outputdir, "PaxHeaders*")) os.tryrm(path.join(outputdir, "@PaxHeader")) -- https://github.com/xmake-io/xmake-repo/pull/2673 os.tryrm(path.join(outputdir, "*.paxheader")) end return _extract_uncompressed_tar(outputdir_old, outputdir, opt) end -- extract archivefile using gzip function _extract_using_gzip(archivefile, outputdir, extension, opt) -- find gzip local gzip = find_tool("gzip") if not gzip then return false end local program = gzip.program -- extract to *.tar file first local outputdir_old = nil if extension:startswith(".tar.") then outputdir_old = outputdir outputdir = os.tmpfile({ramdisk = false}) .. ".tar" end -- init temporary archivefile local tmpfile = path.join(outputdir, path.filename(archivefile)) -- init argv local argv = {"-d", "-f"} if not option.get("verbose") then table.insert(argv, "-q") end table.insert(argv, tmpfile) -- ensure output directory if not os.isdir(outputdir) then os.mkdir(outputdir) end -- copy archivefile to outputdir first if path.absolute(archivefile) ~= path.absolute(tmpfile) then os.cp(archivefile, tmpfile) end -- extract it os.vrunv(program, argv, {curdir = outputdir}) return _extract_uncompressed_tar(outputdir_old, outputdir, opt) end -- extract archivefile using xz function _extract_using_xz(archivefile, outputdir, extension, opt) -- find xz local xz = find_tool("xz") if not xz then return false end local program = xz.program -- extract to *.tar file first local outputdir_old = nil if extension:startswith(".tar.") then outputdir_old = outputdir outputdir = os.tmpfile({ramdisk = false}) .. ".tar" end -- init temporary archivefile local tmpfile = path.join(outputdir, path.filename(archivefile)) -- init argv local argv = {"-d", "-f"} if not option.get("verbose") then table.insert(argv, "-q") end table.insert(argv, tmpfile) -- ensure output directory if not os.isdir(outputdir) then os.mkdir(outputdir) end -- copy archivefile to outputdir first if path.absolute(archivefile) ~= path.absolute(tmpfile) then os.cp(archivefile, tmpfile) end -- extract it os.vrunv(program, argv, {curdir = outputdir}) return _extract_uncompressed_tar(outputdir_old, outputdir, opt) end -- extract archivefile using zstd function _extract_using_zstd(archivefile, outputdir, extension, opt) -- find zstd local zstd = find_tool("zstd") if not zstd then return false end local program = zstd.program -- extract to *.tar file first local outputdir_old = nil if extension:startswith(".tar.") then outputdir_old = outputdir outputdir = os.tmpfile({ramdisk = false}) .. ".tar" end -- init temporary archivefile local tmpfile = path.join(outputdir, path.filename(archivefile)) -- init argv local argv = {"-d"} if not option.get("verbose") then table.insert(argv, "-q") end table.insert(argv, tmpfile) -- ensure output directory if not os.isdir(outputdir) then os.mkdir(outputdir) end -- copy archivefile to outputdir first if path.absolute(archivefile) ~= path.absolute(tmpfile) then os.cp(archivefile, tmpfile) end -- extract it os.vrunv(program, argv, {curdir = outputdir}) return _extract_uncompressed_tar(outputdir_old, outputdir, opt) end -- extract archivefile using unzip function _extract_using_unzip(archivefile, outputdir, extension, opt) -- find unzip local unzip = find_tool("unzip") if not unzip then return false end local program = unzip.program -- extract to *.tar file first local outputdir_old = nil if extension:startswith(".tar.") then outputdir_old = outputdir outputdir = os.tmpfile({ramdisk = false}) .. ".tar" end -- init argv local argv = {"-o"} -- overwrite existing files without prompting if not option.get("verbose") then table.insert(argv, "-q") end table.insert(argv, archivefile) -- ensure output directory if not os.isdir(outputdir) then os.mkdir(outputdir) end -- extract to outputdir table.insert(argv, "-d") table.insert(argv, outputdir) -- excludes files if opt.excludes and not outputdir_old then table.insert(argv, "-x") for _, exclude in ipairs(opt.excludes) do table.insert(argv, exclude) end end -- extract it os.vrunv(program, argv) return _extract_uncompressed_tar(outputdir_old, outputdir, opt) end -- extract archivefile using powershell -- powershell -ExecutionPolicy Bypass -File "D:\scripts\unzip.ps1" "archivefile" "outputdir" function _extract_using_powershell(archivefile, outputdir, extension, opt) -- find powershell local powershell = find_tool("pwsh") or find_tool("powershell") if not powershell then return false end -- get the script file local scriptfile = path.join(os.programdir(), "scripts", "unzip.ps1") -- extract to *.tar file first local outputdir_old = nil if extension:startswith(".tar.") then outputdir_old = outputdir outputdir = os.tmpfile({ramdisk = false}) .. ".tar" end -- ensure output directory if not os.isdir(outputdir) then os.mkdir(outputdir) end -- extract it local argv = {"-ExecutionPolicy", "Bypass", "-File", scriptfile, archivefile, outputdir} os.vrunv(powershell.program, argv) return _extract_uncompressed_tar(outputdir_old, outputdir, opt) end -- extract archivefile using bzip2 function _extract_using_bzip2(archivefile, outputdir, extension, opt) -- find bzip2 local bzip2 = find_tool("bzip2") if not bzip2 then return false end local program = bzip2.program -- extract to *.tar file first local outputdir_old = nil if extension:startswith(".tar.") then outputdir_old = outputdir outputdir = os.tmpfile({ramdisk = false}) .. ".tar" end -- on msys2/cygwin? we need to translate input path to cygwin-like path if is_subhost("msys", "cygwin") and program:gsub("\\", "/"):find("/usr/bin") then archivefile = path.cygwin_path(archivefile) end -- init temporary archivefile local tmpfile = path.join(outputdir, path.filename(archivefile)) -- init argv local argv = {"-d", "-f"} if not option.get("verbose") then table.insert(argv, "-q") end table.insert(argv, tmpfile) -- ensure output directory if not os.isdir(outputdir) then os.mkdir(outputdir) end -- copy archivefile to outputdir first if path.absolute(archivefile) ~= path.absolute(tmpfile) then os.cp(archivefile, tmpfile) end -- extract it os.vrunv(program, argv, {curdir = outputdir}) return _extract_uncompressed_tar(outputdir_old, outputdir, opt) end -- extract *.tar after decompress function _extract_uncompressed_tar(outputdir_old, outputdir, opt) if outputdir_old then local tarfile = find_file("**.tar", outputdir) if tarfile and os.isfile(tarfile) then local ok = _extract(tarfile, outputdir_old, ".tar", {_extract_using_7z, _extract_using_tar}, opt) -- remove the temporary tar file -- @see https://github.com/xmake-io/xmake/issues/6311 os.rm(tarfile) os.rm(outputdir, {emptydirs = true}) return ok end end return true end -- extract archive file using extractors function _extract(archivefile, outputdir, extension, extractors, opt) local errors for _, extract in ipairs(extractors) do local ok = try { function () return extract(archivefile, outputdir, extension, opt) end, catch { function (errs) if errs then errors = tostring(errs) end end } } if ok then return true end end raise("cannot extract %s, %s!", path.filename(archivefile), errors or "no extractor(like unzip, ...) found") end -- extract file -- -- @param archivefile the archive file. e.g. *.tar.gz, *.zip, *.7z, *.tar.bz2, .. -- @param outputdir the output directory -- @param options the options, e.g.. {excludes = {"*/dir/*", "dir/*"}} -- function main(archivefile, outputdir, opt) opt = opt or {} outputdir = outputdir or os.curdir() -- init extractors local extractors if is_subhost("windows") then -- we use 7z first, becase xmake package has builtin 7z program on windows -- tar/windows can not extract .bz2 ... -- 7z doesn't support zstd by default extractors = { [".zip"] = {_extract_using_7z, _extract_using_unzip, _extract_using_tar, _extract_using_powershell} , [".7z"] = {_extract_using_7z} , [".gz"] = {_extract_using_7z, _extract_using_gzip, _extract_using_tar} , [".xz"] = {_extract_using_7z, _extract_using_xz, _extract_using_tar} , [".zst"] = {_extract_using_zstd, _extract_using_tar} , [".tgz"] = {_extract_using_7z, _extract_using_tar} , [".bz2"] = {_extract_using_7z, _extract_using_bzip2} , [".tar"] = {_extract_using_7z, _extract_using_tar} -- @see https://github.com/xmake-io/xmake/issues/5538 , [".tar.gz"] = {_extract_using_tar, _extract_using_7z, _extract_using_gzip} , [".tar.xz"] = {_extract_using_7z, _extract_using_xz} , [".tar.zst"] = {_extract_using_zstd} , [".tar.bz2"] = {_extract_using_7z, _extract_using_bzip2} , [".tar.lz"] = {_extract_using_7z} , [".tar.Z"] = {_extract_using_7z} , [".xmz"] = {_extract_using_xmz} } else extractors = { -- tar supports .zip on macOS but does not on Linux [".zip"] = is_host("macosx") and {_extract_using_unzip, _extract_using_tar, _extract_using_7z} or {_extract_using_unzip, _extract_using_7z} , [".7z"] = {_extract_using_7z} , [".gz"] = {_extract_using_gzip, _extract_using_tar, _extract_using_7z} , [".xz"] = {_extract_using_xz, _extract_using_tar, _extract_using_7z} , [".zst"] = {_extract_using_zstd, _extract_using_tar} , [".tgz"] = {_extract_using_tar, _extract_using_7z} , [".bz2"] = {_extract_using_bzip2, _extract_using_tar, _extract_using_7z} , [".tar"] = {_extract_using_tar, _extract_using_7z} , [".tar.gz"] = {_extract_using_tar, _extract_using_7z, _extract_using_gzip} , [".tar.xz"] = {_extract_using_tar, _extract_using_7z, _extract_using_xz} , [".tar.zst"] = {_extract_using_tar, _extract_using_zstd} , [".tar.bz2"] = {_extract_using_tar, _extract_using_7z, _extract_using_bzip2} , [".tar.lz"] = {_extract_using_tar, _extract_using_7z} , [".tar.Z"] = {_extract_using_tar, _extract_using_7z} , [".xmz"] = {_extract_using_xmz} } end -- get extension local extension = opt.extension or get_archive_extension(archivefile) -- extract it return _extract(archivefile, outputdir, extension, extractors[extension], opt) end