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 }