1 /*
2 Copyright 2020 René Ferdinand Rivera Morell
3 Distributed under the Boost Software License, Version 1.0.
4 (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
5 */
6 
7 #include "startup.h"
8 #include "rules.h"
9 #include "frames.h"
10 #include "object.h"
11 #include "pathsys.h"
12 #include "cwd.h"
13 #include "filesys.h"
14 #include "output.h"
15 #include "variable.h"
16 
17 #include <cstdlib>
18 #include <string>
19 #include <algorithm>
20 
21 namespace
22 {
bind_builtin(char const * name_,LIST * (* f)(FRAME *,int flags),int flags,char const ** args)23     void bind_builtin(
24         char const *name_, LIST *(*f)(FRAME *, int flags),
25         int flags, char const **args)
26     {
27         FUNCTION *func;
28         OBJECT *name = object_new(name_);
29         func = function_builtin(f, flags, args);
30         new_rule_body(root_module(), name, func, 1);
31         function_free(func);
32         object_free(name);
33     }
34 } // namespace
35 
load_builtins()36 void b2::startup::load_builtins()
37 {
38     {
39         char const *args[] = {"dir", "?", 0};
40         bind_builtin("boost-build", builtin_boost_build, 0, args);
41     }
42 }
43 
builtin_boost_build(FRAME * frame,int flags)44 LIST *b2::startup::builtin_boost_build(FRAME *frame, int flags)
45 {
46     b2::jam::list dir_arg{lol_get(frame->args, 0)};
47     std::string dir;
48     if (!dir_arg.empty()) dir = b2::jam::object(*dir_arg.begin());
49 
50     b2::jam::variable dot_bootstrap_file{".bootstrap-file"};
51     if (dot_bootstrap_file)
52     {
53         err_printf(
54             "Error: Illegal attempt to re-bootstrap the build system by invoking\n"
55             "\n"
56             "   'boost-build '%s' ;\n"
57             "\n"
58             "Please consult the documentation at "
59             "'https://boostorg.github.io/build/'.\n\n",
60            dir.c_str());
61         return L0;
62     }
63 
64     // # Add the given directory to the path so we can find the build system. If
65     // # dir is empty, has no effect.
66     b2::jam::variable dot_boost_build_file{".boost-build-file"};
67     b2::jam::list dot_boost_build_file_val{static_cast<b2::jam::list>(dot_boost_build_file)};
68     std::string boost_build_jam = b2::jam::object{*dot_boost_build_file_val.begin()};
69     std::string boost_build_dir;
70     if (b2::paths::is_rooted(dir))
71         boost_build_dir = dir;
72     else
73         boost_build_dir = b2::paths::normalize(
74             std::string{boost_build_jam}+"/../"+dir);
75     b2::jam::list search_path{b2::jam::object{boost_build_dir}};
76     b2::jam::variable BOOST_BUILD_PATH{"BOOST_BUILD_PATH"};
77     search_path.append(BOOST_BUILD_PATH);
78 
79     // We set the global, and env, BOOST_BUILD_PATH so that the loading of the
80     // build system finds the initial set of modules needed for starting it up.
81     BOOST_BUILD_PATH = search_path;
82 
83     // The code that loads the rest of B2, in particular the site-config.jam
84     // and user-config.jam configuration files uses os.environ, so we need to
85     // update the value there.
86     b2::jam::variable dot_ENVIRON__BOOST_BUILD_PATH{".ENVIRON", "BOOST_BUILD_PATH"};
87     dot_ENVIRON__BOOST_BUILD_PATH = search_path;
88 
89     // # Try to find the build system bootstrap file 'bootstrap.jam'.
90     std::string bootstrap_file;
91     for (auto path: search_path)
92     {
93         std::string file = b2::jam::object{path};
94         file = b2::paths::normalize(file+"/bootstrap.jam");
95         if (b2::filesys::is_file(file))
96         {
97             bootstrap_file = file;
98             break;
99         }
100     }
101 
102     // # There is no bootstrap.jam we can find, exit with an error.
103     if (bootstrap_file.empty())
104     {
105         err_printf(
106             "Unable to load B2: could not find build system.\n"
107             "-----------------------------------------------\n"
108             "%s attempted to load the build system by invoking\n"
109             "\n"
110             "   'boost-build %s ;'\n"
111             "\n"
112             "but we were unable to find 'bootstrap.jam' in the specified directory "
113             "or in BOOST_BUILD_PATH:\n",
114             boost_build_jam.c_str(), dir.c_str());
115         for (auto path: search_path)
116         {
117             std::string file = b2::jam::object{path};
118             err_printf("    %s\n", file.c_str());
119         }
120         err_puts(
121             "Please consult the documentation at "
122             "'https://boostorg.github.io/build/'.\n\n");
123         return L0;
124     }
125 
126     // Set the bootstrap=file var as it's used by the build system to refer to
127     // the rest of the build system files.
128     dot_bootstrap_file = b2::jam::list{b2::jam::object{bootstrap_file}};
129 
130     // Show where we found it, if asked.
131     b2::jam::variable dot_OPTION__debug_configuration{".OPTION", "debug-configration"};
132     if (dot_OPTION__debug_configuration)
133     {
134         out_printf("notice: loading B2 from %s\n", bootstrap_file.c_str());
135     }
136 
137     // # Load the build system, now that we know where to start from.
138     parse_file(b2::jam::object{bootstrap_file}, frame);
139 
140     return L0;
141 }
142 
143 extern char const *saved_argv0;
144 
bootstrap(FRAME * frame)145 bool b2::startup::bootstrap(FRAME *frame)
146 {
147     b2::jam::list ARGV = b2::jam::variable{"ARGV"};
148     b2::jam::object opt_debug_configuration{"--debug-configuration"};
149     b2::jam::variable dot_OPTION__debug_configuration{".OPTION", "debug-configration"};
150     for (auto arg: ARGV)
151     {
152         if (opt_debug_configuration == arg)
153         {
154             dot_OPTION__debug_configuration = b2::jam::list{b2::jam::object{"true"}};
155             break;
156         }
157     }
158 
159     char *b2_exe_path_pchar = executable_path(saved_argv0);
160     const std::string b2_exe_path{b2_exe_path_pchar};
161     if (b2_exe_path_pchar)
162     {
163         std::free(b2_exe_path_pchar);
164     }
165     const std::string boost_build_jam{"boost-build.jam"};
166     std::string b2_file_path;
167 
168     // Attempt to find the `boost-build.jam` boot file in work directory tree.
169     if (b2_file_path.empty())
170     {
171         std::string work_dir{b2::paths::normalize(b2::cwd_str()) + "/"};
172         while (b2_file_path.empty() && !work_dir.empty())
173         {
174             if (b2::filesys::is_file(work_dir + boost_build_jam))
175                 b2_file_path = work_dir + boost_build_jam;
176             else if (work_dir.length() == 1 && work_dir[0] == '/')
177                 work_dir.clear();
178             else
179             {
180                 auto parent_pos = work_dir.rfind('/', work_dir.length() - 2);
181                 if (parent_pos != std::string::npos)
182                     work_dir.erase(parent_pos + 1);
183                 else
184                     work_dir.clear();
185             }
186         }
187     }
188 
189     // Check relative to the executable for portable install location.
190     if (b2_file_path.empty())
191     {
192         const std::string path{
193             b2::paths::normalize(
194                 b2_exe_path + "/../.b2/kernel/" + boost_build_jam)};
195         if (b2::filesys::is_file(path))
196             b2_file_path = path;
197     }
198 
199     // Check relative to the executable for portable install location.
200     if (b2_file_path.empty())
201     {
202         const std::string path{
203             b2::paths::normalize(
204                 b2_exe_path + "/../../share/boost-build/src/kernel/" + boost_build_jam)};
205         if (b2::filesys::is_file(path))
206             b2_file_path = path;
207     }
208 
209     // Check the BOOST_BUILD_PATH (and BOOST_ROOT) paths.
210     if (b2_file_path.empty())
211     {
212         b2::jam::list BOOST_BUILD_PATH = b2::jam::variable{"BOOST_BUILD_PATH"};
213         // For back-compat with Boost we also search in the BOOST_ROOT location.
214         BOOST_BUILD_PATH.append(b2::jam::list(b2::jam::variable{"BOOST_ROOT"}));
215         for (auto search_path: BOOST_BUILD_PATH)
216         {
217             std::string path = b2::jam::object{search_path};
218             path = b2::paths::normalize(path+"/"+boost_build_jam);
219             if (b2::filesys::is_file(path))
220             {
221                 b2_file_path = path;
222                 break;
223             }
224         }
225     }
226 
227     // Indicate a load failure when we can't find the build file.
228     if (b2_file_path.empty())
229     {
230         const char * not_found_error =
231             "Unable to load B2: could not find 'boost-build.jam'\n"
232             "---------------------------------------------------\n"
233             "Attempted search from '%s' up to the root "
234             "at '%s'\n"
235             "Please consult the documentation at "
236             "'https://boostorg.github.io/build/'.\n\n";
237         err_printf(not_found_error, b2::cwd_str().c_str(), b2_exe_path.c_str());
238         return false;
239     }
240 
241     // Show where we found it, if asked.
242     if (dot_OPTION__debug_configuration)
243     {
244         out_printf("notice: found boost-build.jam at %s\n", b2_file_path.c_str());
245     }
246 
247     // Load the build system bootstrap file we found. But check we did that.
248     b2::jam::variable dot_boost_build_file{".boost-build-file"};
249     dot_boost_build_file = b2_file_path;
250     b2::jam::object b2_file_path_sym{b2_file_path};
251     parse_file(b2_file_path_sym, frame);
252     b2::jam::list dot_dot_bootstrap_file_val = b2::jam::variable{".bootstrap-file"};
253     if (dot_dot_bootstrap_file_val.empty())
254     {
255         err_printf(
256             "Unable to load B2\n"
257             "-----------------\n"
258             "'%s' was found by searching from %s up to the root.\n"
259             "\n"
260             "However, it failed to call the 'boost-build' rule to indicate "
261             "the location of the build system.\n"
262             "\n"
263             "Please consult the documentation at "
264             "'https://boostorg.github.io/build/'.\n\n",
265             b2_file_path.c_str(), b2::cwd_str().c_str());
266         return false;
267     }
268 
269     return true;
270 }
271