#!/usr/bin/env lua --[[This file is part of MXE. See LICENSE.md for licensing information. build-pkg, Build binary packages from MXE packagesInstructions: https://pkg.mxe.cc/ Requirements (see bootstrapped build below for non-debian systems): MXE (https://mxe.cc/#requirements-debian) apt-get install lua5.1 fakeroot dpkg dpkg-devUsage: lua tools/build-pkg.luaPackages are written to `<codename>/*.tar.xz` files.Debian packages are written to `<codename>/*.deb` files. Build in directory /usr/lib/mxeThis directory can not be changed in .deb packages. To prevent build-pkg from creating deb packages,set environment variable MXE_BUILD_PKG_NO_DEBS to 1In this case fakeroot and dpkg-deb are not needed. To do a dry run without actually building any packages,set environment variable MXE_BUILD_DRY_RUN to any valuePackages will be downloaded, but builds will be skipped. To override the codename detection of `lsb_release -sc`, setMXE_BUILD_PKG_CODENAME. This sets the output directory and namemangling for the pool directory in the apt repo. Could be used tocreate lowest-common-glibc based versions. Set MXE_BUILD_PKG_VERSION_ID to add a git tag to each pkg versionstring - defaults to YYYYMMDD build datestamp. To switch off the second pass, setMXE_BUILD_PKG_NO_SECOND_PASS to 1.See https://github.com/mxe/mxe/issues/1111 To limit number of packages being built to x,set environment variable MXE_BUILD_PKG_MAX_ITEMS to x. To set list of MXE targets to build,set environment variable MXE_BUILD_PKG_TARGETS tothe list of targets separated by space.By default, all 4 major targets are built. To set list of MXE packages to build,set environment variable MXE_BUILD_PKG_PKGS tothe list of packages separated by space. This is similarto a normal `make` invocation in that all dependencieswill be built, so list just the packages you require.By default, all packages are built. The following error:> fakeroot, while creating message channels: Invalid argument> This may be due to a lack of SYSV IPC support.> fakeroot: error while starting the `faked' daemon.can be caused by leaked ipc resources originating in fakeroot.How to remove them: https://stackoverflow.com/a/4262545Alternatively, to switch off using fakeroot (e.g. inside docker),set MXE_BUILD_PKG_NO_FAKEROOT to 1. Bootstrapped build (non-debian systems building withoutdeb pkgs to test print-deps-for-build-pkg, control files etc.): export MXE_DIR=`pwd` && \export BUILD=`$MXE_DIR/ext/config.guess` && \rm -rf $MXE_DIR/usr $MXE_DIR/log $MXE_DIR/mxe-* && \make -C $MXE_DIR lua \ MXE_TARGETS=$BUILD \ lua_TARGETS=$BUILD \ MXE_PREFIX=$MXE_DIR/usr.lua && \MXE_BUILD_PKG_CODENAME=focal \MXE_BUILD_PKG_TARGETS="i686-w64-mingw32.static" \MXE_BUILD_PKG_PKGS=qt5 \MXE_BUILD_DRY_RUN=1 \MXE_BUILD_PKG_MAX_ITEMS= \MXE_BUILD_PKG_NO_DEBS=1 \MXE_BUILD_PKG_NO_SECOND_PASS=1 \$MXE_DIR/usr.lua/$BUILD/bin/lua $MXE_DIR/tools/build-pkg.lua]] local max_items = tonumber(os.getenv('MXE_BUILD_PKG_MAX_ITEMS'))local no_debs = os.getenv('MXE_BUILD_PKG_NO_DEBS')local no_fakeroot = os.getenv('MXE_BUILD_PKG_NO_FAKEROOT')local no_second_pass = os.getenv('MXE_BUILD_PKG_NO_SECOND_PASS')local build_targets = os.getenv('MXE_BUILD_PKG_TARGETS') local VERSION_ID = os.getenv('MXE_BUILD_PKG_VERSION_ID') or os.date("%Y%m%d") local MAX_TRIES = 10 local GIT = 'git --work-tree=./usr/ --git-dir=./usr/.git 'local GIT_USER = '-c user.name="build-pkg" ' .. '-c user.email="build-pkg@mxe" ' local BLACKLIST = { '^usr/installed/check%-requirements$', -- usr/share/cmake is useful '^usr/share/doc/', '^usr/share/info/', '^usr/share/man/', '^usr/share/gcc', '^usr/share/gtk-doc', '^usr/[^/]+/share/doc/', '^usr/[^/]+/share/info/', -- usr/lib/nonetwork.so and -- usr/x86_64-unknown-linux-gnu/lib/nonetwork.so 'lib/nonetwork.so', -- https://github.com/mxe/mxe/issues/1886#issuecomment-331719282 'installed/.gitkeep', 'lib/.gitkeep',} local TARGETS = { 'i686-w64-mingw32.static', 'x86_64-w64-mingw32.static', 'i686-w64-mingw32.shared', 'x86_64-w64-mingw32.shared',}if build_targets then TARGETS = {} for target in build_targets:gmatch('(%S+)') do table.insert(TARGETS, target) endend local function echo(fmt, ...) print(fmt:format(...)) io.stdout:flush()end local function log(fmt, ...) echo('[build-pkg]\t' .. fmt, ...)end -- based on http://lua-users.org/wiki/SplitJoinlocal function split(self, sep, nMax, plain) if not sep then sep = '%s+' end assert(sep ~= '') assert(nMax == nil or nMax >= 1) local aRecord = {} if self:len() > 0 then nMax = nMax or -1 local nField = 1 local nStart = 1 local nFirst, nLast = self:find(sep, nStart, plain) while nFirst and nMax ~= 0 do aRecord[nField] = self:sub(nStart, nFirst - 1) nField = nField + 1 nStart = nLast + 1 nFirst, nLast = self:find(sep, nStart, plain) nMax = nMax - 1 end aRecord[nField] = self:sub(nStart) end return aRecordend local function trim(str) local text = str:gsub("%s+$", "") text = text:gsub("^%s+", "") return textend local function isInArray(element, array) for _, member in ipairs(array) do if member == element then return true end end return falseend local function sliceArray(list, nelements) nelements = nelements or #list local new_list = {} for i = 1, nelements do new_list[i] = list[i] end return new_listend local function concatArrays(...) local result = {} for _, array in ipairs({...}) do for _, elem in ipairs(array) do table.insert(result, elem) end end return resultend local function isInString(substring, string) return string:find(substring, 1, true)end local function shell(cmd) local f = io.popen(cmd, 'r') local text = f:read('*all') f:close() return textend local function execute(cmd) if _VERSION == 'Lua 5.1' then return os.execute(cmd) == 0 else -- Lua >= 5.2 return os.execute(cmd) endend local MXE_DIR = trim(shell('pwd')) -- for tar, try gtar and gnutar firstlocal tools = {}local function tool(name) if tools[name] then return tools[name] end if execute(("g%s --help > /dev/null 2>&1"):format(name)) then tools[name] = 'g' .. name elseif execute(("gnu%s --help > /dev/null 2>&1"):format(name)) then tools[name] = 'gnu' .. name else tools[name] = name end return tools[name]end local function fileExists(name) local f = io.open(name, "r") if f ~= nil then io.close(f) return true else return false endend local function fileSize(name) local f = io.open(name, "r") local size = f:seek("end") io.close(f) return sizeend local function isSymlink(name) return shell(("ls -l %q"):format(name)):sub(1, 1) == "l"end local function writeFile(filename, data) local file = io.open(filename, 'w') file:write(data) file:close()end local NATIVE_TARGET = trim(shell("ext/config.guess"))local function isCross(target) return target ~= NATIVE_TARGETend local function getArch() local cmd = "dpkg-architecture -qDEB_BUILD_ARCH 2> /dev/null" return trim(shell(cmd))endlocal ARCH = getArch() -- return target and package from item namelocal function parseItem(item) return item:match("([^~]+)~([^~]+)")end -- return item name from target and packagelocal function makeItem(target, package) return target .. '~' .. packageend -- return several tables describing packages for all targets-- * list of items-- * map from item to list of deps (which are also items)-- * map from item to version-- Item is a string like "target~pkg"local function getItems() local items = {} local item2deps = {} local item2ver = {} local cmd = '%s print-deps-for-build-pkg MXE_TARGETS=%q' cmd = cmd:format(tool 'make', table.concat(TARGETS, ' ')) local make = io.popen(cmd) for line in make:lines() do local deps = split(trim(line)) if deps[1] == 'for-build-pkg' then -- first value is marker 'for-build-pkg' table.remove(deps, 1) -- first value is name of package which depends on local item = table.remove(deps, 1) -- second value is version of package local ver = table.remove(deps, 1) table.insert(items, item) item2deps[item] = deps item2ver[item] = ver local target, _ = parseItem(item) for _, dep_item in ipairs(deps) do local target2, _ = parseItem(dep_item) if isCross(target2) and target2 ~= target then log("Cross-target dependency %s -> %s", target2, target) end end end end make:close() return items, item2deps, item2verend local function getInstalled() local installed = {} local f = io.popen('ls usr/*/installed/*') local pattern = '/([^/]+)/installed/([^/]+)' for file in f:lines() do local target, pkg = assert(file:match(pattern)) table.insert(installed, makeItem(target, pkg)) end f:close() return installedend -- graph is a map from item to a list of destinationslocal function transpose(graph) local transposed = {} for item, destinations in pairs(graph) do for _, dest in ipairs(destinations) do if not transposed[dest] then transposed[dest] = {} end table.insert(transposed[dest], item) end end return transposedend local function reverse(list) local n = #list local reversed = {} for i = 1, n do reversed[i] = list[n - i + 1] end return reversedend -- return items ordered in build order-- this means, if item depends on item2, then-- item2 precedes item1 in the listlocal function sortForBuild(items, item2deps) local n = #items local item2followers = transpose(item2deps) -- Tarjan's algorithm -- https://en.wikipedia.org/wiki/Topological_sorting local build_list_reversed = {} local marked_permanently = {} local marked_temporarily = {} local function visit(item1) assert(not marked_temporarily[item1], 'not a DAG') if not marked_permanently[item1] then marked_temporarily[item1] = true local followers = item2followers[item1] or {} for _, item2 in ipairs(followers) do visit(item2) end marked_permanently[item1] = true marked_temporarily[item1] = false table.insert(build_list_reversed, item1) end end for _, item in ipairs(items) do if not marked_permanently[item] then visit(item) end end assert(#build_list_reversed == n) local build_list = reverse(build_list_reversed) assert(#build_list == n) return build_listend local function isDependency(item, dependency, item2deps) for _, dep in ipairs(item2deps[item]) do if dep == dependency then return true end if isDependency(dep, dependency, item2deps) then return true end end return falseend local function makeItem2Index(build_list) local item2index = {} for index, item in ipairs(build_list) do assert(not item2index[item], 'Duplicate item') item2index[item] = index end return item2indexend -- return if build_list is ordered topologicallylocal function isTopoOrdered(build_list, items, item2deps) if #build_list ~= #items then return false, 'Length of build_list is wrong' end local item2index = makeItem2Index(build_list) for item, deps in pairs(item2deps) do for _, dep in ipairs(deps) do if item2index[item] < item2index[dep] then return false, 'Item ' .. item .. 'is built before its dependency ' .. dep end end end return trueend local function isListed(file, list) for _, pattern in ipairs(list) do if file:match(pattern) then return true end end return falseend local function isBlacklisted(file) return isListed(file, BLACKLIST)end local GIT_INITIAL = 'initial'local GIT_ALL_PSEUDOITEM = 'all' local function itemToBranch(item, pass) return pass .. '-' .. item:gsub('~', '_')end -- creates git repo in ./usrlocal function gitInit() os.execute('mkdir -p ./usr') os.execute(GIT .. 'init --quiet')end local function gitTag(name) os.execute(GIT .. 'tag ' .. name)end local function gitConflicts() local cmd = GIT .. 'diff --name-only --diff-filter=U' local f = io.popen(cmd, 'r') local conflicts = {} for conflict in f:lines() do table.insert(conflicts, conflict) end f:close() return conflictsend -- git commits changes in ./usrlocal function gitCommit(message) local cmd = GIT .. GIT_USER .. 'commit -a -m %q --quiet' assert(execute(cmd:format(message)))end local function gitCheckout(new_branch, deps, item2index, pass_of_deps) local main_dep = deps[1] if main_dep then main_dep = itemToBranch(main_dep, pass_of_deps) else main_dep = GIT_INITIAL end local cmd = '%s checkout -q -b %s %s' assert(execute(cmd:format(GIT, new_branch, main_dep))) -- merge with other dependencies for i = 2, #deps do local message = 'Merge with ' .. deps[i] local cmd2 = '%s %s merge -q %s -m %q' if not execute(cmd2:format(GIT, GIT_USER, itemToBranch(deps[i], pass_of_deps), message)) then -- probably merge conflict local conflicts = table.concat(gitConflicts(), ' ') log('Merge conflicts: %s', conflicts) local cmd3 = '%s checkout --ours %s' assert(execute(cmd3:format(GIT, conflicts)))Lines 1–500 of 1166. Load more
#!/usr/bin/env lua --[[ This file is part of MXE. See LICENSE.md for licensing information. build-pkg, Build binary packages from MXE packages Instructions: https://pkg.mxe.cc/ Requirements (see bootstrapped build below for non-debian systems): MXE (https://mxe.cc/#requirements-debian) apt-get install lua5.1 fakeroot dpkg dpkg-dev Usage: lua tools/build-pkg.lua Packages are written to `<codename>/*.tar.xz` files. Debian packages are written to `<codename>/*.deb` files. Build in directory /usr/lib/mxe This directory can not be changed in .deb packages. To prevent build-pkg from creating deb packages, set environment variable MXE_BUILD_PKG_NO_DEBS to 1 In this case fakeroot and dpkg-deb are not needed. To do a dry run without actually building any packages, set environment variable MXE_BUILD_DRY_RUN to any value Packages will be downloaded, but builds will be skipped. To override the codename detection of `lsb_release -sc`, set MXE_BUILD_PKG_CODENAME. This sets the output directory and name mangling for the pool directory in the apt repo. Could be used to create lowest-common-glibc based versions. Set MXE_BUILD_PKG_VERSION_ID to add a git tag to each pkg version string - defaults to YYYYMMDD build datestamp. To switch off the second pass, set MXE_BUILD_PKG_NO_SECOND_PASS to 1. See https://github.com/mxe/mxe/issues/1111 To limit number of packages being built to x, set environment variable MXE_BUILD_PKG_MAX_ITEMS to x. To set list of MXE targets to build, set environment variable MXE_BUILD_PKG_TARGETS to the list of targets separated by space. By default, all 4 major targets are built. To set list of MXE packages to build, set environment variable MXE_BUILD_PKG_PKGS to the list of packages separated by space. This is similar to a normal `make` invocation in that all dependencies will be built, so list just the packages you require. By default, all packages are built. The following error: > fakeroot, while creating message channels: Invalid argument > This may be due to a lack of SYSV IPC support. > fakeroot: error while starting the `faked' daemon. can be caused by leaked ipc resources originating in fakeroot. How to remove them: https://stackoverflow.com/a/4262545 Alternatively, to switch off using fakeroot (e.g. inside docker), set MXE_BUILD_PKG_NO_FAKEROOT to 1. Bootstrapped build (non-debian systems building without deb pkgs to test print-deps-for-build-pkg, control files etc.): export MXE_DIR=`pwd` && \ export BUILD=`$MXE_DIR/ext/config.guess` && \ rm -rf $MXE_DIR/usr $MXE_DIR/log $MXE_DIR/mxe-* && \ make -C $MXE_DIR lua \ MXE_TARGETS=$BUILD \ lua_TARGETS=$BUILD \ MXE_PREFIX=$MXE_DIR/usr.lua && \ MXE_BUILD_PKG_CODENAME=focal \ MXE_BUILD_PKG_TARGETS="i686-w64-mingw32.static" \ MXE_BUILD_PKG_PKGS=qt5 \ MXE_BUILD_DRY_RUN=1 \ MXE_BUILD_PKG_MAX_ITEMS= \ MXE_BUILD_PKG_NO_DEBS=1 \ MXE_BUILD_PKG_NO_SECOND_PASS=1 \ $MXE_DIR/usr.lua/$BUILD/bin/lua $MXE_DIR/tools/build-pkg.lua ]] local max_items = tonumber(os.getenv('MXE_BUILD_PKG_MAX_ITEMS')) local no_debs = os.getenv('MXE_BUILD_PKG_NO_DEBS') local no_fakeroot = os.getenv('MXE_BUILD_PKG_NO_FAKEROOT') local no_second_pass = os.getenv('MXE_BUILD_PKG_NO_SECOND_PASS') local build_targets = os.getenv('MXE_BUILD_PKG_TARGETS') local VERSION_ID = os.getenv('MXE_BUILD_PKG_VERSION_ID') or os.date("%Y%m%d") local MAX_TRIES = 10 local GIT = 'git --work-tree=./usr/ --git-dir=./usr/.git ' local GIT_USER = '-c user.name="build-pkg" ' .. '-c user.email="build-pkg@mxe" ' local BLACKLIST = { '^usr/installed/check%-requirements$', -- usr/share/cmake is useful '^usr/share/doc/', '^usr/share/info/', '^usr/share/man/', '^usr/share/gcc', '^usr/share/gtk-doc', '^usr/[^/]+/share/doc/', '^usr/[^/]+/share/info/', -- usr/lib/nonetwork.so and -- usr/x86_64-unknown-linux-gnu/lib/nonetwork.so 'lib/nonetwork.so', -- https://github.com/mxe/mxe/issues/1886#issuecomment-331719282 'installed/.gitkeep', 'lib/.gitkeep', } local TARGETS = { 'i686-w64-mingw32.static', 'x86_64-w64-mingw32.static', 'i686-w64-mingw32.shared', 'x86_64-w64-mingw32.shared', } if build_targets then TARGETS = {} for target in build_targets:gmatch('(%S+)') do table.insert(TARGETS, target) end end local function echo(fmt, ...) print(fmt:format(...)) io.stdout:flush() end local function log(fmt, ...) echo('[build-pkg]\t' .. fmt, ...) end -- based on http://lua-users.org/wiki/SplitJoin local function split(self, sep, nMax, plain) if not sep then sep = '%s+' end assert(sep ~= '') assert(nMax == nil or nMax >= 1) local aRecord = {} if self:len() > 0 then nMax = nMax or -1 local nField = 1 local nStart = 1 local nFirst, nLast = self:find(sep, nStart, plain) while nFirst and nMax ~= 0 do aRecord[nField] = self:sub(nStart, nFirst - 1) nField = nField + 1 nStart = nLast + 1 nFirst, nLast = self:find(sep, nStart, plain) nMax = nMax - 1 end aRecord[nField] = self:sub(nStart) end return aRecord end local function trim(str) local text = str:gsub("%s+$", "") text = text:gsub("^%s+", "") return text end local function isInArray(element, array) for _, member in ipairs(array) do if member == element then return true end end return false end local function sliceArray(list, nelements) nelements = nelements or #list local new_list = {} for i = 1, nelements do new_list[i] = list[i] end return new_list end local function concatArrays(...) local result = {} for _, array in ipairs({...}) do for _, elem in ipairs(array) do table.insert(result, elem) end end return result end local function isInString(substring, string) return string:find(substring, 1, true) end local function shell(cmd) local f = io.popen(cmd, 'r') local text = f:read('*all') f:close() return text end local function execute(cmd) if _VERSION == 'Lua 5.1' then return os.execute(cmd) == 0 else -- Lua >= 5.2 return os.execute(cmd) end end local MXE_DIR = trim(shell('pwd')) -- for tar, try gtar and gnutar first local tools = {} local function tool(name) if tools[name] then return tools[name] end if execute(("g%s --help > /dev/null 2>&1"):format(name)) then tools[name] = 'g' .. name elseif execute(("gnu%s --help > /dev/null 2>&1"):format(name)) then tools[name] = 'gnu' .. name else tools[name] = name end return tools[name] end local function fileExists(name) local f = io.open(name, "r") if f ~= nil then io.close(f) return true else return false end end local function fileSize(name) local f = io.open(name, "r") local size = f:seek("end") io.close(f) return size end local function isSymlink(name) return shell(("ls -l %q"):format(name)):sub(1, 1) == "l" end local function writeFile(filename, data) local file = io.open(filename, 'w') file:write(data) file:close() end local NATIVE_TARGET = trim(shell("ext/config.guess")) local function isCross(target) return target ~= NATIVE_TARGET end local function getArch() local cmd = "dpkg-architecture -qDEB_BUILD_ARCH 2> /dev/null" return trim(shell(cmd)) end local ARCH = getArch() -- return target and package from item name local function parseItem(item) return item:match("([^~]+)~([^~]+)") end -- return item name from target and package local function makeItem(target, package) return target .. '~' .. package end -- return several tables describing packages for all targets -- * list of items -- * map from item to list of deps (which are also items) -- * map from item to version -- Item is a string like "target~pkg" local function getItems() local items = {} local item2deps = {} local item2ver = {} local cmd = '%s print-deps-for-build-pkg MXE_TARGETS=%q' cmd = cmd:format(tool 'make', table.concat(TARGETS, ' ')) local make = io.popen(cmd) for line in make:lines() do local deps = split(trim(line)) if deps[1] == 'for-build-pkg' then -- first value is marker 'for-build-pkg' table.remove(deps, 1) -- first value is name of package which depends on local item = table.remove(deps, 1) -- second value is version of package local ver = table.remove(deps, 1) table.insert(items, item) item2deps[item] = deps item2ver[item] = ver local target, _ = parseItem(item) for _, dep_item in ipairs(deps) do local target2, _ = parseItem(dep_item) if isCross(target2) and target2 ~= target then log("Cross-target dependency %s -> %s", target2, target) end end end end make:close() return items, item2deps, item2ver end local function getInstalled() local installed = {} local f = io.popen('ls usr/*/installed/*') local pattern = '/([^/]+)/installed/([^/]+)' for file in f:lines() do local target, pkg = assert(file:match(pattern)) table.insert(installed, makeItem(target, pkg)) end f:close() return installed end -- graph is a map from item to a list of destinations local function transpose(graph) local transposed = {} for item, destinations in pairs(graph) do for _, dest in ipairs(destinations) do if not transposed[dest] then transposed[dest] = {} end table.insert(transposed[dest], item) end end return transposed end local function reverse(list) local n = #list local reversed = {} for i = 1, n do reversed[i] = list[n - i + 1] end return reversed end -- return items ordered in build order -- this means, if item depends on item2, then -- item2 precedes item1 in the list local function sortForBuild(items, item2deps) local n = #items local item2followers = transpose(item2deps) -- Tarjan's algorithm -- https://en.wikipedia.org/wiki/Topological_sorting local build_list_reversed = {} local marked_permanently = {} local marked_temporarily = {} local function visit(item1) assert(not marked_temporarily[item1], 'not a DAG') if not marked_permanently[item1] then marked_temporarily[item1] = true local followers = item2followers[item1] or {} for _, item2 in ipairs(followers) do visit(item2) end marked_permanently[item1] = true marked_temporarily[item1] = false table.insert(build_list_reversed, item1) end end for _, item in ipairs(items) do if not marked_permanently[item] then visit(item) end end assert(#build_list_reversed == n) local build_list = reverse(build_list_reversed) assert(#build_list == n) return build_list end local function isDependency(item, dependency, item2deps) for _, dep in ipairs(item2deps[item]) do if dep == dependency then return true end if isDependency(dep, dependency, item2deps) then return true end end return false end local function makeItem2Index(build_list) local item2index = {} for index, item in ipairs(build_list) do assert(not item2index[item], 'Duplicate item') item2index[item] = index end return item2index end -- return if build_list is ordered topologically local function isTopoOrdered(build_list, items, item2deps) if #build_list ~= #items then return false, 'Length of build_list is wrong' end local item2index = makeItem2Index(build_list) for item, deps in pairs(item2deps) do for _, dep in ipairs(deps) do if item2index[item] < item2index[dep] then return false, 'Item ' .. item .. 'is built before its dependency ' .. dep end end end return true end local function isListed(file, list) for _, pattern in ipairs(list) do if file:match(pattern) then return true end end return false end local function isBlacklisted(file) return isListed(file, BLACKLIST) end local GIT_INITIAL = 'initial' local GIT_ALL_PSEUDOITEM = 'all' local function itemToBranch(item, pass) return pass .. '-' .. item:gsub('~', '_') end -- creates git repo in ./usr local function gitInit() os.execute('mkdir -p ./usr') os.execute(GIT .. 'init --quiet') end local function gitTag(name) os.execute(GIT .. 'tag ' .. name) end local function gitConflicts() local cmd = GIT .. 'diff --name-only --diff-filter=U' local f = io.popen(cmd, 'r') local conflicts = {} for conflict in f:lines() do table.insert(conflicts, conflict) end f:close() return conflicts end -- git commits changes in ./usr local function gitCommit(message) local cmd = GIT .. GIT_USER .. 'commit -a -m %q --quiet' assert(execute(cmd:format(message))) end local function gitCheckout(new_branch, deps, item2index, pass_of_deps) local main_dep = deps[1] if main_dep then main_dep = itemToBranch(main_dep, pass_of_deps) else main_dep = GIT_INITIAL end local cmd = '%s checkout -q -b %s %s' assert(execute(cmd:format(GIT, new_branch, main_dep))) -- merge with other dependencies for i = 2, #deps do local message = 'Merge with ' .. deps[i] local cmd2 = '%s %s merge -q %s -m %q' if not execute(cmd2:format(GIT, GIT_USER, itemToBranch(deps[i], pass_of_deps), message)) then -- probably merge conflict local conflicts = table.concat(gitConflicts(), ' ') log('Merge conflicts: %s', conflicts) local cmd3 = '%s checkout --ours %s' assert(execute(cmd3:format(GIT, conflicts)))