1 /** 2 Package skeleton initialization code. 3 4 Copyright: © 2013-2016 rejectedsoftware e.K. 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 Authors: Sönke Ludwig 7 */ 8 module dub.init; 9 10 import dub.internal.vibecompat.core.file; 11 import dub.internal.logging; 12 import dub.package_ : PackageFormat, packageInfoFiles, defaultPackageFilename; 13 import dub.recipe.packagerecipe; 14 import dub.dependency; 15 16 import std.exception; 17 import std.file; 18 import std.format; 19 import std.process; 20 import std.string; 21 22 23 /** Initializes a new package in the given directory. 24 25 The given `root_path` will be checked for any of the files that will be 26 created by this function. If any exist, an exception will be thrown before 27 altering the directory. 28 29 Params: 30 root_path = Directory in which to create the new package. If the 31 directory doesn't exist, a new one will be created. 32 deps = A set of extra dependencies to add to the package recipe. The 33 associative array is expected to map from package name to package 34 version. 35 type = The type of package skeleton to create. Can currently be 36 "minimal", "vibe.d" or "deimos" 37 recipe_callback = Optional callback that can be used to customize the 38 package recipe and the file format used to store it prior to 39 writing it to disk. 40 */ 41 void initPackage(NativePath root_path, VersionRange[string] deps, string type, 42 PackageFormat format, scope RecipeCallback recipe_callback = null) 43 { 44 import std.conv : to; 45 import dub.recipe.io : writePackageRecipe; 46 47 void enforceDoesNotExist(string filename) { 48 enforce(!existsFile(root_path ~ filename), 49 "The target directory already contains a '%s' %s. Aborting." 50 .format(filename, filename.isDir ? "directory" : "file")); 51 } 52 53 string username = getUserName(); 54 55 PackageRecipe p; 56 p.name = root_path.head.name.toLower(); 57 p.authors ~= username; 58 p.license = "proprietary"; 59 foreach (pack, v; deps) { 60 p.buildSettings.dependencies[pack] = Dependency(v); 61 } 62 63 //Check to see if a target directory needs to be created 64 if (!root_path.empty) { 65 ensureDirectory(root_path); 66 } 67 68 //Make sure we do not overwrite anything accidentally 69 foreach (fil; packageInfoFiles) 70 enforceDoesNotExist(fil.filename); 71 72 auto files = ["source/", "views/", "public/", "dub.json"]; 73 foreach (fil; files) 74 enforceDoesNotExist(fil); 75 76 void processRecipe() 77 { 78 if (recipe_callback) 79 recipe_callback(p, format); 80 } 81 82 switch (type) { 83 default: break; 84 case "minimal": initMinimalPackage(root_path, p, &processRecipe); break; 85 case "vibe.d": initVibeDPackage(root_path, p, &processRecipe); break; 86 case "deimos": initDeimosPackage(root_path, p, &processRecipe); break; 87 } 88 89 writePackageRecipe(root_path ~ ("dub."~format.to!string), p); 90 writeGitignore(root_path, p.name); 91 } 92 93 alias RecipeCallback = void delegate(ref PackageRecipe, ref PackageFormat); 94 95 private void initMinimalPackage(NativePath root_path, ref PackageRecipe p, scope void delegate() pre_write_callback) 96 { 97 p.description = "A minimal D application."; 98 pre_write_callback(); 99 100 ensureDirectory(root_path ~ "source"); 101 write((root_path ~ "source/app.d").toNativeString(), 102 q{import std.stdio; 103 104 void main() 105 { 106 writeln("Edit source/app.d to start your project."); 107 } 108 }); 109 } 110 111 private void initVibeDPackage(NativePath root_path, ref PackageRecipe p, scope void delegate() pre_write_callback) 112 { 113 if ("vibe-d" !in p.buildSettings.dependencies) 114 p.buildSettings.dependencies["vibe-d"] = Dependency("~>0.9"); 115 p.description = "A simple vibe.d server application."; 116 pre_write_callback(); 117 118 ensureDirectory(root_path ~ "source"); 119 ensureDirectory(root_path ~ "views"); 120 ensureDirectory(root_path ~ "public"); 121 write((root_path ~ "source/app.d").toNativeString(), 122 q{import vibe.vibe; 123 124 void main() 125 { 126 auto settings = new HTTPServerSettings; 127 settings.port = 8080; 128 settings.bindAddresses = ["::1", "127.0.0.1"]; 129 auto listener = listenHTTP(settings, &hello); 130 scope (exit) 131 { 132 listener.stopListening(); 133 } 134 135 logInfo("Please open http://127.0.0.1:8080/ in your browser."); 136 runApplication(); 137 } 138 139 void hello(HTTPServerRequest req, HTTPServerResponse res) 140 { 141 res.writeBody("Hello, World!"); 142 } 143 }); 144 } 145 146 private void initDeimosPackage(NativePath root_path, ref PackageRecipe p, scope void delegate() pre_write_callback) 147 { 148 import dub.compilers.buildsettings : TargetType; 149 150 p.description = format("Deimos Bindings for "~p.name~"."); 151 p.buildSettings.importPaths[""] ~= "."; 152 p.buildSettings.targetType = TargetType.sourceLibrary; 153 pre_write_callback(); 154 155 ensureDirectory(root_path ~ "C"); 156 ensureDirectory(root_path ~ "deimos"); 157 } 158 159 /** 160 * Write the `.gitignore` file to the directory, if it does not already exists 161 * 162 * As `dub` is often used with `git`, adding a `.gitignore` is a nice touch for 163 * most users. However, this file is not mandatory for `dub` to do its job, 164 * so we do not depend on the content. 165 * One important use case we need to support is people running `dub init` on 166 * a Github-initialized repository. Those might already contain a `.gitignore` 167 * (and a README and a LICENSE), thus we should not bail out if the file already 168 * exists, just ignore it. 169 * 170 * Params: 171 * root_path = The path to the directory hosting the project 172 * pkg_name = Name of the package, to generate a list of binaries to ignore 173 */ 174 private void writeGitignore(NativePath root_path, const(char)[] pkg_name) 175 { 176 auto full_path = (root_path ~ ".gitignore").toNativeString(); 177 178 if (existsFile(full_path)) 179 return; 180 181 write(full_path, 182 q"{.dub 183 docs.json 184 __dummy.html 185 docs/ 186 /%1$s 187 %1$s.so 188 %1$s.dylib 189 %1$s.dll 190 %1$s.a 191 %1$s.lib 192 %1$s-test-* 193 *.exe 194 *.pdb 195 *.o 196 *.obj 197 *.lst 198 }".format(pkg_name)); 199 } 200 201 private string getUserName() 202 { 203 version (Windows) 204 return environment.get("USERNAME", "Peter Parker"); 205 else version (Posix) 206 { 207 import core.sys.posix.pwd, core.sys.posix.unistd, core.stdc.string : strlen; 208 import std.algorithm : splitter; 209 210 // Bionic doesn't have pw_gecos on ARM 211 version(CRuntime_Bionic) {} else 212 if (auto pw = getpwuid(getuid)) 213 { 214 auto uinfo = pw.pw_gecos[0 .. strlen(pw.pw_gecos)].splitter(','); 215 if (!uinfo.empty && uinfo.front.length) 216 return uinfo.front.idup; 217 } 218 return environment.get("USER", "Peter Parker"); 219 } 220 else 221 static assert(0); 222 }