summaryrefslogtreecommitdiff
path: root/xmake/modules/private/service/remote_build/filesync.lua
blob: d2c2d6703fe55f1192469dce15cedbef34e626db (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
--!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        filesync.lua
--

-- imports
import("core.base.object")

-- define module
local filesync = filesync or object()

-- init filesync
function filesync:init(rootdir, manifest_file)
    self._ROOTDIR = rootdir
    self._MANIFEST_FILE = manifest_file
end

-- get root directory
function filesync:rootdir()
    return self._ROOTDIR
end

-- get ignore files
function filesync:ignorefiles()
    local ignorefiles = self._IGNOREFILES
    if not ignorefiles then
        ignorefiles = {}
        self:_ignorefiles_load(ignorefiles)
        self._IGNOREFILES = ignorefiles
    end
    return ignorefiles
end

-- add ignore files
function filesync:ignorefiles_add(...)
    table.join2(self:ignorefiles(), ...)
end

-- get manifest
function filesync:manifest()
    local manifest = self._MANIFEST
    if not manifest then
        local manifest_file = self:manifest_file()
        if manifest_file and os.isfile(manifest_file) then
            manifest = io.load(manifest_file)
        end
        manifest = manifest or {}
        self._MANIFEST = manifest
    end
    return manifest
end

-- save manifest file
function filesync:manifest_save()
    local manifest_file = self:manifest_file()
    if manifest_file then
        io.save(manifest_file, self:manifest())
    end
end

-- get manifest file
function filesync:manifest_file()
    return self._MANIFEST_FILE
end

-- do snapshot, it will re-scan all and update to manifest file
function filesync:snapshot()
    local rootdir = self:rootdir()
    assert(rootdir and os.isdir(rootdir), "get snapshot %s failed, rootdir not found!", rootdir)
    local manifest = {}
    local manifest_old = self:manifest()
    local ignorefiles = self:ignorefiles()
    if ignorefiles then
        ignorefiles = "|" .. table.concat(ignorefiles, "|")
    end
    local count = 0
    -- we should not translate the whole pattern with ignorefiles in path.join,
    -- because the joined pattern string may be very long (> TB_PATH_MAXN)
    -- https://github.com/xmake-io/xmake/issues/6962
    for _, filepath in ipairs(os.files(path.join(rootdir, "**") .. ignorefiles)) do
        local fileitem = path.relative(filepath, rootdir)
        if fileitem then
            -- we should always use '/' in path key for supporting linux & windows
            -- https://github.com/xmake-io/xmake/issues/2488
            if is_host("windows") then
                fileitem = fileitem:gsub("\\", "/")
            end
            local manifest_info = manifest_old[fileitem]
            local mtime = os.mtime(filepath)
            if not manifest_info or not manifest_info.mtime or mtime > manifest_info.mtime then
                manifest[fileitem] = {sha256 = hash.sha256(filepath), mtime = mtime}
            else
                manifest[fileitem] = manifest_info
            end
            count = count + 1
        end
    end
    self._MANIFEST = manifest
    self:manifest_save()
    return manifest, count
end

-- update file
function filesync:update(fileitem, filepath, sha256)
    local mtime = os.mtime(filepath)
    local manifest = self:manifest()
    sha256 = sha256 or hash.sha256(filepath)
    manifest[fileitem] = {sha256 = sha256, mtime = mtime}
end

-- remove file
function filesync:remove(fileitem)
    local manifest = self:manifest()
    manifest[fileitem] = nil
end

-- load ignore files from .gitignore files
function filesync:_ignorefiles_load(ignorefiles)
    local rootdir = self:rootdir()
    local gitignore_files = os.files(path.join(rootdir, "**", ".gitignore"))
    if os.isfile(path.join(rootdir, ".gitignore")) then
        table.insert(gitignore_files, path.join(rootdir, ".gitignore"))
    end
    for _, gitignore_file in ipairs(gitignore_files) do
        local gitroot = path.directory(gitignore_file)
        local gitignore = io.open(gitignore_file, "r")
        for line in gitignore:lines() do
            local line = line:trim()
            if #line > 0 and not line:startswith("#") then
                local filepath = path.join(gitroot, line)
                local pattern = path.relative(filepath, rootdir)
                if pattern then
                    if line:endswith(path.sep()) or os.isdir(line) then
                        table.insert(ignorefiles, path.join(pattern, "**"))
                    elseif os.isfile(line) then
                        table.insert(ignorefiles, pattern)
                    elseif line:find("*.", 1, true) then
                        table.insert(ignorefiles, (pattern:gsub("%*%.", "**.")))
                    else
                        table.insert(ignorefiles, pattern)
                        table.insert(ignorefiles, path.join(pattern, "**"))
                    end
                end
            end
        end
        gitignore:close()
    end
end

function main(rootdir, manifest_file)
    local instance = filesync()
    instance:init(rootdir, manifest_file)
    return instance
end