summaryrefslogtreecommitdiff
path: root/tools/minicargo/build.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/minicargo/build.cpp')
-rw-r--r--tools/minicargo/build.cpp359
1 files changed, 359 insertions, 0 deletions
diff --git a/tools/minicargo/build.cpp b/tools/minicargo/build.cpp
index 48c296aa..3780e822 100644
--- a/tools/minicargo/build.cpp
+++ b/tools/minicargo/build.cpp
@@ -10,6 +10,11 @@
#include <algorithm>
#include <sstream> // stringstream
#include <cstdlib> // setenv
+#include <thread>
+#include <mutex>
+#include <condition_variable>
+#include <climits>
+#include <cassert>
#ifdef _WIN32
# include <Windows.h>
#else
@@ -221,6 +226,360 @@ struct Timestamp
}
};
+BuildList2::BuildList2(const PackageManifest& manifest, const BuildOptions& opts):
+ m_root_manifest(manifest)
+{
+ struct Builder {
+ struct Ent {
+ const PackageManifest* package;
+ bool native;
+ unsigned level;
+ };
+ ::std::vector<Ent> m_list;
+
+ void add_package(const PackageManifest& p, unsigned level, bool include_build, bool is_native)
+ {
+ TRACE_FUNCTION_F(p.name());
+ // TODO: If this is a proc macro, set `is_native`
+ // If the package is already loaded
+ for(auto& ent : m_list)
+ {
+ if(ent.package == &p && ent.native == is_native && ent.level >= level)
+ {
+ // NOTE: Only skip if this package will be built before we needed (i.e. the level is greater)
+ return ;
+ }
+ // Keep searching (might already have a higher entry)
+ }
+ m_list.push_back({ &p, is_native, level });
+ add_dependencies(p, level, include_build, is_native);
+ }
+ void add_dependencies(const PackageManifest& p, unsigned level, bool include_build, bool is_native)
+ {
+ for (const auto& dep : p.dependencies())
+ {
+ if( dep.is_disabled() )
+ {
+ continue ;
+ }
+ DEBUG(p.name() << ": Dependency " << dep.name());
+ add_package(dep.get_package(), level+1, include_build, is_native);
+ }
+
+ if( p.build_script() != "" && include_build )
+ {
+ for(const auto& dep : p.build_dependencies())
+ {
+ if( dep.is_disabled() )
+ {
+ continue ;
+ }
+ DEBUG(p.name() << ": Build Dependency " << dep.name());
+ add_package(dep.get_package(), level+1, true, true);
+ }
+ }
+ }
+ void sort_list()
+ {
+ ::std::sort(m_list.begin(), m_list.end(), [](const auto& a, const auto& b){ return a.level > b.level; });
+
+ // Needed to deduplicate after sorting (`add_package` doesn't fully dedup)
+ for(auto it = m_list.begin(); it != m_list.end(); )
+ {
+ auto it2 = ::std::find_if(m_list.begin(), it, [&](const auto& x){ return x.package == it->package; });
+ if( it2 != it )
+ {
+ DEBUG((it - m_list.begin()) << ": Duplicate " << it->package->name() << " - Already at pos " << (it2 - m_list.begin()));
+ it = m_list.erase(it);
+ }
+ else
+ {
+ DEBUG((it - m_list.begin()) << ": Keep " << it->package->name() << ", level = " << it->level);
+ ++it;
+ }
+ }
+ }
+ };
+
+ bool cross_compiling = false;
+ Builder b;
+ b.add_dependencies(manifest, 0, !opts.build_script_overrides.is_valid(), !cross_compiling);
+ if( manifest.has_library() )
+ {
+ b.m_list.push_back({ &manifest, !cross_compiling, 0 });
+ }
+
+ // TODO: Add the binaries too?
+ // - They need slightly different treatment.
+
+ b.sort_list();
+
+
+ // Move the contents of the above list to this class's list
+ m_list.reserve(b.m_list.size());
+ for(const auto& e : b.m_list)
+ {
+ m_list.push_back({ e.package, {} });
+ }
+ // Fill in all of the dependents (i.e. packages that will be closer to being buildable when the package is built)
+ for(size_t i = 0; i < m_list.size(); i++)
+ {
+ const auto* cur = m_list[i].package;
+ for(size_t j = i+1; j < m_list.size(); j ++)
+ {
+ for( const auto& dep : m_list[j].package->dependencies() )
+ {
+ if( !dep.is_disabled() && &dep.get_package() == cur )
+ {
+ m_list[i].dependents.push_back(j);
+ }
+ }
+ }
+ }
+}
+bool BuildList2::build(BuildOptions opts, unsigned num_jobs)
+{
+ bool include_build = !opts.build_script_overrides.is_valid();
+ Builder builder { ::std::move(opts) };
+
+ // Pre-count how many dependencies are remaining for each package
+ struct BuildState
+ {
+ ::std::vector<unsigned> num_deps_remaining;
+ ::std::vector<unsigned> build_queue;
+
+ int complete_package(unsigned index, const ::std::vector<Entry>& list)
+ {
+ int rv = 0;
+ DEBUG("Completed " << list[index].package->name());
+
+ for(auto d : list[index].dependents)
+ {
+ assert(this->num_deps_remaining[d] > 0);
+ this->num_deps_remaining[d] --;
+ DEBUG("- " << list[d].package->name() << " has " << this->num_deps_remaining[d] << " deps remaining");
+ if( this->num_deps_remaining[d] == 0 )
+ {
+ rv ++;
+ this->build_queue.push_back(d);
+ }
+ }
+
+ return rv;
+ }
+
+ unsigned get_next()
+ {
+ assert(!this->build_queue.empty());
+ unsigned rv = this->build_queue.back();
+ this->build_queue.pop_back();
+ return rv;
+ }
+ };
+ BuildState state;
+ state.num_deps_remaining.reserve(m_list.size());
+ for(const auto& e : m_list)
+ {
+ const auto& p = *e.package;
+ unsigned n_deps = 0;
+ for (const auto& dep : p.dependencies())
+ {
+ if( dep.is_disabled() )
+ {
+ continue ;
+ }
+ n_deps ++;
+ }
+
+ if( p.build_script() != "" && include_build )
+ {
+ for(const auto& dep : p.build_dependencies())
+ {
+ if( dep.is_disabled() )
+ {
+ continue ;
+ }
+ n_deps ++;
+ }
+ }
+ // If there's no dependencies for this package, add it to the build queue
+ if( n_deps == 0 )
+ {
+ state.build_queue.push_back(state.num_deps_remaining.size());
+ }
+ state.num_deps_remaining.push_back( n_deps );
+ }
+
+ // Actually do the build
+ if( num_jobs > 1 )
+ {
+ class Semaphore
+ {
+ ::std::mutex mutex;
+ ::std::condition_variable condvar;
+ unsigned count = 0;
+ public:
+ void notify_max() {
+ ::std::lock_guard<::std::mutex> lh { this->mutex };
+ count = UINT_MAX;
+ condvar.notify_all();
+ }
+ void notify() {
+ ::std::lock_guard<::std::mutex> lh { this->mutex };
+ if( count == UINT_MAX )
+ return;
+ ++ count;
+ condvar.notify_one();
+ }
+ void wait() {
+ ::std::unique_lock<::std::mutex> lh { this->mutex };
+ while( count == 0 )
+ {
+ condvar.wait(lh);
+ }
+ if( count == UINT_MAX )
+ return;
+ -- count;
+ }
+ };
+ struct Queue
+ {
+ Semaphore dead_threads;
+ Semaphore avaliable_tasks;
+
+ ::std::mutex mutex;
+ BuildState state;
+
+ unsigned num_active;
+ bool failure;
+ bool complete; // Set if num_active==0 and tasks.empty()
+
+ Queue(BuildState x):
+ state(::std::move(x)),
+ num_active(0),
+ failure(false),
+ complete(false)
+ {
+ }
+
+ void signal_all() {
+ avaliable_tasks.notify_max();
+ }
+ };
+ struct H {
+ static void thread_body(unsigned my_idx, const ::std::vector<Entry>* list_p, Queue* queue_p, const Builder* builder)
+ {
+ const auto& list = *list_p;
+ auto& queue = *queue_p;
+ for(;;)
+ {
+ DEBUG("Thread " << my_idx << ": waiting");
+ queue.avaliable_tasks.wait();
+
+ if( queue.complete || queue.failure )
+ {
+ DEBUG("Thread " << my_idx << ": Terminating");
+ break;
+ }
+
+ unsigned cur;
+ {
+ ::std::lock_guard<::std::mutex> sl { queue.mutex };
+ cur = queue.state.get_next();
+ queue.num_active ++;
+ }
+
+ DEBUG("Thread " << my_idx << ": Starting " << cur << " - " << list[cur].package->name());
+ if( ! builder->build_library(*list[cur].package) )
+ {
+ queue.failure = true;
+ queue.signal_all();
+ }
+ else
+ {
+ ::std::lock_guard<::std::mutex> sl { queue.mutex };
+ queue.num_active --;
+ int v = queue.state.complete_package(cur, list);
+ while(v--)
+ {
+ queue.avaliable_tasks.notify();
+ }
+
+ // If the queue is empty, and there's no active jobs, stop.
+ if( queue.state.build_queue.empty() && queue.num_active == 0 )
+ {
+ queue.complete = true;
+ queue.signal_all();
+ }
+ }
+ }
+
+ queue.dead_threads.notify();
+ }
+ };
+ Queue queue { state };
+
+
+ ::std::vector<::std::thread> threads;
+ threads.reserve(num_jobs);
+ DEBUG("Spawning " << num_jobs << " worker threads");
+ for(unsigned i = 0; i < num_jobs; i++)
+ {
+ threads.push_back(::std::thread(H::thread_body, i, &this->m_list, &queue, &builder));
+ }
+
+ DEBUG("Poking jobs");
+ for(auto i = queue.state.build_queue.size(); i --;)
+ {
+ queue.avaliable_tasks.notify();
+ }
+
+ // All jobs are done, wait for each thread to complete
+ for(unsigned i = 0; i < num_jobs; i++)
+ {
+ threads[i].join();
+ DEBUG("> Thread " << i << " complete");
+ }
+
+ }
+ else if( num_jobs == 1 )
+ {
+ while( !state.build_queue.empty() )
+ {
+ auto cur = state.get_next();
+
+ if( ! builder.build_library(*m_list[cur].package) )
+ {
+ return false;
+ }
+ state.complete_package(cur, m_list);
+ }
+ }
+ else
+ {
+ ::std::cout << "DRY RUN BUILD" << ::std::endl;
+ unsigned pass = 0;
+ while( !state.build_queue.empty() )
+ {
+ auto queue = ::std::move(state.build_queue);
+ for(auto idx : queue)
+ {
+ ::std::cout << pass << ": " << m_list[idx].package->name() << ::std::endl;
+ }
+ for(auto idx : queue)
+ {
+ state.complete_package(idx, m_list);
+ }
+ pass ++;
+ }
+ }
+
+ // Now that all libraries are done, build the binaries (if present)
+ return this->m_root_manifest.foreach_binaries([&](const auto& bin_target) {
+ return builder.build_target(this->m_root_manifest, bin_target);
+ });
+}
+
bool MiniCargo_Build(const PackageManifest& manifest, BuildOptions opts)
{
BuildList list;