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.vibecompat.core.log; 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, string[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 import std.ascii : isDigit; 61 p.buildSettings.dependencies[pack] = Dependency(v); 62 } 63 64 //Check to see if a target directory needs to be created 65 if (!root_path.empty) { 66 if (!existsFile(root_path)) 67 createDirectory(root_path); 68 } 69 70 //Make sure we do not overwrite anything accidentally 71 foreach (fil; packageInfoFiles) 72 enforceDoesNotExist(fil.filename); 73 74 auto files = ["source/", "views/", "public/", "dub.json"]; 75 foreach (fil; files) 76 enforceDoesNotExist(fil); 77 78 void processRecipe() 79 { 80 if (recipe_callback) 81 recipe_callback(p, format); 82 } 83 84 switch (type) { 85 default: break; 86 case "minimal": initMinimalPackage(root_path, p, &processRecipe); break; 87 case "vibe.d": initVibeDPackage(root_path, p, &processRecipe); break; 88 case "deimos": initDeimosPackage(root_path, p, &processRecipe); break; 89 } 90 91 writePackageRecipe(root_path ~ ("dub."~format.to!string), p); 92 writeGitignore(root_path, p.name); 93 } 94 95 alias RecipeCallback = void delegate(ref PackageRecipe, ref PackageFormat); 96 97 private void initMinimalPackage(NativePath root_path, ref PackageRecipe p, scope void delegate() pre_write_callback) 98 { 99 p.description = "A minimal D application."; 100 pre_write_callback(); 101 102 createDirectory(root_path ~ "source"); 103 write((root_path ~ "source/app.d").toNativeString(), 104 q{import std.stdio; 105 106 void main() 107 { 108 writeln("Edit source/app.d to start your project."); 109 } 110 }); 111 } 112 113 private void initVibeDPackage(NativePath root_path, ref PackageRecipe p, scope void delegate() pre_write_callback) 114 { 115 if ("vibe-d" !in p.buildSettings.dependencies) 116 p.buildSettings.dependencies["vibe-d"] = Dependency("~>0.8.2"); 117 p.description = "A simple vibe.d server application."; 118 pre_write_callback(); 119 120 createDirectory(root_path ~ "source"); 121 createDirectory(root_path ~ "views"); 122 createDirectory(root_path ~ "public"); 123 write((root_path ~ "source/app.d").toNativeString(), 124 q{import vibe.vibe; 125 126 void main() 127 { 128 auto settings = new HTTPServerSettings; 129 settings.port = 8080; 130 settings.bindAddresses = ["::1", "127.0.0.1"]; 131 listenHTTP(settings, &hello); 132 133 logInfo("Please open http://127.0.0.1:8080/ in your browser."); 134 runApplication(); 135 } 136 137 void hello(HTTPServerRequest req, HTTPServerResponse res) 138 { 139 res.writeBody("Hello, World!"); 140 } 141 }); 142 } 143 144 private void initDeimosPackage(NativePath root_path, ref PackageRecipe p, scope void delegate() pre_write_callback) 145 { 146 import dub.compilers.buildsettings : TargetType; 147 148 p.description = format("Deimos Bindings for "~p.name~"."); 149 p.buildSettings.importPaths[""] ~= "."; 150 p.buildSettings.targetType = TargetType.sourceLibrary; 151 pre_write_callback(); 152 153 createDirectory(root_path ~ "C"); 154 createDirectory(root_path ~ "deimos"); 155 } 156 157 /** 158 * Write the `.gitignore` file to the directory, if it does not already exists 159 * 160 * As `dub` is often used with `git`, adding a `.gitignore` is a nice touch for 161 * most users. However, this file is not mandatory for `dub` to do its job, 162 * so we do not depend on the content. 163 * One important use case we need to support is people running `dub init` on 164 * a Github-initialized repository. Those might already contain a `.gitignore` 165 * (and a README and a LICENSE), thus we should not bail out if the file already 166 * exists, just ignore it. 167 * 168 * Params: 169 * root_path = The path to the directory hosting the project 170 * pkg_name = Name of the package, to generate a list of binaries to ignore 171 */ 172 private void writeGitignore(NativePath root_path, const(char)[] pkg_name) 173 { 174 auto full_path = (root_path ~ ".gitignore").toNativeString(); 175 176 if (existsFile(full_path)) 177 return; 178 179 write(full_path, 180 q"{.dub 181 docs.json 182 __dummy.html 183 docs/ 184 /%1$s 185 %1$s.so 186 %1$s.dylib 187 %1$s.dll 188 %1$s.a 189 %1$s.lib 190 %1$s-test-* 191 *.exe 192 *.o 193 *.obj 194 *.lst 195 }".format(pkg_name)); 196 } 197 198 private string getUserName() 199 { 200 version (Windows) 201 return environment.get("USERNAME", "Peter Parker"); 202 else version (Posix) 203 { 204 import core.sys.posix.pwd, core.sys.posix.unistd, core.stdc.string : strlen; 205 import std.algorithm : splitter; 206 207 // Bionic doesn't have pw_gecos on ARM 208 version(CRuntime_Bionic) {} else 209 if (auto pw = getpwuid(getuid)) 210 { 211 auto uinfo = pw.pw_gecos[0 .. strlen(pw.pw_gecos)].splitter(','); 212 if (!uinfo.empty && uinfo.front.length) 213 return uinfo.front.idup; 214 } 215 return environment.get("USER", "Peter Parker"); 216 } 217 else 218 static assert(0); 219 }