github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/snap/pack/pack.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2017 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package pack 21 22 import ( 23 "fmt" 24 "io" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 29 "github.com/snapcore/snapd/gadget" 30 "github.com/snapcore/snapd/kernel" 31 "github.com/snapcore/snapd/logger" 32 "github.com/snapcore/snapd/snap" 33 "github.com/snapcore/snapd/snap/snapdir" 34 "github.com/snapcore/snapd/snap/squashfs" 35 ) 36 37 // this could be shipped as a file like "info", and save on the memory and the 38 // overhead of creating and removing the tempfile, but on darwin we can't AFAIK 39 // easily know where it's been placed. As it's not really that big, just 40 // shipping it in memory seems fine for now. 41 // from click's click.build.ClickBuilderBase, and there from 42 // @Dpkg::Source::Package::tar_ignore_default_pattern; 43 // changed to match squashfs's "-wildcards" syntax 44 // 45 // for anchored vs non-anchored syntax see RELEASE_README in squashfs-tools. 46 const excludesContent = ` 47 # "anchored", only match at top level 48 DEBIAN 49 .arch-ids 50 .arch-inventory 51 .bzr 52 .bzr-builddeb 53 .bzr.backup 54 .bzr.tags 55 .bzrignore 56 .cvsignore 57 .git 58 .gitattributes 59 .gitignore 60 .gitmodules 61 .hg 62 .hgignore 63 .hgsigs 64 .hgtags 65 .shelf 66 .svn 67 CVS 68 DEADJOE 69 RCS 70 _MTN 71 _darcs 72 {arch} 73 .snapignore 74 75 # "non-anchored", match anywhere 76 ... .[#~]* 77 ... *.snap 78 ... *.click 79 ... .*.sw? 80 ... *~ 81 ... ,,* 82 ` 83 84 // small helper that returns the architecture of the snap, or "multi" if it's multiple arches 85 func debArchitecture(info *snap.Info) string { 86 switch len(info.Architectures) { 87 case 0: 88 return "all" 89 case 1: 90 return info.Architectures[0] 91 default: 92 return "multi" 93 } 94 } 95 96 // CheckSkeleton attempts to validate snap data in source directory 97 func CheckSkeleton(w io.Writer, sourceDir string) error { 98 info, err := loadAndValidate(sourceDir) 99 if err == nil { 100 snap.SanitizePlugsSlots(info) 101 if len(info.BadInterfaces) > 0 { 102 fmt.Fprintln(w, snap.BadInterfacesSummary(info)) 103 } 104 } 105 return err 106 } 107 108 func loadAndValidate(sourceDir string) (*snap.Info, error) { 109 // ensure we have valid content 110 yaml, err := ioutil.ReadFile(filepath.Join(sourceDir, "meta", "snap.yaml")) 111 if err != nil { 112 return nil, err 113 } 114 115 info, err := snap.InfoFromSnapYaml(yaml) 116 if err != nil { 117 return nil, err 118 } 119 120 if err := snap.Validate(info); err != nil { 121 return nil, fmt.Errorf("cannot validate snap %q: %v", info.InstanceName(), err) 122 } 123 124 if err := snap.ValidateContainer(snapdir.New(sourceDir), info, logger.Noticef); err != nil { 125 return nil, err 126 } 127 128 if info.SnapType == snap.TypeGadget { 129 if err := gadget.Validate(sourceDir, nil); err != nil { 130 return nil, err 131 } 132 } 133 if info.SnapType == snap.TypeKernel { 134 if err := kernel.Validate(sourceDir); err != nil { 135 return nil, err 136 } 137 } 138 139 return info, nil 140 } 141 142 func snapPath(info *snap.Info, targetDir, snapName string) string { 143 if snapName == "" { 144 snapName = fmt.Sprintf("%s_%s_%v.snap", info.InstanceName(), info.Version, debArchitecture(info)) 145 } 146 if targetDir != "" && !filepath.IsAbs(snapName) { 147 snapName = filepath.Join(targetDir, snapName) 148 } 149 return snapName 150 } 151 152 func prepare(sourceDir, targetDir string) (*snap.Info, error) { 153 info, err := loadAndValidate(sourceDir) 154 if err != nil { 155 return nil, err 156 } 157 158 if targetDir != "" { 159 if err := os.MkdirAll(targetDir, 0755); err != nil { 160 return nil, err 161 } 162 } 163 164 return info, nil 165 } 166 167 func excludesFile() (filename string, err error) { 168 tmpf, err := ioutil.TempFile("", ".snap-pack-exclude-") 169 if err != nil { 170 return "", err 171 } 172 173 // inspited by ioutil.WriteFile 174 n, err := tmpf.Write([]byte(excludesContent)) 175 if err == nil && n < len(excludesContent) { 176 err = io.ErrShortWrite 177 } 178 179 if err1 := tmpf.Close(); err == nil { 180 err = err1 181 } 182 183 if err == nil { 184 filename = tmpf.Name() 185 } 186 187 return filename, err 188 } 189 190 type Options struct { 191 // TargetDir is the direction where the snap file will be placed, or empty 192 // to use the current directory 193 TargetDir string 194 // SnapName is the name of the snap file, or empty to use the default name 195 // which is <snapname>_<version>_<architecture>.snap 196 SnapName string 197 // Compression method to use 198 Compression string 199 } 200 201 var Defaults *Options = nil 202 203 // Snap the given sourceDirectory and return the generated 204 // snap file 205 func Snap(sourceDir string, opts *Options) (string, error) { 206 if opts == nil { 207 opts = &Options{} 208 } 209 switch opts.Compression { 210 case "xz", "lzo", "": 211 // fine 212 default: 213 return "", fmt.Errorf("cannot use compression %q", opts.Compression) 214 } 215 216 info, err := prepare(sourceDir, opts.TargetDir) 217 if err != nil { 218 return "", err 219 } 220 221 excludes, err := excludesFile() 222 if err != nil { 223 return "", err 224 } 225 defer os.Remove(excludes) 226 227 snapName := snapPath(info, opts.TargetDir, opts.SnapName) 228 d := squashfs.New(snapName) 229 if err = d.Build(sourceDir, &squashfs.BuildOpts{ 230 SnapType: string(info.Type()), 231 Compression: opts.Compression, 232 ExcludeFiles: []string{excludes}, 233 }); err != nil { 234 return "", err 235 } 236 237 return snapName, nil 238 }