1 #!/usr/bin/env rdmd
2 /*******************************************************************************
3 
4     Standalone build script for DUB
5 
6     This script can be called from anywhere, as it deduces absolute paths
7     based on the script's placement in the repository.
8 
9     Invoking it while making use of all the options would like like this:
10     DMD=ldmd2 DFLAGS="-O -inline" ./build.d my-dub-version
11     Using an environment variable for the version is also supported:
12     DMD=dmd DFLAGS="-w -g" GITVER="1.2.3" ./build.d
13 
14     Copyright: D Language Foundation
15     Authors: Mathias 'Geod24' Lang
16     License: MIT
17 
18 *******************************************************************************/
19 module build;
20 
21 private:
22 
23 import std.algorithm;
24 static import std.file;
25 import std.format;
26 import std.path;
27 import std.process;
28 import std.stdio;
29 import std.string;
30 
31 /// Root of the `git` repository
32 immutable RootPath = __FILE_FULL_PATH__.dirName;
33 /// Path to the version file
34 immutable VersionFilePath = RootPath.buildPath("source", "dub", "version_.d");
35 /// Path to the file containing the files to be built
36 immutable SourceListPath = RootPath.buildPath("build-files.txt");
37 /// Path at which the newly built `dub` binary will be
38 version (Windows) {
39 	immutable DubBinPath = RootPath.buildPath("bin", "dub.exe");
40 } else {
41 	immutable DubBinPath = RootPath.buildPath("bin", "dub");
42 }
43 
44 // Flags for DMD
45 immutable OutputFlag = "-of" ~ DubBinPath;
46 immutable IncludeFlag = "-I" ~ RootPath.buildPath("source");
47 immutable DefaultDFLAGS = [ "-g", "-O", "-w" ];
48 
49 
50 /// Entry point
51 int main(string[] args)
52 {
53     // This does not have a 'proper' CLI interface, as it's only used in
54     // special cases (e.g. package maintainers can use it for bootstrapping),
55     // not for general / everyday usage by newcomer.
56     // So the following is just an heuristic / best effort approach.
57     if (args.canFind("--help", "/?", "-h"))
58     {
59         writeln("USAGE: ./build.d [compiler args (default:", DefaultDFLAGS, "]");
60         writeln();
61         writeln("  In order to build DUB, a version module must first be generated.");
62         writeln("  If the GITVER environment variable is present, it will be used to generate the version module.");
63         writeln("  Otherwise this script will look for a pre-existing version module.");
64         writeln("  If no GITVER is provided and no version module exists, `git describe` will be called");
65         writeln("  Build flags can be provided as arguments.");
66         writeln("  LDC or GDC can be used by setting the `DMD` value to " ~
67                 "`ldmd2` and `gdmd` (or their path), respectively.");
68         return 1;
69     }
70 
71     immutable dubVersion = environment.get("GITVER", "");
72     if (!writeVersionFile(dubVersion))
73         return 1;
74 
75     immutable dmd = getCompiler();
76     if (!dmd.length) return 1;
77     const dflags = args.length > 1 ? args[1 .. $] : DefaultDFLAGS;
78 
79     // Compiler says no to immutable (because it can't handle the appending)
80     const command = [
81         dmd,
82         OutputFlag, IncludeFlag,
83         "-version=DubUseCurl", "-version=DubApplication",
84         ] ~ dflags ~ [ "@build-files.txt" ];
85 
86     writeln("Building dub using ", dmd, " (dflags: ", dflags, "), this may take a while...");
87     auto proc = execute(command);
88     if (proc.status != 0)
89     {
90         writeln("Command `", command, "` failed, output was:");
91         writeln(proc.output);
92         return 1;
93     }
94 
95     writeln("DUB has been built as: ", DubBinPath);
96     version (Posix)
97         writeln("You may want to run `sudo ln -s ", DubBinPath, " /usr/local/bin` now");
98     else version (Windows)
99         writeln("You may want to add the following entry to your PATH " ~
100                 "environment variable: ", DubBinPath);
101     return 0;
102 }
103 
104 /**
105    Generate the version file describing DUB's version / commit
106 
107    Params:
108      dubVersion = User provided version file. Can be `null` / empty,
109                   in which case the existing file (if any) takes precedence,
110                   or the version is infered with `git describe`.
111                   A non-empty parameter will always override the existing file.
112  */
113 bool writeVersionFile(string dubVersion)
114 {
115     if (!dubVersion.length)
116     {
117         if (std.file.exists(VersionFilePath))
118         {
119             writeln("Using pre-existing version file. To force a rebuild, " ~
120                     "provide an explicit version (first argument) or remove: ",
121                     VersionFilePath);
122             return true;
123         }
124 
125         auto pid = execute(["git", "describe"]);
126         if (pid.status != 0)
127         {
128             writeln("Could not determine version with `git describe`. " ~
129                     "Make sure 'git' is installed and this is a git repository. " ~
130                     "Alternatively, you can provide a version explicitly via the " ~
131                     "`GITVER environment variable or pass it as the first " ~
132                     "argument to this script");
133             return false;
134         }
135         dubVersion = pid.output.strip();
136     }
137 
138     try
139     {
140         std.file.write(VersionFilePath, q{
141 /**
142    DUB version file
143 
144    This file is auto-generated by 'build.d'. DO NOT EDIT MANUALLY!
145  */
146 module dub.version_;
147 
148 enum dubVersion = "%s";
149 }.format(dubVersion));
150         writeln("Wrote version_.d` file with version: ", dubVersion);
151         return true;
152     }
153     catch (Exception e)
154     {
155         writeln("Writing version file to '", VersionFilePath, "' failed: ", e.msg);
156         return false;
157     }
158 }
159 
160 /**
161    Detect which compiler is available
162 
163    Default to DMD, then LDC (ldmd2), then GDC (gdmd).
164    If none is in the PATH, an error will be thrown.
165 
166    Note:
167      It would be optimal if we could get the path of the compiler
168      invoking this script, but AFAIK this isn't possible.
169  */
170 string getCompiler ()
171 {
172     auto env = environment.get("DMD", "");
173     // If the user asked for a compiler explicitly, respect it
174     if (env.length)
175         return env;
176 
177     static immutable Compilers = [ "dmd", "ldmd2", "gdmd" ];
178     foreach (bin; Compilers)
179     {
180         try
181         {
182             auto pid = execute([bin, "--version"]);
183             if (pid.status == 0)
184                 return bin;
185         }
186         catch (Exception e)
187             continue;
188     }
189     writeln("No compiler has been found in the PATH. Attempted values: ", Compilers);
190     writeln("Make sure one of those is in the PATH, or set the `DMD` variable");
191     return null;
192 }