aboutsummaryrefslogtreecommitdiff
path: root/lib/cpt-source
blob: add5a14ccb916adce6ba2ce84eadebf6df10e68d (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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# Functions related to obtaining sources

pkg_sources() {
    # Download any remote package sources. The existence of local
    # files is also checked.
    repo_dir=$(pkg_find "$1")

    # Support packages without sources. Simply do nothing.
    [ -f "$repo_dir/sources" ] || return 0

    log "$1" "Downloading sources"

    # Store each downloaded source in a directory named after the
    # package it belongs to. This avoid conflicts between two packages
    # having a source of the same name.
    mkdir -p "$src_dir/$1" && cd "$src_dir/$1"

    repo_dir=$(pkg_find "$1")

    while read -r src dest || [ "$src" ]; do
        # Remote git/hg repository or comment.
        if [ -z "${src##\#*}" ]   ||
           [ -z "${src##git+*}" ] ||
           [ -z "${src##hg+*}" ]

        then :

        # Remote source (cached).
        elif [ -f "${src##*/}" ]; then
            log "$1" "Found cached source '${src##*/}'"

        # Remote source.
        elif [ -z "${src##*://*}" ]; then
            log "$1" "Downloading $src"

            curl "$src" -fLo "${src##*/}" || {
                rm -f "${src##*/}"
                die "$1" "Failed to download $src"
            }

        # Local source.
        elif [ -f "$repo_dir/$src" ]; then
            log "$1" "Found local file '$src'"

        else
            die "$1" "No local file '$src'"
        fi
    done < "$repo_dir/sources"
}

pkg_extract() {
    # Extract all source archives to the build directory and copy over
    # any local repository files.
    repo_dir=$(pkg_find "$1")

    # Support packages without sources. Simply do nothing.
    [ -f "$repo_dir/sources" ] || return 0

    log "$1" "Extracting sources"

    while read -r src dest || [ "$src" ]; do
        mkdir -p "$mak_dir/$1/$dest" && cd "$mak_dir/$1/$dest"

        case $src in
            # Git repository.
            git+*)
                # Split the source into URL + OBJECT (branch or commit).
                url=${src##git+} com=${url##*[@#]} com=${com#${url%[@#]*}}

                log "$1" "Cloning ${url%[@#]*}"; {
                    git init
                    git remote add origin "${url%[@#]*}"
                    case "$url" in
                        # Tags are specified via '@'
                        *@*) git fetch -t --depth=1 origin "$com" || git fetch ;;
                        *)   git fetch --depth=1 origin "$com" || git fetch
                    esac
                    git checkout "${com:-FETCH_HEAD}"
                } || die "$1" "Failed to clone $src"
            ;;

            # Mercurial repository.
            hg+*)
                # Split the source into URL + OBJECT (branch or commit).
                url=${src##hg+} com=${url##*[@#]} com=${com#${url%[@#]*}}

                # Unfortunately, there is no shallow cloning with Mercurial.
                log "$1" "Cloning ${url%[@#]*}"
                hg clone -u "${com:-tip}"

            ;;

            # Comment or blank line.
            \#*|'') continue ;;

            # Only 'tar', 'cpio', and 'zip' archives are currently supported for
            # extraction. Other filetypes are simply copied to '$mak_dir'
            # which allows for manual extraction.
            *://*.tar|*://*.tar.??|*://*.tar.???|*://*.tar.????|*://*.tgz|*://*.txz)

                decompress "$src_dir/$1/${src##*/}" > .ktar

                "$tar" xf .ktar || die "$1" "Couldn't extract ${src##*/}"

                # We now list the contents of the tarball so we can do our
                # version of 'strip-components'.
                "$tar" tf .ktar |
                    while read -r file; do printf '%s\n' "${file%%/*}"; done |

                    # Do not repeat files.
                    uniq |

                    # For every directory in the base we move each file
                    # inside it to the upper directory.
                    while read -r dir ; do

                        # Skip if we are not dealing with a directory here.
                        # This way we don't remove files on the upper directory
                        # if a tar archive doesn't need directory stripping.
                        [ -d "${dir#.}" ] || continue

                        # Change into the directory in a subshell so we don't
                        # need to cd back to the upper directory.
                        (
                            cd "$dir"

                            # We use find because we want to move hidden files
                            # as well.
                            #
                            # Skip the file if it has the same name as the directory.
                            # We will deal with it later.
                            #
                            # Word splitting is intentional here.
                            # shellcheck disable=2046
                            find . \( ! -name . -prune \) ! -name "$dir" \
                                 -exec mv -f {} .. \;

                            # If a file/directory with the same name as the directory
                            # exists, append a '.cptbak' to it and move it to the
                            # upper directory.
                            ! [ -e "$dir" ] || mv "$dir" "../${dir}.cptbak"
                        )
                        rmdir "$dir"

                        # If a backup file exists, move it into the original location.
                        ! [ -e "${dir}.cptbak" ] || mv "${dir}.cptbak" "$dir"
                done

                # Clean up the temporary tarball.
                rm -f .ktar
            ;;

            *://*.cpio|*://*.cpio.??|*://*.cpio.???|*://*.cpio.????)
                decompress "$src_dir/$1/${src##*/}" | cpio -i

            ;;

            *://*.zip)
                unzip "$src_dir/$1/${src##*/}" ||
                    die "$1" "Couldn't extract ${src##*/}"

            ;;

            *)
                # Local file.
                if [ -f "$repo_dir/$src" ]; then
                    cp -f "$repo_dir/$src" .

                # Remote file.
                elif [ -f "$src_dir/$1/${src##*/}" ]; then
                    cp -f "$src_dir/$1/${src##*/}" .

                else
                    die "$1" "Local file $src not found"
                fi
            ;;
        esac
    done < "$repo_dir/sources"
}

pkg_fetch() {
    log "Updating repositories"

    run_hook pre-fetch

    # Create a list of all repositories.
    # See [1] at top of script.
    # shellcheck disable=2046,2086
    { IFS=:; set -- $CPT_PATH; IFS=$old_ifs ;}

    # Update each repository in '$CPT_PATH'. It is assumed that
    # each repository is 'git' tracked.
    for repo; do
        # Go to the root of the repository (if it exists).
        cd "$repo"
        cd "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null ||
            cd "$(hg root 2>/dev/null)" 2>/dev/null ||:

        if [ -d .git ]; then

            [ "$(git remote 2>/dev/null)" ] || {
                log "$repo" " "
                printf '%s\n' "No remote, skipping."
                continue
            }

            contains "$repos" "$PWD" || {
                repos="$repos $PWD "

                # Display a tick if signing is enabled for this
                # repository.
                case $(git config merge.verifySignatures) in
                    true) log "$PWD" "[signed] " ;;
                    *)    log "$PWD" " " ;;
                esac

                if [ -w "$PWD" ] && [ "$uid" != 0 ]; then
                    git fetch
                    git merge
                    git submodule update --remote --init -f

                else
                    [ "$uid" = 0 ] || log "$PWD" "Need root to update"

                    # Find out the owner of the repository and spawn
                    # git as this user below.
                    #
                    # This prevents 'git' from changing the original
                    # ownership of files and directories in the rare
                    # case that the repository is owned by a 3rd user.
                    (
                        user=$(_stat "$PWD")

                        [ "$user" = root ] ||
                            log "Dropping permissions to $user for pull"

                        git_cmd="git fetch && git merge && git submodule update --remote --init -f"
                        case $su in *su) git_cmd="'$git_cmd'"; esac

                        # Spawn a subshell to run multiple commands as
                        # root at once. This makes things easier on users
                        # who aren't using persist/timestamps for auth
                        # caching.
                        user=$user as_root sh -c "$git_cmd"
                    )
                fi
            }
        elif [ -d .hg ]; then

            [ "$(hg showconfig paths 2>/dev/null)" ] || {
                log "$repo" " "
                printf '%s\n' "No remote, skipping."
                continue
            }

            contains "$repos $PWD" || {
                repos="$repos $PWD"

                if [ -w "$PWD" ] && [ "$uid" != 0 ]; then
                    hg pull
                    hg update
                else
                    [ "$uid" ] || log "$PWD" "Need root to update"

                    # We are going to do the same operation as above, to
                    # find the owner of the repository.
                    (
                        user=$(_stat "$PWD")

                        [ "$user" = root ] ||
                            log "Dropping permissions to $user for pull"

                        hg_cmd="hg pull && hg update"

                        case $su in *su) hg_cmd="'$hg_cmd'"; esac
                        user=$user as_root sh -c "$hg_cmd"
                    )
                fi
            }
        elif [ -f .rsync ]; then
            # If an .rsync_root file exists, we check that the repository root
            # exists. If it does, we change to that directory to do the fetch.
            # This way, we allow for partial repositories while making sure that
            # we can fetch the repository in a single operation.
            [ -f .rsync_root ] && {
                read -r rsync_root < .rsync_root
                [ -f "$rsync_root/.rsync" ] && cd "$rsync_root"
            }
            contains "$repos" "$PWD" || {
                repos="$repos $PWD"
                read -r remote < .rsync
                if [ -w "$PWD" ] && [ "$uid" != 0 ]; then
                    rsync -acvzzC --include=core --delete "$remote/" "$PWD"
                else
                    [ "$uid" = 0 ] || log "$PWD" "Need root to update"

                    # Similar to the git update, we find the owner of
                    # the repository and spawn rsync as that user.
                    (
                        user=$(_stat "$PWD")

                        [ "$user" = root ] ||
                            log "Dropping permissions to $user for pull"

                        user=$user as_root rsync -acvzzC --include=core --delete "$remote/" "$PWD"
                    )
                fi
            }
        else
            log "$repo" " "
            printf '%s\n' "Not a remote repository, skipping."
        fi
    done

    run_hook post-fetch
}

# Local Variables:
# mode: sh
# End: